class TopMenuBar(Menu): def __init__(self, parent): Menu.__init__(self, parent) self.parent = parent self.newmenu = Menu(self) self.showmenu = Menu(self) self.added_buttons = [] self.newmenu.add_command(label="NPC", command=self.parent.onNew) self.newmenu.add_separator() self.newmenu.add_command(label="Save", command=self.parent.save) self.newmenu.add_command(label="Load", command=self.parent.load) self.showmenu.add_command(label="Info", command=self.parent.get_info) self.showmenu.add_command(label="Save Image", command=self.parent.save_image) self.newmenu.add_separator() self.showmenu.add_separator() self.add_cascade(label="New", menu=self.newmenu) self.add_cascade(label="Show", menu=self.showmenu) self.add_command(label="Exit", command=self.parent.quit) def add_button(self, menu, name, call): func = partial(call, name) if menu == "new": self.newmenu.add_command(label=name, command=func) elif menu == "show": self.showmenu.add_command(label=name, command=func) self.added_buttons.append(name) def remove_all(self): for but in self.added_buttons: self.remove_item(name) def remove_item(self, name): self.showmenu.delete(name)
class TopMenuBar(Menu): def __init__(self, parent): Menu.__init__(self, parent) self.parent = parent self.newmenu = Menu(self) self.showmenu = Menu(self) self.added_buttons = [] self.newmenu.add_command(label="NPC", command=self.parent.onNew) self.newmenu.add_separator() self.newmenu.add_command(label="Save", command=self.parent.save) self.newmenu.add_command(label="Load", command=self.parent.load) self.showmenu.add_command(label="Info", command=self.parent.get_info) self.showmenu.add_command(label="Save Image", command=self.parent.save_image) self.newmenu.add_separator() self.showmenu.add_separator() self.add_cascade(label="New", menu=self.newmenu) self.add_cascade(label="Show", menu=self.showmenu) self.add_command(label="Exit", command=self.parent.quit) def add_button(self, menu, name, call): func = partial(call, name) if menu == "new": self.newmenu.add_command(label=name, command=func) elif menu == "show": self.showmenu.add_command(label=name, command=func) self.added_buttons.append(name) def remove_all(self): for but in self.added_buttons: self.remove_item(name) def remove_item(self, name): self.showmenu.delete(name)
class AppMenu(Menu): def __init__(self, root): self.root = root Menu.__init__(self, root, postcommand=self.update_menu) m_file = Menu(self, tearoff=0) self.add_cascade(label='File', menu=m_file) m_file.add_command(label='Open', command=root.editor.open) m_file.add_command(label='Save', command=root.editor.save) m_file.add_command(label='Save as', command=root.editor.save_as) m_file.add_separator() self.m_recent = Menu(m_file, tearoff=0) m_file.add_cascade(label='Recent', menu=self.m_recent) m_file.add_separator() m_file.add_command(label='Exit', command=root.close) self.add_cascade(label='Run Script (F5)', command=self.root.console.run_script) self.m_firmware = FirmwareMenu(self, root) self.add_cascade(label='Firmware', menu=self.m_firmware) self.add_command(label='About', command=self.root.about) def clear_recent(self): self.root.settings('recent', []) def update_menu(self): self.m_recent.delete(0, 20) for file in self.root.settings('recent'): self.m_recent.add_command( label=file, command=lambda file=file: self.root.editor.open(file)) self.m_recent.add_separator() self.m_recent.add_command(label='Clear', command=self.clear_recent)
def rightKey(self, event, editor): menubar = Menu(self.tk_win, tearoff=False) # 创建一个菜单 menubar.delete(0, END) # menubar.add_command(label='剪切', command=lambda: self.cut(editor)) menubar.add_command(label='复制', command=lambda: self.copy(editor)) # menubar.add_command(label='粘贴', command=lambda: self.paste(editor)) menubar.post(event.x_root, event.y_root)
def clear(user_menu: tk.Menu): try: user_menu.delete(0) F = open(".\password.txt", "a") F.seek(0) F.truncate() F.close() except IOError: tk.messagebox.showinfo(title="Hi", message="can not find password.txt")
def show_menu(self, menu: tk.Menu) -> None: """ Метод для отображения меню пользователя :param menu: меню пользователя :return: None """ if Config.DEBUG: print('Game show_menu') self.config(menu=menu) menu.delete(0, 2) if menu == self.user_menu: self.create_user_menu() else: self.create_standart_menu() menu.add_command(label='О программе', command=self.about)
def accounts_postcommand(menu: tk.Menu): """Update the accounts menu whenever the user clicks on the menu bar. Args: menu: The accounts menu. This will delete every menu item from the second item to the end. Each account will be added showing a combination of institution and account name. Menu postcommands are subordinate to the main menu in the module hierarchy so are best placed in the main menu module. """ start_item_ix = 2 menu.delete(start_item_ix, menu.index(tk.END)) inst_name_accs = sorted([("{} {}…".format(acc.inst, acc.name), acc) for acc in config.data.accs]) for inst_name, acc in inst_name_accs: menu.add_command(label=inst_name, command=lambda acc=acc: accountsmenu.account_edit(acc), state=tk.ACTIVE)
class Sync(Tk): """FolderSync main window.""" def __init__(self): Tk.__init__(self, className='FolderSync') self.title("FolderSync") self.geometry("%ix%i" % (self.winfo_screenwidth(), self.winfo_screenheight())) self.protocol("WM_DELETE_WINDOW", self.quitter) self.icon = PhotoImage(master=self, file=IM_ICON) self.iconphoto(True, self.icon) self.rowconfigure(2, weight=1) self.columnconfigure(0, weight=1) # --- icons self.img_about = PhotoImage(master=self, file=IM_ABOUT) self.img_open = PhotoImage(master=self, file=IM_OPEN) self.img_plus = PhotoImage(master=self, file=IM_PLUS) self.img_moins = PhotoImage(master=self, file=IM_MOINS) self.img_sync = PhotoImage(master=self, file=IM_SYNC) self.img_prev = PhotoImage(master=self, file=IM_PREV) self.img_expand = PhotoImage(master=self, file=IM_EXPAND) self.img_collapse = PhotoImage(master=self, file=IM_COLLAPSE) self.original = "" self.sauvegarde = "" # list of files / folders to delete before starting the copy because # they are not of the same type on the original and the backup self.pb_chemins = [] self.err_copie = False self.err_supp = False # --- init log files l = [f for f in listdir(PATH) if match(r"foldersync[0-9]+.pid", f)] nbs = [] for f in l: with open(join(PATH, f)) as fich: old_pid = fich.read().strip() if exists("/proc/%s" % old_pid): nbs.append(int(search(r"[0-9]+", f).group())) else: remove(join(PATH, f)) if not nbs: i = 0 else: nbs.sort() i = 0 while i in nbs: i += 1 self.pidfile = PID_FILE % i open(self.pidfile, 'w').write(str(getpid())) self.log_copie = LOG_COPIE % i self.log_supp = LOG_SUPP % i self.logger_copie = setup_logger("copie", self.log_copie) self.logger_supp = setup_logger("supp", self.log_supp) date = datetime.now().strftime('%d/%m/%Y %H:%M') self.logger_copie.info("\n### %s ###\n" % date) self.logger_supp.info("\n### %s ###\n" % date) # --- filenames and extensions that will not be copied exclude_list = split(r'(?<!\\) ', CONFIG.get("Defaults", "exclude_copie")) self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[1:]) else: self.exclude_names.append(elt.replace("\ ", " ")) # --- paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] # while "" in self.exclude_path_supp: # self.exclude_path_supp.remove("") self.q_copie = Queue() self.q_supp = Queue() # True if a copy / deletion is running self.is_running_copie = False self.is_running_supp = False self.style = Style(self) self.style.theme_use("clam") self.style.configure("TProgressbar", troughcolor='lightgray', background='#387EF5', lightcolor="#5D95F5", darkcolor="#2758AB") self.style.map("TProgressbar", lightcolor=[("disabled", "white")], darkcolor=[("disabled", "gray")]) self.style.configure("folder.TButton", padding=0) # --- menu self.menu = Menu(self, tearoff=False) self.configure(menu=self.menu) # -------- recents self.menu_recent = Menu(self.menu, tearoff=False) if RECENT: for ch_o, ch_s in RECENT: self.menu_recent.add_command( label="%s -> %s" % (ch_o, ch_s), command=lambda o=ch_o, s=ch_s: self.open(o, s)) else: self.menu.entryconfigure(0, state="disabled") # -------- favorites self.menu_fav = Menu(self.menu, tearoff=False) self.menu_fav_del = Menu(self.menu_fav, tearoff=False) self.menu_fav.add_command(label=_("Add"), image=self.img_plus, compound="left", command=self.add_fav) self.menu_fav.add_cascade(label=_("Remove"), image=self.img_moins, compound="left", menu=self.menu_fav_del) for ch_o, ch_s in FAVORIS: label = "%s -> %s" % (ch_o, ch_s) self.menu_fav.add_command( label=label, command=lambda o=ch_o, s=ch_s: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") # -------- log files menu_log = Menu(self.menu, tearoff=False) menu_log.add_command(label=_("Copy"), command=self.open_log_copie) menu_log.add_command(label=_("Removal"), command=self.open_log_suppression) # -------- settings menu_params = Menu(self.menu, tearoff=False) self.copy_links = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "copy_links")) self.show_size = BooleanVar(self, value=CONFIG.getboolean( "Defaults", "show_size")) menu_params.add_checkbutton(label=_("Copy links"), variable=self.copy_links, command=self.toggle_copy_links) menu_params.add_checkbutton(label=_("Show total size"), variable=self.show_size, command=self.toggle_show_size) self.langue = StringVar(self, CONFIG.get("Defaults", "language")) menu_lang = Menu(menu_params, tearoff=False) menu_lang.add_radiobutton(label="English", value="en", variable=self.langue, command=self.change_language) menu_lang.add_radiobutton(label="Français", value="fr", variable=self.langue, command=self.change_language) menu_params.add_cascade(label=_("Language"), menu=menu_lang) menu_params.add_command(label=_("Exclude from copy"), command=self.exclusion_copie) menu_params.add_command(label=_("Exclude from removal"), command=self.exclusion_supp) self.menu.add_cascade(label=_("Recents"), menu=self.menu_recent) self.menu.add_cascade(label=_("Favorites"), menu=self.menu_fav) self.menu.add_cascade(label=_("Log"), menu=menu_log) self.menu.add_cascade(label=_("Settings"), menu=menu_params) self.menu.add_command(image=self.img_prev, compound="center", command=self.list_files_to_sync) self.menu.add_command(image=self.img_sync, compound="center", state="disabled", command=self.synchronise) self.menu.add_command(image=self.img_about, compound="center", command=lambda: About(self)) # --- tooltips wrapper = TooltipMenuWrapper(self.menu) wrapper.add_tooltip(4, _('Preview')) wrapper.add_tooltip(5, _('Sync')) wrapper.add_tooltip(6, _('About')) # --- path selection frame_paths = Frame(self) frame_paths.grid(row=0, sticky="ew", pady=(10, 0)) frame_paths.columnconfigure(0, weight=1) frame_paths.columnconfigure(1, weight=1) f1 = Frame(frame_paths, height=26) f2 = Frame(frame_paths, height=26) f1.grid(row=0, column=0, sticky="ew") f2.grid(row=0, column=1, sticky="ew") f1.grid_propagate(False) f2.grid_propagate(False) f1.columnconfigure(1, weight=1) f2.columnconfigure(1, weight=1) # -------- path to original Label(f1, text=_("Original")).grid(row=0, column=0, padx=(10, 4)) f11 = Frame(f1) f11.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_orig = Entry(f11) self.entry_orig.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_orig = Button(f1, image=self.img_open, style="folder.TButton", command=self.open_orig) self.b_open_orig.grid(row=0, column=2, padx=(0, 7)) # -------- path to backup Label(f2, text=_("Backup")).grid(row=0, column=0, padx=(8, 4)) f22 = Frame(f2) f22.grid(row=0, column=1, sticky="nsew", padx=(4, 0)) self.entry_sauve = Entry(f22) self.entry_sauve.place(x=1, y=0, bordermode='outside', relwidth=1) self.b_open_sauve = Button(f2, image=self.img_open, width=2, style="folder.TButton", command=self.open_sauve) self.b_open_sauve.grid(row=0, column=5, padx=(0, 10)) paned = PanedWindow(self, orient='horizontal') paned.grid(row=2, sticky="eswn") paned.rowconfigure(0, weight=1) paned.columnconfigure(1, weight=1) paned.columnconfigure(0, weight=1) # --- left side frame_left = Frame(paned) paned.add(frame_left, weight=1) frame_left.rowconfigure(3, weight=1) frame_left.columnconfigure(0, weight=1) # -------- files to copy f_left = Frame(frame_left) f_left.columnconfigure(2, weight=1) f_left.grid(row=2, columnspan=2, pady=(4, 2), padx=(10, 4), sticky="ew") Label(f_left, text=_("To copy")).grid(row=0, column=2) frame_copie = Frame(frame_left) frame_copie.rowconfigure(0, weight=1) frame_copie.columnconfigure(0, weight=1) frame_copie.grid(row=3, column=0, sticky="eswn", columnspan=2, pady=(2, 4), padx=(10, 4)) self.tree_copie = CheckboxTreeview(frame_copie, selectmode='none', show='tree') self.b_expand_copie = Button(f_left, image=self.img_expand, style="folder.TButton", command=self.tree_copie.expand_all) TooltipWrapper(self.b_expand_copie, text=_("Expand all")) self.b_expand_copie.grid(row=0, column=0) self.b_expand_copie.state(("disabled", )) self.b_collapse_copie = Button(f_left, image=self.img_collapse, style="folder.TButton", command=self.tree_copie.collapse_all) TooltipWrapper(self.b_collapse_copie, text=_("Collapse all")) self.b_collapse_copie.grid(row=0, column=1, padx=4) self.b_collapse_copie.state(("disabled", )) self.tree_copie.tag_configure("warning", foreground="red") self.tree_copie.tag_configure("link", font="tkDefaultFont 9 italic", foreground="blue") self.tree_copie.tag_bind("warning", "<Button-1>", self.show_warning) self.tree_copie.grid(row=0, column=0, sticky="eswn") self.scroll_y_copie = Scrollbar(frame_copie, orient="vertical", command=self.tree_copie.yview) self.scroll_y_copie.grid(row=0, column=1, sticky="ns") self.scroll_x_copie = Scrollbar(frame_copie, orient="horizontal", command=self.tree_copie.xview) self.scroll_x_copie.grid(row=1, column=0, sticky="ew") self.tree_copie.configure(yscrollcommand=self.scroll_y_copie.set, xscrollcommand=self.scroll_x_copie.set) self.pbar_copie = Progressbar(frame_left, orient="horizontal", mode="determinate") self.pbar_copie.grid(row=4, columnspan=2, sticky="ew", padx=(10, 4), pady=4) self.pbar_copie.state(("disabled", )) # --- right side frame_right = Frame(paned) paned.add(frame_right, weight=1) frame_right.rowconfigure(3, weight=1) frame_right.columnconfigure(0, weight=1) # -------- files to delete f_right = Frame(frame_right) f_right.columnconfigure(2, weight=1) f_right.grid(row=2, columnspan=2, pady=(4, 2), padx=(4, 10), sticky="ew") Label(f_right, text=_("To remove")).grid(row=0, column=2) frame_supp = Frame(frame_right) frame_supp.rowconfigure(0, weight=1) frame_supp.columnconfigure(0, weight=1) frame_supp.grid(row=3, columnspan=2, sticky="eswn", pady=(2, 4), padx=(4, 10)) self.tree_supp = CheckboxTreeview(frame_supp, selectmode='none', show='tree') self.b_expand_supp = Button(f_right, image=self.img_expand, style="folder.TButton", command=self.tree_supp.expand_all) TooltipWrapper(self.b_expand_supp, text=_("Expand all")) self.b_expand_supp.grid(row=0, column=0) self.b_expand_supp.state(("disabled", )) self.b_collapse_supp = Button(f_right, image=self.img_collapse, style="folder.TButton", command=self.tree_supp.collapse_all) TooltipWrapper(self.b_collapse_supp, text=_("Collapse all")) self.b_collapse_supp.grid(row=0, column=1, padx=4) self.b_collapse_supp.state(("disabled", )) self.tree_supp.grid(row=0, column=0, sticky="eswn") self.scroll_y_supp = Scrollbar(frame_supp, orient="vertical", command=self.tree_supp.yview) self.scroll_y_supp.grid(row=0, column=1, sticky="ns") self.scroll_x_supp = Scrollbar(frame_supp, orient="horizontal", command=self.tree_supp.xview) self.scroll_x_supp.grid(row=1, column=0, sticky="ew") self.tree_supp.configure(yscrollcommand=self.scroll_y_supp.set, xscrollcommand=self.scroll_x_supp.set) self.pbar_supp = Progressbar(frame_right, orient="horizontal", mode="determinate") self.pbar_supp.grid(row=4, columnspan=2, sticky="ew", padx=(4, 10), pady=4) self.pbar_supp.state(("disabled", )) # --- bindings self.entry_orig.bind("<Key-Return>", self.list_files_to_sync) self.entry_sauve.bind("<Key-Return>", self.list_files_to_sync) def exclusion_supp(self): excl = ExclusionsSupp(self) self.wait_window(excl) # paths that will not be deleted self.exclude_path_supp = [ ch.replace("\ ", " ") for ch in split( r'(?<!\\) ', CONFIG.get("Defaults", "exclude_supp")) if ch ] def exclusion_copie(self): excl = ExclusionsCopie(self) self.wait_window(excl) exclude_list = CONFIG.get("Defaults", "exclude_copie").split(" ") self.exclude_names = [] self.exclude_ext = [] for elt in exclude_list: if elt: if elt[:2] == "*.": self.exclude_ext.append(elt[2:]) else: self.exclude_names.append(elt) def toggle_copy_links(self): CONFIG.set("Defaults", "copy_links", str(self.copy_links.get())) def toggle_show_size(self): CONFIG.set("Defaults", "show_size", str(self.show_size.get())) def open_log_copie(self): open_file(self.log_copie) def open_log_suppression(self): open_file(self.log_supp) def quitter(self): rep = True if self.is_running_copie or self.is_running_supp: rep = askokcancel( _("Confirmation"), _("A synchronization is ongoing, do you really want to quit?"), parent=self) if rep: self.destroy() def del_fav(self, nom): self.menu_fav.delete(nom) self.menu_fav_del.delete(nom) FAVORIS.remove(tuple(nom.split(" -> "))) save_config() if not FAVORIS: self.menu_fav.entryconfigure(1, state="disabled") def add_fav(self): sauvegarde = self.entry_sauve.get() original = self.entry_orig.get() if original != sauvegarde and original and sauvegarde: if exists(original) and exists(sauvegarde): if not (original, sauvegarde) in FAVORIS: FAVORIS.append((original, sauvegarde)) save_config() label = "%s -> %s" % (original, sauvegarde) self.menu_fav.entryconfigure(1, state="normal") self.menu_fav.add_command(label=label, command=lambda o=original, s= sauvegarde: self.open(o, s)) self.menu_fav_del.add_command( label=label, command=lambda nom=label: self.del_fav(nom)) def open(self, ch_o, ch_s): self.entry_orig.delete(0, "end") self.entry_orig.insert(0, ch_o) self.entry_sauve.delete(0, "end") self.entry_sauve.insert(0, ch_s) self.list_files_to_sync() def open_sauve(self): sauvegarde = askdirectory(self.entry_sauve.get(), parent=self) if sauvegarde: self.entry_sauve.delete(0, "end") self.entry_sauve.insert(0, sauvegarde) def open_orig(self): original = askdirectory(self.entry_orig.get(), parent=self) if original: self.entry_orig.delete(0, "end") self.entry_orig.insert(0, original) def sync(self, original, sauvegarde): """ peuple tree_copie avec l'arborescence des fichiers d'original à copier vers sauvegarde et tree_supp avec celle des fichiers de sauvegarde à supprimer """ errors = [] copy_links = self.copy_links.get() excl_supp = [ path for path in self.exclude_path_supp if commonpath([path, sauvegarde]) == sauvegarde ] def get_name(elt): return elt.name.lower() def lower(char): return char.lower() def arbo(tree, parent, n): """ affiche l'arborescence complète de parent et renvoie la longueur maximale des items (pour gérer la scrollbar horizontale) """ m = 0 try: with scandir(parent) as content: l = sorted(content, key=get_name) for item in l: chemin = item.path nom = item.name if item.is_symlink(): if copy_links: tree.insert(parent, 'end', chemin, text=nom, tags=("whole", "link")) m = max(m, len(nom) * 9 + 20 * (n + 1)) elif ((nom not in self.exclude_names) and (splitext(nom)[-1] not in self.exclude_ext)): tree.insert(parent, 'end', chemin, text=nom, tags=("whole", )) m = max(m, len(nom) * 9 + 20 * (n + 1)) if item.is_dir(): m = max(m, arbo(tree, chemin, n + 1)) except NotADirectoryError: pass except Exception as e: errors.append(str(e)) return m def aux(orig, sauve, n, search_supp): m_copie = 0 m_supp = 0 try: lo = listdir(orig) ls = listdir(sauve) except Exception as e: errors.append(str(e)) lo = [] ls = [] lo.sort(key=lambda x: x.lower()) ls.sort(key=lambda x: x.lower()) supp = False copie = False if search_supp: for item in ls: chemin_s = join(sauve, item) if chemin_s not in excl_supp and item not in lo: supp = True self.tree_supp.insert(sauve, 'end', chemin_s, text=item, tags=("whole", )) m_supp = max(m_supp, int(len(item) * 9 + 20 * (n + 1)), arbo(self.tree_supp, chemin_s, n + 1)) for item in lo: chemin_o = join(orig, item) chemin_s = join(sauve, item) if ((item not in self.exclude_names) and (splitext(item)[-1] not in self.exclude_ext)): if item not in ls: # le dossier / fichier n'est pas dans la sauvegarde if islink(chemin_o): if copy_links: copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "link")) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1)))) else: copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", )) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1))), arbo(self.tree_copie, chemin_o, n + 1)) elif islink(chemin_o) and exists(chemin_o): # checking the existence prevent from copying broken links if copy_links: if not islink(chemin_s): self.pb_chemins.append(chemin_o) tags = ("whole", "warning", "link") else: tags = ("whole", "link") self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=tags) m_copie = max(m_copie, int(len(item) * 9 + 20 * (n + 1))) copie = True elif isfile(chemin_o): # first check if chemin_s is also a file if isfile(chemin_s): if getmtime(chemin_o) // 60 > getmtime( chemin_s) // 60: # le fichier f a été modifié depuis la dernière sauvegarde copie = True self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", )) else: self.pb_chemins.append(chemin_o) self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "warning")) elif isdir(chemin_o): # to avoid errors due to unrecognized item types (neither file nor folder nor link) if isdir(chemin_s): self.tree_copie.insert(orig, 'end', chemin_o, text=item) self.tree_supp.insert(sauve, 'end', chemin_s, text=item) c, s, mc, ms = aux( chemin_o, chemin_s, n + 1, search_supp and (chemin_s not in excl_supp)) supp = supp or s copie = copie or c if not c: # nothing to copy self.tree_copie.delete(chemin_o) else: m_copie = max( m_copie, mc, int(len(item) * 9 + 20 * (n + 1))) if not s: # nothing to delete self.tree_supp.delete(chemin_s) else: m_supp = max(m_supp, ms, int(len(item) * 9 + 20 * (n + 1))) else: copie = True self.pb_chemins.append(chemin_o) self.tree_copie.insert(orig, 'end', chemin_o, text=item, tags=("whole", "warning")) m_copie = max( m_copie, (int(len(item) * 9 + 20 * (n + 1))), arbo(self.tree_copie, chemin_o, n + 1)) return copie, supp, m_copie, m_supp self.tree_copie.insert("", 0, original, text=original, tags=("checked", ), open=True) self.tree_supp.insert("", 0, sauvegarde, text=sauvegarde, tags=("checked", ), open=True) c, s, mc, ms = aux(original, sauvegarde, 1, True) if not c: self.tree_copie.delete(original) self.tree_copie.column("#0", minwidth=0, width=0) else: mc = max(len(original) * 9 + 20, mc) self.tree_copie.column("#0", minwidth=mc, width=mc) if not s: self.tree_supp.delete(sauvegarde) self.tree_supp.column("#0", minwidth=0, width=0) else: ms = max(len(sauvegarde) * 9 + 20, mc) self.tree_supp.column("#0", minwidth=ms, width=ms) return errors def show_warning(self, event): if "disabled" not in self.b_open_orig.state(): x, y = event.x, event.y elem = event.widget.identify("element", x, y) if elem == "padding": orig = self.tree_copie.identify_row(y) sauve = orig.replace(self.original, self.sauvegarde) showwarning( _("Warning"), _("%(original)s and %(backup)s are not of the same kind (folder/file/link)" ) % { 'original': orig, 'backup': sauve }, master=self) def list_files_to_sync(self, event=None): """Display in a treeview the file to copy and the one to delete.""" self.pbar_copie.configure(value=0) self.pbar_supp.configure(value=0) self.sauvegarde = self.entry_sauve.get() self.original = self.entry_orig.get() if self.original != self.sauvegarde and self.original and self.sauvegarde: if exists(self.original) and exists(self.sauvegarde): o_s = (self.original, self.sauvegarde) if o_s in RECENT: RECENT.remove(o_s) self.menu_recent.delete("%s -> %s" % o_s) RECENT.insert(0, o_s) self.menu_recent.insert_command( 0, label="%s -> %s" % o_s, command=lambda o=self.original, s=self.sauvegarde: self. open(o, s)) if len(RECENT) == 10: del (RECENT[-1]) self.menu_recent.delete(9) save_config() self.menu.entryconfigure(0, state="normal") self.configure(cursor="watch") self.toggle_state_gui() self.update_idletasks() self.update() self.efface_tree() err = self.sync(self.original, self.sauvegarde) self.configure(cursor="") self.toggle_state_gui() c = self.tree_copie.get_children("") s = self.tree_supp.get_children("") if not (c or s): self.menu.entryconfigure(5, state="disabled") self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) elif not c: self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) elif not s: self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) if err: showerror(_("Errors"), "\n".join(err), master=self) notification_send(_("Scan is finished.")) warnings = self.tree_copie.tag_has('warning') if warnings: showwarning( _("Warning"), _("Some elements to copy (in red) are not of the same kind on the original and the backup." ), master=self) else: showerror(_("Error"), _("Invalid path!"), master=self) def efface_tree(self): """Clear both trees.""" c = self.tree_copie.get_children("") for item in c: self.tree_copie.delete(item) s = self.tree_supp.get_children("") for item in s: self.tree_supp.delete(item) self.b_collapse_copie.state(("disabled", )) self.b_expand_copie.state(("disabled", )) self.b_collapse_supp.state(("disabled", )) self.b_expand_supp.state(("disabled", )) def toggle_state_gui(self): """Toggle the state (normal/disabled) of key elements of the GUI.""" if "disabled" in self.b_open_orig.state(): state = "!disabled" for i in range(7): self.menu.entryconfigure(i, state="normal") else: state = "disabled" for i in range(7): self.menu.entryconfigure(i, state="disabled") self.tree_copie.state((state, )) self.tree_supp.state((state, )) self.entry_orig.state((state, )) self.entry_sauve.state((state, )) self.b_expand_copie.state((state, )) self.b_collapse_copie.state((state, )) self.b_expand_supp.state((state, )) self.b_collapse_supp.state((state, )) self.b_open_orig.state((state, )) self.b_open_sauve.state((state, )) def update_pbar(self): """ Dislay the progress of the copy and deletion and put the GUI back in normal state once both processes are done. """ if not self.is_running_copie and not self.is_running_supp: notification_send(_("Sync is finished.")) self.toggle_state_gui() self.pbar_copie.configure(value=self.pbar_copie.cget("maximum")) self.pbar_supp.configure(value=self.pbar_supp.cget("maximum")) self.menu.entryconfigure(5, state="disabled") self.configure(cursor="") self.efface_tree() msg = "" if self.err_copie: msg += _( "There were errors during the copy, see %(file)s for more details.\n" ) % { 'file': self.log_copie } if self.err_supp: msg += _( "There were errors during the removal, see %(file)s for more details.\n" ) % { 'file': self.log_supp } if msg: showerror(_("Error"), msg, master=self) else: if not self.q_copie.empty(): self.pbar_copie.configure(value=self.q_copie.get()) if not self.q_supp.empty(): self.pbar_supp.configure(value=self.q_supp.get()) self.update() self.after(50, self.update_pbar) @staticmethod def get_list(tree): """Return the list of files/folders to copy/delete (depending on the tree).""" selected = [] def aux(item): tags = tree.item(item, "tags") if "checked" in tags and "whole" in tags: selected.append(item) elif "checked" in tags or "tristate" in tags: ch = tree.get_children(item) for c in ch: aux(c) ch = tree.get_children("") for c in ch: aux(c) return selected def synchronise(self): """ Display the list of files/folders that will be copied / deleted and launch the copy and deletion if the user validates the sync. """ # get files to delete and folder to delete if they are empty a_supp = self.get_list(self.tree_supp) # get files to copy a_copier = self.get_list(self.tree_copie) a_supp_avant_cp = [] for ch in self.pb_chemins: if ch in a_copier: a_supp_avant_cp.append( ch.replace(self.original, self.sauvegarde)) if a_supp or a_copier: Confirmation(self, a_copier, a_supp, a_supp_avant_cp, self.original, self.sauvegarde, self.show_size.get()) def copie_supp(self, a_copier, a_supp, a_supp_avant_cp): """Launch sync.""" self.toggle_state_gui() self.configure(cursor="watch") self.update() self.pbar_copie.state(("!disabled", )) self.pbar_supp.state(("!disabled", )) nbtot_copie = len(a_copier) + len(a_supp_avant_cp) self.pbar_copie.configure(maximum=nbtot_copie, value=0) nbtot_supp = len(a_supp) self.pbar_supp.configure(maximum=nbtot_supp, value=0) self.is_running_copie = True self.is_running_supp = True process_copie = Thread(target=self.copie, name="copie", daemon=True, args=(a_copier, a_supp_avant_cp)) process_supp = Thread(target=self.supp, daemon=True, name="suppression", args=(a_supp, )) process_copie.start() process_supp.start() self.pbar_copie.configure(value=0) self.pbar_supp.configure(value=0) self.update_pbar() def copie(self, a_copier, a_supp_avant_cp): """ Copie tous les fichiers/dossiers de a_copier de original vers sauvegarde en utilisant la commande système cp. Les erreurs rencontrées au cours du processus sont inscrites dans ~/.foldersync/copie.log """ self.err_copie = False orig = abspath(self.original) + "/" sauve = abspath(self.sauvegarde) + "/" chdir(orig) self.logger_copie.info( _("\n###### Copy: %(original)s -> %(backup)s\n") % { 'original': self.original, 'backup': self.sauvegarde }) n = len(a_supp_avant_cp) self.logger_copie.info(_("Removal before copy:")) for i, ch in zip(range(1, n + 1), a_supp_avant_cp): self.logger_copie.info(ch) p_copie = run(["rm", "-r", ch], stderr=PIPE) self.q_copie.put(i) err = p_copie.stderr.decode() if err: self.err_copie = True self.logger_copie.error(err.strip()) self.logger_copie.info(_("Copy:")) for i, ch in zip(range(n + 1, n + 1 + len(a_copier)), a_copier): ch_o = ch.replace(orig, "") self.logger_copie.info("%s -> %s" % (ch_o, sauve)) p_copie = run(["cp", "-ra", "--parents", ch_o, sauve], stderr=PIPE) self.q_copie.put(i) err = p_copie.stderr.decode() if err: self.err_copie = True self.logger_copie.error(err.strip()) self.is_running_copie = False def supp(self, a_supp): """ Supprime tous les fichiers/dossiers de a_supp de original vers sauvegarde en utilisant la commande système rm. Les erreurs rencontrées au cours du processus sont inscrites dans ~/.foldersync/suppression.log. """ self.err_supp = False self.logger_supp.info( _("\n###### Removal: %(original)s -> %(backup)s\n") % { 'original': self.original, 'backup': self.sauvegarde }) for i, ch in enumerate(a_supp): self.logger_supp.info(ch) p_supp = run(["rm", "-r", ch], stderr=PIPE) self.q_supp.put(i + 1) err = p_supp.stderr.decode() if err: self.logger_supp.error(err.strip()) self.err_supp = True self.is_running_supp = False def unlink(self): """Unlink pidfile.""" unlink(self.pidfile) def change_language(self): """Change app language.""" CONFIG.set("Defaults", "language", self.langue.get()) showinfo( _("Information"), _("The language setting will take effect after restarting the application" ))
class EventCalendar(Calendar): """ Calendar widget that can display events. """ def __init__(self, master=None, **kw): """ Create an EventCalendar. KEYWORD OPTIONS COMMON WITH CALENDAR cursor, font, year, month, day, locale, background, foreground, bordercolor, othermonthforeground, selectbackground, selectforeground, normalbackground, normalforeground, weekendbackground, weekendforeground, headersbackground, headersforeground WIDGET-SPECIFIC OPTIONS tooltipforeground, tooltipbackground, tooltipalpha selectmode is set to 'none' and cannot be changed """ tp_fg = kw.pop('tooltipforeground', 'white') tp_bg = kw.pop('tooltipbackground', 'black') tp_alpha = kw.pop('tooltipalpha', 0.8) kw['selectmode'] = 'none' self._events = {} self._weekly_events = {i: [] for i in range(7)} self._events_tooltips = [[None for i in range(7)] for j in range(6)] Calendar.__init__(self, master, class_='EventCalendar', **kw) self._properties['tooltipbackground'] = tp_bg self._properties['tooltipforeground'] = tp_fg self._properties['tooltipalpha'] = tp_alpha self.menu = Menu(self) for i, week in enumerate(self._calendar): for j, day in enumerate(week): day.bind('<Double-1>', lambda e, w=i: self._on_dbclick(e, w)) day.bind('<3>', lambda e, w=i: self._post_menu(e, w)) def update_style(self): cats = {cat: CONFIG.get('Categories', cat).split(', ') for cat in CONFIG.options('Categories')} for cat, (fg, bg) in cats.items(): style = 'ev_%s.%s.TLabel' % (cat, self._style_prefixe) self.style.configure(style, background=bg, foreground=fg) def __setitem__(self, item, value): if item == 'tooltipbackground': self._properties[item] = value for line in self._events_tooltips: for tp in line: if tp is not None: tp.configure(background=value) elif item == 'tooltipforeground': self._properties[item] = value for line in self._events_tooltips: for tp in line: if tp is not None: tp.configure(foreground=value) elif item == 'tooltipalpha': self._properties[item] = value for line in self._events_tooltips: for tp in line: if tp is not None: tp.configure(alpha=value) elif item == 'selectmode': raise AttributeError('This attribute cannot be modified.') else: Calendar.__setitem__(self, item, value) def _get_cal(self, year, month): """Get calendar for given month of given year.""" cal = self._cal.monthdatescalendar(year, month) next_m = month + 1 y = year if next_m == 13: next_m = 1 y += 1 if len(cal) < 6: if cal[-1][-1].month == month: i = 0 else: i = 1 cal.append(self._cal.monthdatescalendar(y, next_m)[i]) if len(cal) < 6: cal.append(self._cal.monthdatescalendar(y, next_m)[i + 1]) return cal def update_sel(self): """Update current day.""" logging.info('Update current day to %s' % self.date.today()) prev_sel = self._sel_date self._sel_date = self.date.today() if prev_sel == self._sel_date: return self._date = self._sel_date.replace(day=1) self._display_calendar() self._display_selection() def _display_calendar(self): Calendar._display_calendar(self) year, month = self._date.year, self._date.month cal = self._get_cal(year, month) # --- clear tooltips for i in range(6): for j in range(7): tp = self._events_tooltips[i][j] if tp is not None: tp.destroy() self._events_tooltips[i][j] = None we_style = 'we.%s.TLabel' % self._style_prefixe we_om_style = 'we_om.%s.TLabel' % self._style_prefixe # --- display events and holidays for w in range(6): for d in range(7): day = cal[w][d] label = self._calendar[w][d] if not label.cget('text'): continue date = day.strftime('%Y/%m/%d') # --- holidays if date in HOLIDAYS: if month == day.month: label.configure(style=we_style) else: label.configure(style=we_om_style) # --- one time event if date in self._events: evts = self._events[date] txt = '\n'.join([k[0] for k in evts]) cat = evts[-1][-1] self._add_to_tooltip(w, d, txt, cat) # --- yearly event date = day.strftime('*/%m/%d') if date in self._events: evts = self._events[date] for desc, iid, start, end, cat in evts: if day >= start and (end is None or day <= end): self._add_to_tooltip(w, d, desc, cat) # --- monthly event date = day.strftime('*/*/%d') if date in self._events: evts = self._events[date] for desc, iid, start, end, cat in evts: if day >= start and (end is None or day <= end): self._add_to_tooltip(w, d, desc, cat) # --- weekly events evts = self._weekly_events[d] for desc, iid, start, end, cat in evts: if day >= start and (end is None or day <= end): self._add_to_tooltip(w, d, desc, cat) self._display_selection() def _add_to_tooltip(self, week_nb, day, txt, cat): tp = self._events_tooltips[week_nb][day] label = self._calendar[week_nb][day] label.configure(style='ev_%s.%s.TLabel' % (cat, self._style_prefixe)) if tp is None: prop = {'background': self._properties['tooltipbackground'], 'foreground': self._properties['tooltipforeground'], 'alpha': self._properties['tooltipalpha'], 'text': txt, 'font': self._font} self._events_tooltips[week_nb][day] = TooltipWrapper(label, **prop) else: tp.configure(text='\n'.join([tp.cget('text'), txt])) def _remove_from_tooltip(self, date, txt): y1, y2 = date.year, self._date.year m1, m2 = date.month, self._date.month if y1 == y2 or (y1 - y2 == 1 and m1 == 1 and m2 == 12) or (y2 - y1 == 1 and m2 == 1 and m1 == 12): _, week_nb, d = date.isocalendar() d -= 1 week_nb -= self._date.isocalendar()[1] week_nb %= 52 if week_nb < 6: tp = self._events_tooltips[week_nb][d] if tp is not None: text = tp.cget('text').split('\n') try: text.remove(txt) except ValueError: return if text: tp.configure(text='\n'.join(text)) else: tp.destroy() self._events_tooltips[week_nb][d] = None label = self._calendar[week_nb][d] if type(date) == datetime: date = date.date() if date == self._sel_date: label.configure(style='sel.%s.TLabel' % self._style_prefixe) elif date.strftime('%Y/%m/%d') in HOLIDAYS: if m1 == m2: label.configure(style='we.%s.TLabel' % self._style_prefixe) else: label.configure(style='we_om.%s.TLabel' % self._style_prefixe) else: if m1 == m2: if d < 5: label.configure(style='normal.%s.TLabel' % self._style_prefixe) else: label.configure(style='we.%s.TLabel' % self._style_prefixe) else: if d < 5: label.configure(style='normal_om.%s.TLabel' % self._style_prefixe) else: label.configure(style='we_om.%s.TLabel' % self._style_prefixe) def _show_event(self, date, txt, cat): y1, y2 = date.year, self._date.year m1, m2 = date.month, self._date.month if y1 == y2 or (y1 - y2 == 1 and m1 == 1 and m2 == 12) or (y2 - y1 == 1 and m2 == 1 and m1 == 12): _, w, d = date.isocalendar() w -= self._date.isocalendar()[1] w %= 52 if w < 6: self._add_to_tooltip(w, d - 1, txt, cat) def _add_event(self, date, desc, iid, repeat, cat): year, month, day = date.year, date.month, date.day if not repeat: date2 = date.strftime('%Y/%m/%d') if date2 not in self._events: self._events[date2] = [] self._events[date2].append((desc, iid, cat)) self._show_event(date, desc, cat) else: start = date.date() freq = repeat['Frequency'] if repeat['Limit'] == 'until': end = repeat['EndDate'] elif repeat['Limit'] == 'after': nb = repeat['NbTimes'] - 1 # date is the first time if freq == 'year': end = date.replace(year=date.year + nb) elif freq == 'month': m = date.month + nb month = m % 12 year = date.year + m // 12 end = date.replace(year=year, month=month) else: start_day = date.isocalendar()[2] - 1 week_days = [(x - start_day) % 7 for x in repeat['WeekDays']] nb_per_week = len(repeat['WeekDays']) nb_week = nb // nb_per_week rem = nb % nb_per_week end = date + self.timedelta(days=(7 * nb_week + week_days[rem] + 1)) end = end.date() else: end = None if freq == 'year': date2 = date.strftime('*/%m/%d') if date2 not in self._events: self._events[date2] = [] self._events[date2].append((desc, iid, start, end, cat)) ev_date = self.date(self._date.year, month, day) if (end is None or ev_date <= end) and (ev_date >= start): self._show_event(ev_date, desc, cat) elif freq == 'month': date2 = date.strftime('*/*/%d') if date2 not in self._events: self._events[date2] = [] self._events[date2].append((desc, iid, start, end, cat)) # previous month if day > 22: m = self._date.month - 1 y = self._date.year if m == -1: m = 12 y -= 1 try: ev_date = self.date(y, m, day) if (end is None or ev_date <= end) and (ev_date >= start): self._show_event(ev_date, desc, cat) except ValueError: pass # month has no day 'day' # current month try: ev_date = self.date(self._date.year, self._date.month, day) if (end is None or ev_date <= end) and (ev_date >= start): self._show_event(ev_date, desc, cat) except ValueError: pass # month has no day 'day' # next month if day < 15: try: m = self._date.month + 1 y = self._date.year if m == 13: m = 1 y += 1 ev_date = self.date(y, m, day) if (end is None or ev_date <= end) and (ev_date >= start): self._show_event(ev_date, desc, cat) except ValueError: pass # month has no day 'day' elif freq == 'week': cal = self._get_cal(self._date.year, self._date.month) if start < cal[0][0]: w_min = 0 d_min = 0 elif start > cal[-1][-1]: w_min = 6 d_min = 7 else: _, w_min, d_min = start.isocalendar() w_min -= self._date.isocalendar()[1] w_min %= 52 d_min -= 1 if end is None or end > cal[-1][-1]: w_max = 6 d_max = 7 elif end < cal[0][0]: w_max = 0 d_max = 0 else: _, w_max, d_max = end.isocalendar() w_max -= self._date.isocalendar()[1] w_max %= 52 for d in repeat['WeekDays']: self._weekly_events[d].append((desc, iid, start, end, cat)) for w in range(w_min + 1, w_max): self._add_to_tooltip(w, d, desc, cat) if w_min < w_max: for d in repeat['WeekDays']: if d >= d_min: self._add_to_tooltip(w_min, d, desc, cat) if w_max < 6: for d in repeat['WeekDays']: if d < d_max: self._add_to_tooltip(w_max, d, desc, cat) self._display_selection() def _remove_event(self, date, desc, iid, repeat, cat): year, month, day = date.year, date.month, date.day try: if not repeat: date2 = date.strftime('%Y/%m/%d') self._events[date2].remove((desc, iid, cat)) if not self._events[date2]: del(self._events[date2]) self._remove_from_tooltip(date, desc) else: start = date.date() freq = repeat['Frequency'] if repeat['Limit'] == 'until': end = repeat['EndDate'] elif repeat['Limit'] == 'after': nb = repeat['NbTimes'] - 1 # date is the first time if freq == 'year': end = date.replace(year=date.year + nb) elif freq == 'month': m = date.month + nb month = m % 12 year = date.year + m // 12 end = date.replace(year=year, month=month) else: start_day = date.isocalendar()[2] - 1 week_days = [(x - start_day) % 7 for x in repeat['WeekDays']] nb_per_week = len(repeat['WeekDays']) nb_week = nb // nb_per_week rem = nb % nb_per_week end = date + self.timedelta(days=(7 * nb_week + week_days[rem] + 1)) end = end.date() else: end = None if freq == 'year': date2 = date.strftime('*/%m/%d') self._events[date2].remove((desc, iid, start, end, cat)) if not self._events[date2]: del(self._events[date2]) ev_date = self.date(self._date.year, month, day) if (end is None or ev_date <= end) and (ev_date >= start): self._remove_from_tooltip(ev_date, desc) elif freq == 'month': date2 = date.strftime('*/*/%d') self._events[date2].remove((desc, iid, start, end, cat)) if not self._events[date2]: del(self._events[date2]) # previous month if day > 22: m = self._date.month - 1 y = self._date.year if m == -1: m = 12 y -= 1 try: ev_date = self.date(y, m, day) if (end is None or ev_date <= end) and (ev_date >= start): self._remove_from_tooltip(ev_date, desc) except ValueError: pass # month has no day 'day' # current month try: ev_date = self.date(self._date.year, self._date.month, day) if (end is None or ev_date <= end) and (ev_date >= start): self._remove_from_tooltip(ev_date, desc) except ValueError: pass # month has no day 'day' # next month if day < 15: try: m = self._date.month + 1 y = self._date.year if m == 13: m = 1 y += 1 ev_date = self.date(y, m, day) if (end is None or ev_date <= end) and (ev_date >= start): self._remove_from_tooltip(ev_date, desc) except ValueError: pass # month has no day 'day' elif freq == 'week': cal = self._get_cal(self._date.year, self._date.month) for d in repeat['WeekDays']: self._weekly_events[d].remove((desc, iid, start, end, cat)) for w in range(6): self._remove_from_tooltip(cal[w][d], desc) except ValueError: raise ValueError('Event not in calendar.') def _get_date(self, week_row, day): year, month = self._date.year, self._date.month now = datetime.now() + self.timedelta(minutes=5) now = now.replace(minute=(now.minute // 5) * 5) if week_row == 0 and day > 7: # prev month if month == 1: month = 12 year -= 1 else: month -= 1 elif week_row in [4, 5] and day < 15: # next month if month == 12: month = 1 year += 1 else: month += 1 return datetime(year, month, day, now.hour, now.minute) # --- bindings def _on_dbclick(self, event, w): day = int(event.widget.cget('text')) date = self._get_date(w, day) self.master.master.add(date) def _post_menu(self, event, w): self.menu.delete(0, 'end') day = int(event.widget.cget('text')) date = self._get_date(w, day) date2 = date.date() # --- one time events date_str = date.strftime('%Y/%m/%d') if date_str in self._events: evts = self._events[date_str].copy() else: evts = [] # --- yearly events date_str = date.strftime('*/%m/%d') if date_str in self._events: for desc, iid, start, end, cat in self._events[date_str]: if start <= date2 and (end is None or end >= date2): evts.append((desc, iid)) # --- monthly events date_str = date.strftime('*/*/%d') if date_str in self._events: for desc, iid, start, end, cat in self._events[date_str]: if start <= date2 and (end is None or end >= date2): evts.append((desc, iid)) d = date.isocalendar()[2] - 1 # --- weekly events for desc, iid, start, end, cat in self._weekly_events[d]: if start <= date2 and (end is None or end >= date2): evts.append((desc, iid)) self.menu.add_command(label=_('New Event'), command=lambda: self.master.master.add(date)) if evts: self.menu.add_separator() self.menu.add_separator() index_edit = 2 for vals in evts: desc, iid = vals[0], vals[1] self.menu.insert_command(index_edit, label=_("Edit") + " %s" % desc, command=lambda i=iid: self.master.master.edit(i)) index_edit += 1 self.menu.add_command(label=_("Delete") + " %s" % desc, command=lambda i=iid: self.master.master.delete(i)) else: self.menu.add_separator() if date.strftime('%Y/%m/%d') in HOLIDAYS: self.menu.add_command(label=_('Remove Holiday'), command=lambda: self.remove_holiday(date.date())) else: self.menu.add_command(label=_('Set Holiday'), command=lambda: self.add_holiday(date.date())) self.menu.tk_popup(event.x_root, event.y_root) # --- public methods def get_events(self, date): """ Return the iid of all events occuring on date. """ evts = [] for desc, iid, cat in self._events.get(date.strftime('%Y/%m/%d'), []): evts.append(iid) for desc, iid, start, end, cat in self._events.get(date.strftime('*/%m/%d'), []): if start <= date and (end is None or end >= date): evts.append(iid) for desc, iid, start, end, cat in self._events.get(date.strftime('*/*/%d'), []): if start <= date and (end is None or end >= date): evts.append(iid) y, w, d = date.isocalendar() for desc, iid, start, end, cat in self._weekly_events[d - 1]: if start <= date and (end is None or end >= date): evts.append(iid) return evts def add_holiday(self, date): year = date.year HOLIDAYS.add(date.strftime('%Y/%m/%d')) if year == self._date.year: _, w, d = date.isocalendar() w -= self._date.isocalendar()[1] w %= 52 if 0 <= w and w < 6: style = self._calendar[w][d - 1].cget('style') we_style = 'we.%s.TLabel' % self._style_prefixe we_om_style = 'we_om.%s.TLabel' % self._style_prefixe normal_style = 'normal.%s.TLabel' % self._style_prefixe normal_om_style = 'normal_om.%s.TLabel' % self._style_prefixe if style == normal_style: self._calendar[w][d - 1].configure(style=we_style) elif style == normal_om_style: self._calendar[w][d - 1].configure(style=we_om_style) def remove_holiday(self, date): year = date.year try: HOLIDAYS.remove(date.strftime('%Y/%m/%d')) if year == self._date.year: _, w, d = date.isocalendar() w -= self._date.isocalendar()[1] w %= 52 if 0 <= w and w < 6: style = self._calendar[w][d - 1].cget('style') we_style = 'we.%s.TLabel' % self._style_prefixe we_om_style = 'we_om.%s.TLabel' % self._style_prefixe normal_style = 'normal.%s.TLabel' % self._style_prefixe normal_om_style = 'normal_om.%s.TLabel' % self._style_prefixe if style == we_style: self._calendar[w][d - 1].configure(style=normal_style) elif style == we_om_style: self._calendar[w][d - 1].configure(style=normal_om_style) except ValueError: raise ValueError('%s is not a holiday.' % format_date(date, locale=self["locale"])) def add_event(self, event): start = event['Start'] end = event['End'] if not event["WholeDay"]: deb = format_time(start, locale=self["locale"]) fin = format_time(end, locale=self["locale"]) desc = '➢ %s - %s %s' % (deb, fin, event['Summary']) else: desc = '➢ %s' % event['Summary'] repeat = event['Repeat'] dt = end - start for d in range(dt.days + 1): self._add_event(start + self.timedelta(days=d), desc, event.iid, repeat, event['Category']) def remove_event(self, event): start = event['Start'] end = event['End'] if not event["WholeDay"]: deb = format_time(start, locale=self["locale"]) fin = format_time(end, locale=self["locale"]) desc = '➢ %s - %s %s' % (deb, fin, event['Summary']) else: desc = '➢ %s' % event['Summary'] repeat = event['Repeat'] dt = end - start for d in range(dt.days + 1): self._remove_event(start + self.timedelta(days=d), desc, event.iid, repeat, event['Category']) def bind(self, *args): Calendar.bind(self, *args) header = self._header_month.master.master header.bind(*args) for widget in header.children.values(): widget.bind(*args) for w in widget.children.values(): w.bind(*args)
class App(Tk): """ Main app. Put an icon in the system tray with a right click menu to create notes. """ def __init__(self): Tk.__init__(self) self.withdraw() self.notes = {} self.img = PhotoImage(file=cst.IM_ICON) self.icon = PhotoImage(master=self, file=cst.IM_ICON_48) self.iconphoto(True, self.icon) self.ewmh = ewmh.EWMH() style = Style(self) style.theme_use("clam") self.close1 = PhotoImage("img_close", file=cst.IM_CLOSE) self.close2 = PhotoImage("img_closeactive", file=cst.IM_CLOSE_ACTIVE) self.roll1 = PhotoImage("img_roll", file=cst.IM_ROLL) self.roll2 = PhotoImage("img_rollactive", file=cst.IM_ROLL_ACTIVE) self.protocol("WM_DELETE_WINDOW", self.quit) self.icon = tktray.Icon(self, docked=True) # --- Menu self.menu_notes = Menu(self.icon.menu, tearoff=False) self.hidden_notes = {cat: {} for cat in CONFIG.options("Categories")} self.menu_show_cat = Menu(self.icon.menu, tearoff=False) self.menu_hide_cat = Menu(self.icon.menu, tearoff=False) self.icon.configure(image=self.img) self.icon.menu.add_command(label=_("New Note"), command=self.new) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Show All'), command=self.show_all) self.icon.menu.add_cascade(label=_('Show Category'), menu=self.menu_show_cat) self.icon.menu.add_cascade(label=_('Show Note'), menu=self.menu_notes, state="disabled") self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Hide All'), command=self.hide_all) self.icon.menu.add_cascade(label=_('Hide Category'), menu=self.menu_hide_cat) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Preferences"), command=self.config) self.icon.menu.add_command(label=_("Note Manager"), command=self.manage) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Backup Notes"), command=self.backup) self.icon.menu.add_command(label=_("Restore Backup"), command=self.restore) self.icon.menu.add_separator() self.icon.menu.add_command(label=_("Export"), command=self.export_notes) self.icon.menu.add_command(label=_("Import"), command=self.import_notes) self.icon.menu.add_separator() self.icon.menu.add_command(label=_('Check for Updates'), command=lambda: UpdateChecker(self)) self.icon.menu.add_command(label=_('About'), command=lambda: About(self)) self.icon.menu.add_command(label=_('Quit'), command=self.quit) # --- Restore notes self.note_data = {} if os.path.exists(PATH_DATA): with open(PATH_DATA, "rb") as fich: dp = pickle.Unpickler(fich) note_data = dp.load() for i, key in enumerate(note_data): self.note_data["%i" % i] = note_data[key] backup() for key in self.note_data: data = self.note_data[key] cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) if data["visible"]: self.notes[key] = Sticky(self, key, **data) else: self.add_note_to_menu(key, data["title"], cat) self.nb = len(self.note_data) self.update_menu() self.update_notes() self.make_notes_sticky() # --- class bindings # newline depending on mode self.bind_class("Text", "<Return>", self.insert_newline) # char deletion taking into account list type self.bind_class("Text", "<BackSpace>", self.delete_char) # change Ctrl+A to select all instead of go to the beginning of the line self.bind_class('Text', '<Control-a>', self.select_all_text) self.bind_class('TEntry', '<Control-a>', self.select_all_entry) # bind Ctrl+Y to redo self.bind_class('Text', '<Control-y>', self.redo_event) # unbind Ctrl+I and Ctrl+B self.bind_class('Text', '<Control-i>', lambda e: None) self.bind_class('Text', '<Control-b>', lambda e: None) # highlight checkboxes when inside text selection self.bind_class("Text", "<ButtonPress-1>", self.highlight_checkboxes, True) self.bind_class("Text", "<ButtonRelease-1>", self.highlight_checkboxes, True) self.bind_class("Text", "<B1-Motion>", self.highlight_checkboxes, True) evs = [ '<<SelectAll>>', '<<SelectLineEnd>>', '<<SelectLineStart>>', '<<SelectNextChar>>', '<<SelectNextLine>>', '<<SelectNextPara>>', '<<SelectNextWord>>', '<<SelectNone>>', '<<SelectPrevChar>>', '<<SelectPrevLine>>', '<<SelectPrevPara>>', '<<SelectPrevWord>>' ] for ev in evs: self.bind_class("Text", ev, self.highlight_checkboxes, True) # check for updates if CONFIG.getboolean("General", "check_update"): UpdateChecker(self) # --- class bindings methods def highlight_checkboxes(self, event): txt = event.widget try: deb = cst.sorting(txt.index("sel.first")) fin = cst.sorting(txt.index("sel.last")) for ch in txt.children.values(): try: i = cst.sorting(txt.index(ch)) if i >= deb and i <= fin: ch.configure(style="sel.TCheckbutton") else: ch.configure(style=txt.master.id + ".TCheckbutton") except TclError: pass except TclError: for ch in txt.children.values(): try: i = cst.sorting(txt.index(ch)) ch.configure(style=txt.master.id + ".TCheckbutton") except TclError: pass def redo_event(self, event): try: event.widget.edit_redo() except TclError: # nothing to redo pass def select_all_entry(self, event): event.widget.selection_range(0, "end") def select_all_text(self, event): event.widget.tag_add("sel", "1.0", "end-1c") self.highlight_checkboxes(event) def delete_char(self, event): txt = event.widget deb_line = txt.get("insert linestart", "insert") tags = txt.tag_names("insert") if txt.tag_ranges("sel"): if txt.tag_nextrange("enum", "sel.first", "sel.last"): update = True else: update = False txt.delete("sel.first", "sel.last") if update: txt.master.update_enum() elif txt.index("insert") != "1.0": if re.match('^\t[0-9]+\.\t$', deb_line) and 'enum' in tags: txt.delete("insert linestart", "insert") txt.insert("insert", "\t\t") txt.master.update_enum() elif deb_line == "\t•\t" and 'list' in tags: txt.delete("insert linestart", "insert") txt.insert("insert", "\t\t") elif deb_line == "\t\t": txt.delete("insert linestart", "insert") elif "todolist" in tags and txt.index("insert") == txt.index( "insert linestart+1c"): try: ch = txt.window_cget("insert-1c", "window") txt.delete("insert-1c") txt.children[ch.split('.')[-1]].destroy() txt.insert("insert", "\t\t") except TclError: txt.delete("insert-1c") else: txt.delete("insert-1c") def insert_newline(self, event): mode = event.widget.master.mode.get() if mode == "list": event.widget.insert("insert", "\n\t•\t") event.widget.tag_add("list", "1.0", "end") elif mode == "todolist": event.widget.insert("insert", "\n") ch = Checkbutton(event.widget, takefocus=False, style=event.widget.master.id + ".TCheckbutton") event.widget.window_create("insert", window=ch) event.widget.tag_add("todolist", "1.0", "end") elif mode == "enum": event.widget.configure(autoseparators=False) event.widget.edit_separator() event.widget.insert("insert", "\n\t0.\t") event.widget.master.update_enum() event.widget.edit_separator() event.widget.configure(autoseparators=True) else: event.widget.insert("insert", "\n") def make_notes_sticky(self): for w in self.ewmh.getClientList(): if w.get_wm_name()[:7] == 'mynotes': self.ewmh.setWmState(w, 1, '_NET_WM_STATE_STICKY') self.ewmh.display.flush() def add_note_to_menu(self, nb, note_title, category): """add note to 'show notes' menu. """ try: name = self.menu_notes.entrycget(category.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] end = menu.index("end") if end is not None: # le menu n'est pas vide titles = self.hidden_notes[category].values() titles = [t for t in titles if t.split(" ~#")[0] == note_title] if titles: title = "%s ~#%i" % (note_title, len(titles) + 1) else: title = note_title else: title = note_title except TclError: # cat is not in the menu menu = Menu(self.menu_notes, tearoff=False) self.menu_notes.add_cascade(label=category.capitalize(), menu=menu) title = note_title menu.add_command(label=title, command=lambda: self.show_note(nb)) self.icon.menu.entryconfigure(4, state="normal") self.hidden_notes[category][nb] = title def backup(self): """Create a backup at the location indicated by user.""" initialdir, initialfile = os.path.split(PATH_DATA_BACKUP % 0) fichier = asksaveasfilename(defaultextension=".backup", filetypes=[], initialdir=initialdir, initialfile="notes.backup0", title=_('Backup Notes')) if fichier: try: with open(fichier, "wb") as fich: dp = pickle.Pickler(fich) dp.dump(self.note_data) except Exception as e: report_msg = e.strerror != 'Permission denied' showerror(_("Error"), _("Backup failed."), traceback.format_exc(), report_msg) def restore(self, fichier=None, confirmation=True): """Restore notes from backup.""" if confirmation: rep = askokcancel( _("Warning"), _("Restoring a backup will erase the current notes."), icon="warning") else: rep = True if rep: if fichier is None: fichier = askopenfilename(defaultextension=".backup", filetypes=[], initialdir=LOCAL_PATH, initialfile="", title=_('Restore Backup')) if fichier: try: keys = list(self.note_data.keys()) for key in keys: self.delete_note(key) if not os.path.samefile(fichier, PATH_DATA): copy(fichier, PATH_DATA) with open(PATH_DATA, "rb") as myfich: dp = pickle.Unpickler(myfich) note_data = dp.load() for i, key in enumerate(note_data): data = note_data[key] note_id = "%i" % i self.note_data[note_id] = data cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) if data["visible"]: self.notes[note_id] = Sticky(self, note_id, **data) self.nb = len(self.note_data) self.update_menu() self.update_notes() except FileNotFoundError: showerror( _("Error"), _("The file {filename} does not exists.").format( filename=fichier)) except Exception as e: showerror(_("Error"), str(e), traceback.format_exc(), True) def show_all(self): """Show all notes.""" for cat in self.hidden_notes.keys(): keys = list(self.hidden_notes[cat].keys()) for key in keys: self.show_note(key) def show_cat(self, category): """Show all notes belonging to category.""" keys = list(self.hidden_notes[category].keys()) for key in keys: self.show_note(key) def hide_all(self): """Hide all notes.""" keys = list(self.notes.keys()) for key in keys: self.notes[key].hide() def hide_cat(self, category): """Hide all notes belonging to category.""" keys = list(self.notes.keys()) for key in keys: if self.note_data[key]["category"] == category: self.notes[key].hide() def manage(self): """Launch note manager.""" Manager(self) def config(self): """Launch the setting manager.""" conf = Config(self) self.wait_window(conf) col_changes, name_changes = conf.get_changes() if col_changes or name_changes: self.update_notes(col_changes, name_changes) self.update_menu() alpha = CONFIG.getint("General", "opacity") / 100 for note in self.notes.values(): note.attributes("-alpha", alpha) note.update_title_font() note.update_text_font() note.update_titlebar() def delete_cat(self, category): """Delete all notes belonging to category.""" keys = list(self.notes.keys()) for key in keys: if self.note_data[key]["category"] == category: self.notes[key].delete(confirmation=False) def delete_note(self, nb): if self.note_data[nb]["visible"]: self.notes[nb].delete(confirmation=False) else: cat = self.note_data[nb]["category"] name = self.menu_notes.entrycget(cat.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] index = menu.index(self.hidden_notes[cat][nb]) menu.delete(index) if menu.index("end") is None: # the menu is empty self.menu_notes.delete(cat.capitalize()) if self.menu_notes.index('end') is None: self.icon.menu.entryconfigure(4, state="disabled") del (self.hidden_notes[cat][nb]) del (self.note_data[nb]) self.save() def show_note(self, nb): """Display the note corresponding to the 'nb' key in self.note_data.""" self.note_data[nb]["visible"] = True cat = self.note_data[nb]["category"] name = self.menu_notes.entrycget(cat.capitalize(), 'menu') if not isinstance(name, str): name = str(name) menu = self.menu_notes.children[name.split('.')[-1]] index = menu.index(self.hidden_notes[cat][nb]) del (self.hidden_notes[cat][nb]) self.notes[nb] = Sticky(self, nb, **self.note_data[nb]) menu.delete(index) if menu.index("end") is None: # the menu is empty self.menu_notes.delete(cat.capitalize()) if self.menu_notes.index('end') is None: self.icon.menu.entryconfigure(4, state="disabled") self.make_notes_sticky() def update_notes(self, col_changes={}, name_changes={}): """Update the notes after changes in the categories.""" categories = CONFIG.options("Categories") categories.sort() self.menu_notes.delete(0, "end") self.hidden_notes = {cat: {} for cat in categories} for key in self.note_data: cat = self.note_data[key]["category"] if cat in name_changes: cat = name_changes[cat] self.note_data[key]["category"] = cat if self.note_data[key]["visible"]: self.notes[key].change_category(cat) elif cat not in categories: default = CONFIG.get("General", "default_category") default_color = CONFIG.get("Categories", default) if self.note_data[key]["visible"]: self.notes[key].change_category(default) self.note_data[key]["category"] = default self.note_data[key]["color"] = default_color cat = default if cat in col_changes: old_color, new_color = col_changes[cat] if self.note_data[key]["color"] == old_color: self.note_data[key]["color"] = new_color if self.note_data[key]["visible"]: self.notes[key].change_color(cst.INV_COLORS[new_color]) if not self.note_data[key]['visible']: self.add_note_to_menu(key, self.note_data[key]["title"], self.note_data[key]['category']) else: self.notes[key].update_menu_cat(categories) self.save() if self.menu_notes.index("end") is not None: self.icon.menu.entryconfigure(4, state="normal") else: self.icon.menu.entryconfigure(4, state="disabled") def update_menu(self): """Populate self.menu_show_cat and self.menu_hide_cat with the categories.""" self.menu_hide_cat.delete(0, "end") self.menu_show_cat.delete(0, "end") categories = CONFIG.options("Categories") categories.sort() for cat in categories: self.menu_show_cat.add_command( label=cat.capitalize(), command=lambda c=cat: self.show_cat(c)) self.menu_hide_cat.add_command( label=cat.capitalize(), command=lambda c=cat: self.hide_cat(c)) def save(self): """Save the data.""" with open(PATH_DATA, "wb") as fich: dp = pickle.Pickler(fich) dp.dump(self.note_data) def new(self): """Create a new note.""" key = "%i" % self.nb self.notes[key] = Sticky(self, key) data = self.notes[key].save_info() data["visible"] = True self.note_data[key] = data self.nb += 1 self.make_notes_sticky() def export_notes(self): export = Export(self) self.wait_window(export) categories_to_export, only_visible = export.get_export() if categories_to_export: initialdir, initialfile = os.path.split(PATH_DATA_BACKUP % 0) fichier = asksaveasfilename(defaultextension=".html", filetypes=[ (_("HTML file (.html)"), "*.html"), (_("Text file (.txt)"), "*.txt"), (_("All files"), "*") ], initialdir=initialdir, initialfile="", title=_('Export Notes As')) if fichier: try: if os.path.splitext(fichier)[-1] == ".html": # --- html export cats = {cat: [] for cat in categories_to_export} for key in self.note_data: cat = self.note_data[key]["category"] if cat in cats and ( (not only_visible) or self.note_data[key]["visible"]): cats[cat].append( (self.note_data[key]["title"], cst.note_to_html(self.note_data[key], self))) text = "" for cat in cats: cat_txt = "<h1 style='text-align:center'>" + _( "Category: {category}").format( category=cat) + "<h1/>\n" text += cat_txt text += "<br>" for title, txt in cats[cat]: text += "<h2 style='text-align:center'>%s</h2>\n" % title text += txt text += "<br>\n" text += "<hr />" text += "<br>\n" text += '<hr style="height: 8px;background-color:grey" />' text += "<br>\n" with open(fichier, "w") as fich: fich.write('<body style="max-width:30em">\n') fich.write( text.encode( 'ascii', 'xmlcharrefreplace').decode("utf-8")) fich.write("\n</body>") # if os.path.splitext(fichier)[-1] == ".txt": else: # --- txt export # export notes to .txt: all formatting is lost cats = {cat: [] for cat in categories_to_export} for key in self.note_data: cat = self.note_data[key]["category"] if cat in cats and ( (not only_visible) or self.note_data[key]["visible"]): cats[cat].append( (self.note_data[key]["title"], cst.note_to_txt(self.note_data[key]))) text = "" for cat in cats: cat_txt = _("Category: {category}").format( category=cat) + "\n" text += cat_txt text += "=" * len(cat_txt) text += "\n\n" for title, txt in cats[cat]: text += title text += "\n" text += "-" * len(title) text += "\n\n" text += txt text += "\n\n" text += "-" * 30 text += "\n\n" text += "#" * 30 text += "\n\n" with open(fichier, "w") as fich: fich.write(text) # else: # # --- pickle export # note_data = {} # for key in self.note_data: # if self.note_data[key]["category"] in categories_to_export: # if (not only_visible) or self.note_data[key]["visible"]: # note_data[key] = self.note_data[key] # # with open(fichier, "wb") as fich: # dp = pickle.Pickler(fich) # dp.dump(note_data) except Exception as e: report_msg = e.strerror != 'Permission denied' showerror(_("Error"), str(e), traceback.format_exc(), report_msg) def import_notes(self): fichier = askopenfilename(defaultextension=".backup", filetypes=[(_("Notes (.notes)"), "*.notes"), (_("All files"), "*")], initialdir=LOCAL_PATH, initialfile="", title=_('Import')) if fichier: try: with open(fichier, "rb") as fich: dp = pickle.Unpickler(fich) note_data = dp.load() for i, key in enumerate(note_data): data = note_data[key] note_id = "%i" % (i + self.nb) self.note_data[note_id] = data cat = data["category"] if not CONFIG.has_option("Categories", cat): CONFIG.set("Categories", cat, data["color"]) self.hidden_notes[cat] = {} if data["visible"]: self.notes[note_id] = Sticky(self, note_id, **data) self.nb = int(max(self.note_data.keys(), key=lambda x: int(x))) + 1 self.update_menu() self.update_notes() except Exception: message = _("The file {file} is not a valid .notes file." ).format(file=fichier) showerror(_("Error"), message, traceback.format_exc()) def cleanup(self): """Remove unused latex images.""" img_stored = os.listdir(cst.PATH_LATEX) img_used = [] for data in self.note_data.values(): img_used.extend(list(data.get("latex", {}).keys())) for img in img_stored: if img not in img_used: os.remove(os.path.join(cst.PATH_LATEX, img)) def quit(self): self.destroy()
class RightClick(): def __init__(self, parent, context): self.parent = parent # create a popup menu self.popup_menu = Menu(self.parent, tearoff=0) self.prev_selection = () self.curr_selection = () self.progress = StringVar() self.progress.set('None') self.context = context self.cached_selection = None #region public methods def popup(self, event): self._add_options() self.popup_menu.post(event.x_root, event.y_root) def set_current_selected(self): # keep track of the files selected each time a right-click occurs if self.prev_selection != self.curr_selection: # assign the previous set of selected files if they have changed self.prev_selection = self.curr_selection # now get the current set of selected files self.curr_selection = self.parent.file_treeview.selection() def undraw(self, event): self.popup_menu.unpost() #region private methods def _add_options(self): # a context dependent function to only add options that are applicable # to the current situation. # first, remove any currently visible entries in the menu so we can # draw only the ones required by the current context self.popup_menu.delete(0, self.popup_menu.index("end")) # now, draw the manu elements required depending on context if self.parent.treeview_select_mode == "NORMAL": # add an option to associate the file as extra data to be included # in the bids output self.popup_menu.add_command(label="Add to BIDS output", command=self._add_to_bids) if ('.CON' in self.context or '.MRK' in self.context): self.popup_menu.add_command(label="Associate", command=self._associate_mrk) else: pass elif self.parent.treeview_select_mode.startswith("ASSOCIATE"): if (('.MRK' in self.context and self.parent.treeview_select_mode == 'ASSOCIATE-MRK') or ('.CON' in self.context and self.parent.treeview_select_mode == 'ASSOCIATE-CON')): self.popup_menu.add_command(label="Associate", command=self._associate_mrk) # give the option to associate one or more mrk files with all con files if (".MRK" in self.context and not self.parent.treeview_select_mode.startswith("ASSOCIATE")): self.popup_menu.add_command( label="Associate with all", command=lambda: self._associate_mrk(all_=True)) # Add an option mark all selected .con files as junk if ".CON" in self.context and not self.context.is_mixed: self.popup_menu.add_command(label="Ignore file(s)", command=self._ignore_cons) self.popup_menu.add_command(label="Include file(s)", command=self._include_cons) if len(self.curr_selection) == 1: fname = self.parent.file_treeview.get_text(self.curr_selection[0]) fpath = self.parent.file_treeview.get_filepath( self.curr_selection[0]) # the selected object selected_obj = self.parent.preloaded_data[self.curr_selection[0]] # if the folder is a BIDS folder allow it to be uploaded to the # archive if BIDS_PATTERN.match(fname): self.popup_menu.add_command( label="Upload to archive", command=lambda: self._upload()) # allow any folder to be sent to another location using the # BIDSMERGE functionality if isinstance(selected_obj, (BIDSTree, Project, Subject, Session)): self.popup_menu.add_command( label="Send to...", command=lambda: self._send_to()) if path.isdir(fpath): if isinstance(self.parent.preloaded_data.get( self.curr_selection[0], None), Folder): self.popup_menu.add_command( label="Assign as BIDS folder", command=self._toggle_bids_folder) def _add_to_bids(self): """Add the selected file(s) to the list of files to be retained across BIDS conversion. """ sids = self.curr_selection parent_obj = None for sid in sids: parent = self.parent.file_treeview.parent(sid) fpath = self.parent.file_treeview.get_filepath(sid) parent_obj = self.parent.preloaded_data.get(parent, None) if isinstance(parent_obj, BIDSContainer): parent_obj.extra_files.append(fpath) def _associate_mrk(self, all_=False): """ Allow the user to select an .mrk file if a .con file has been selected (or vice-versa) and associate the mrk file with the con file. """ # First do a simple check for a mixed selection. if self.context.is_mixed: cont = messagebox.askretrycancel( "Error", ("You have not selected only .mrk or only .con files. Please " "select a single file type at a time or press 'cancel' to " "stop associating.")) if not cont: self.parent.treeview_select_mode = "NORMAL" self.curr_selection = self.prev_selection return mrk_files = None con_files = None issue = False cont = True # Set the current selection to the appropriate type if '.MRK' in self.context: mrk_files = [self.parent.preloaded_data[sid] for sid in self.curr_selection] elif '.CON' in self.context: con_files = [self.parent.preloaded_data[sid] for sid in self.curr_selection] else: # TODO: improve message/dialog messagebox.showerror("Error", "Invalid file selection") self.curr_selection = self.prev_selection return # Associate selected mrk files with all other con files in folder. if all_: # This can only be called if we have mrk files selected. # Get the parent folder and then find all .con file children. parent = self.parent.file_treeview.parent(mrk_files[0].ID) container = self.parent.preloaded_data[parent] for con in container.contained_files['.con']: con.hpi = mrk_files con.validate() return if self.parent.treeview_select_mode == "NORMAL": # Change the treeview mode and display a message if needed. if '.MRK' in self.context: # Make sure the markers are in the same folder and there aren't # too many. issue, cont = validate_markers(self.parent.file_treeview, mrk_files) if not issue: self.parent.set_treeview_mode("ASSOCIATE-CON") self.cached_selection = mrk_files elif '.CON' in self.context: self.parent.set_treeview_mode("ASSOCIATE-MRK") self.cached_selection = con_files # Finish up if there is no issue. If there is we want to handle it # correctly later. if not issue: if self.parent.settings.get('SHOW_ASSOC_MESSAGE', True): msg = ("Please select the {0} file(s) associated with " "this file.\nOnce you have selected all required " "files, right click and press 'associate' again") # getting the context from the variable is a bit hacky... ctx = list(self.context.get())[0].lower() new_ctx = {'.con': '.mrk', '.mrk': '.con'}.get(ctx) messagebox.showinfo("Select", msg.format(new_ctx)) return # make sure the marker files and con files are in the same location elif self.parent.treeview_select_mode == "ASSOCIATE-MRK": con_files = self.cached_selection issue, cont = validate_markers(self.parent.file_treeview, mrk_files, con_files) elif self.parent.treeview_select_mode == "ASSOCIATE-CON": mrk_files = self.cached_selection issue, cont = validate_markers(self.parent.file_treeview, mrk_files, con_files) # Handle any issues/check for continuation. if issue: if not cont: self.parent.set_treeview_mode("NORMAL") self.curr_selection = self.prev_selection return if self.parent.treeview_select_mode.startswith('ASSOCIATE'): # Now associate the mrk files with the con files: for cf in con_files: cf.hpi = mrk_files cf.validate() # Check if the con file is the currently selected file. if self.parent.treeview_select_mode == "ASSOCIATE-CON": # if so, redraw the info panel and call the mrk # association function so GUI is updated self.parent.info_notebook.determine_tabs() self.parent.highlight_associated_mrks(None) self.parent.set_treeview_mode("NORMAL") self.cached_selection = None def _create_folder(self): """ Create a folder at the currently open level. Clicking on a folder and selecting "create folder" will create a sibling folder, not child folder (not sure which to do?) """ # get the current root depth if self.context != set(): # maybe?? dir_ = path.dirname( self.parent.file_treeview.get_filepath(self.curr_selection[0])) else: dir_ = self.parent.settings['DATA_PATH'] # ask the user for the folder name: folder_name = simpledialog.askstring("Folder Name", "Enter a folder Name:", parent=self.parent) # we will need to make sure the folder doesn't already exist at the # selected level if folder_name is not None: # create the folder full_path = path.join(dir_, folder_name) _, exists_already = create_folder(full_path) if not exists_already: try: parent = self.parent.file_treeview.parent( self.parent.selected_files[0]) except IndexError: # we have clicked outside the tree. Set the parent as the # root parent = '' self.parent.file_treeview.ordered_insert( parent, values=['', str(full_path)], text=folder_name, open=False) print('folder created!!') else: print('Folder already exists!') def _ignore_cons(self): """ Set all selected con files to have 'Is Junk' as True """ for sid in self.curr_selection: con = self.parent.preloaded_data.get(sid, None) if con is not None: if isinstance(con, con_file): con.is_junk.set(True) con.validate() def _include_cons(self): """ Set all selected con files to have 'Is Junk' as False """ for sid in self.curr_selection: con = self.parent.preloaded_data.get(sid, None) if con is not None: if isinstance(con, con_file): con.is_junk.set(False) con.validate() def _send_to(self): """Send the selected object to another selected location.""" src_obj = self.parent.preloaded_data[self.curr_selection[0]] dst = filedialog.askdirectory(title="Select BIDS folder") if dst != '': if isinstance(src_obj, (BIDSTree, Project, Subject, Session)): SendFilesWindow(self.parent, src_obj, dst, opt_verify=True) else: # try and convert the object to a BIDSTree try: self._toggle_bids_folder() src_obj = self.parent.preloaded_data[ self.curr_selection[0]] except TypeError: return def _toggle_bids_folder(self): """Assign the selected folder as a BIDS-formatted folder. This will attempt to load the selected folder into a BIDSController.BIDSTree object. If this isn't possible an error will be raised stating this. """ sid = self.curr_selection[0] fpath = self.parent.file_treeview.get_filepath(sid) bids_folder = assign_bids_folder(fpath, self.parent.file_treeview, self.parent.preloaded_data) if bids_folder is not None: # Only needed if the current selection is the same thing that has # been right-clicked. self.parent.info_notebook.data = [bids_folder] else: messagebox.showerror( "Error", "Invalid folder selected. Please select a folder which " "contains the BIDS project folders.") raise TypeError def _upload(self): """Upload the selected object to the MEG_RAW archive.""" src_obj = self.parent.preloaded_data[self.curr_selection[0]] dst = self.parent.settings.get("ARCHIVE_PATH", None) if dst is None: messagebox.showerror("No path set!", "No Archive path has been set. Please set " "one in the settings.") return if not isinstance(src_obj, BIDSTree): # automatically convert to a BIDSTree object try: self._toggle_bids_folder() src_obj = self.parent.preloaded_data[self.curr_selection[0]] except TypeError: return access = authorise(dst) if not access: messagebox.showerror("Error", "Invalid username or password. Please try " "again.") return SendFilesWindow(self.parent, src_obj, dst, set_copied=True)
class App(Frame): """ Класс главного виджета(Frame), который включает все остальные виджеты """ def __init__(self, root): Frame.__init__(self, root) self.root = root self.menu = None self.account_menu = None self.connect_menu = None self.help_menu = None self.connect = None self.session = None self.connect_window = None self.sign_up_window = None self.sign_in_window = None self.update_password_window = None self.init_window() def init_window(self): """ метод инициализации окна """ self.root.title(config.TITLE) self.root.geometry( '%dx%d+%d+%d' % (config.ROOT_WIDTH, config.ROOT_HEIGHT, round(self.root.winfo_screenwidth() / 2 - config.ROOT_WIDTH / 2, 1), round(self.root.winfo_screenheight() / 2 - config.ROOT_HEIGHT / 2, 1))) self.root.resizable(0, 0) self.root.protocol('WM_DELETE_WINDOW', self.close_root_process) self.init_menu() def init_menu(self): """ метод инициализации меню """ self.menu = Menu(self) self.account_menu = Menu(self.menu, tearoff=0) self.account_menu.add_command(label='Sign up', command=self.sign_up_open_window) self.account_menu.add_command(label='Sign in', command=self.sign_in_open_window) self.connect_menu = Menu(self.menu, tearoff=0) self.connect_menu.add_command(label='Create', command=self.connect_open_window) self.connect_menu.add_command(label='Close', command=self.close_connect) self.help_menu = Menu(self.menu, tearoff=0) self.help_menu.add_command( label='About', command=lambda: messagebox.showinfo(config.TITLE, config.ABOUT)) self.help_menu.add_command(label='How to use it', command=lambda: messagebox.showinfo( config.TITLE, config.HOW_TO_USE)) self.menu.add_cascade(label='Account', menu=self.account_menu) self.menu.add_cascade(label='Connect', menu=self.connect_menu) self.menu.add_cascade(label='Help', menu=self.help_menu) self.init_widgets() self.root['menu'] = self.menu def init_widgets(self): """ метод инициализации виджетов """ self.pack(fill=BOTH, expand=1) self.repositories = Listbox(self, highlightthickness=0, borderwidth=1) self.repositories.place(x=5, y=5, width=310, height=140) Button(self, text='download', font='Calibri 11', relief='groove', command=self.download_repository).place(x=5, y=150, width=100, height=35) Button(self, text='add', font='Calibri 11', relief='groove', command=self.add_repository).place(x=110, y=150, width=100, height=35) Button(self, text='delete', font='Calibri 11', relief='groove', command=self.delete_repository).place(x=215, y=150, width=100, height=35) def connect_open_window(self): """ метод создания виджета для подключения к базе данных """ if self.connect == None and self.connect_window == None: self.connect_window = Connect(Toplevel(), self.connect_process, self.remove_connect_window) elif self.connect != None and self.connect_window == None: v = messagebox.askyesno(title='PyFiles', message='Close current connection?') if v: self.connect.close() self.connect = None self.connect_window = Connect(Toplevel(), self.connect_process, self.remove_connect_window) def remove_connect_window(self): """ метод удаления виджета подключения к базе данных из памяти """ self.connect_window = None def close_connect(self): """ метод разрыва подключения к базе данных """ if self.connect: if self.session: self.logout() self.connect.close() self.connect = None messagebox.showinfo('PyFiles', 'Connection terminated successfully') else: messagebox.showerror('PyFiles', 'No active database connection') def connect_process(self, host, name, user, password): """ метод создания подключения к базе данных """ try: self.connect = psycopg2.connect( "host='%s' dbname='%s' user='******' password='******'" % (host, name, user, password)) return True except: return False def sign_up_open_window(self): """ метод создания виджета регистрации пользователя """ if self.connect: if not self.sign_up_window: self.sign_up_window = SignUp(Toplevel(), self.sign_up, self.remove_sign_up_window) else: messagebox.showerror('PyFiles', 'Need to connect to the database') def remove_sign_up_window(self): """ метод удаления окна регистрации пользователя из памяти """ self.sign_up_window = None def sign_up(self, email, password): """ метод регистрации пользователя """ cursor = self.connect.cursor() cursor.execute( """ SELECT * FROM py_users WHERE email = %s; """, (email, )) if not cursor.rowcount: cursor.execute( """ INSERT INTO py_users ( email, password ) VALUES (%s, %s) """, (email, b64encode(password.encode()).decode('utf-8'))) self.connect.commit() cursor.close() return True else: cursor.close() def sign_in_open_window(self): """ метод создания виджета авторизации пользователя """ if self.connect: if not self.sign_in_window: self.sign_in_window = SignIn(Toplevel(), self.sign_in, self.remove_sign_in_window) else: messagebox.showerror('PyFiles', 'Need to connect to the database') def remove_sign_in_window(self): """ метод удаления виджета авторизации пользователя из памяти """ self.sign_in_window = None def sign_in(self, email, password): """ метод авторизации пользователя """ cursor = self.connect.cursor() cursor.execute( """ SELECT * FROM py_users WHERE email = %s AND password = %s; """, (email, b64encode(password.encode()).decode('utf-8'))) if bool(cursor.rowcount): self.session = cursor.fetchone() self.change_menu() self.get_repositories() cursor.close() return True else: cursor.close() def logout(self): """ метод деавторизации пользователя """ self.session = None self.change_menu() self.repositories.delete(0, 'end') messagebox.showinfo('PyFiles', 'You have successfully logged out') def change_menu(self): """ метод изменения структуры меню """ if self.session: self.account_menu.delete('Sign up') self.account_menu.delete('Sign in') self.account_menu.add_command( label='Update password', command=self.update_password_open_window) self.account_menu.add_command(label='Exit', command=self.logout) else: self.account_menu.delete('Update password') self.account_menu.delete('Exit') self.account_menu.add_command(label='Sign up', command=self.sign_up_open_window) self.account_menu.add_command(label='Sign in', command=self.sign_in_open_window) def get_repositories(self): """ метод получения, отображения репозиториев пользователя """ self.repositories.delete(0, 'end') repositories = [] cursor = self.connect.cursor() cursor.execute( """ SELECT * FROM py_files WHERE user_id = %s; """, (self.session[0], )) files = cursor.fetchall() if files: for file in files: if not (file[1] in repositories): self.repositories.insert('end', file[1]) repositories.append(file[1]) cursor.close() def add_repository(self): """ метод добавления репозитория в список репозиториев пользователя """ if self.session: cursor = self.connect.cursor() place = filedialog.askdirectory() #если dirs, files пустые и текущий раздел(root) отличается от раздела, заданного пользователем(p), то это пустой каталог for root, dirs, files in walk(place): if len(files) > 0: for file in files: r = '' if root == place else root.replace(place, '') f = open(root + '/' + file, 'rb') cursor.execute( """ INSERT INTO py_files ( rep, root, name, data, user_id ) VALUES (%s, %s, %s, %s, %s); """, (place, r, file, f.read(), self.session[0])) f.close() self.connect.commit() elif root != place and not files and not dirs: cursor.execute( """ INSERT INTO py_files ( rep, root, user_id ) VALUES (%s, %s, %s) """, (place, root.replace(place, ''), self.session[0])) cursor.close() self.repositories.insert('end', place) else: messagebox.showerror('PyFiles', 'Need to login') def download_repository(self): """ метод загрузки репозитория пользователя в выбранную директорию """ if self.session: repository = self.repositories.curselection() if repository: cursor = self.connect.cursor() place = filedialog.askdirectory() if place: cursor.execute( """ SELECT * FROM py_files WHERE user_id = %s AND rep = %s; """, (self.session[0], self.repositories.get( repository[0]))) files = cursor.fetchall() for file in files: if not path.exists(place + file[2]): makedirs(place + file[2]) if file[3] != None: f = open(place + file[2] + '/' + file[3], 'w+b') f.write(file[4]) f.close() cursor.close() else: messagebox.showerror('PyFiles', 'Need to select a repository') else: messagebox.showerror('PyFiles', 'Need to login') def delete_repository(self): """ метод удаления репозитория из списка репозиториев пользователя """ if self.session: repository = self.repositories.curselection() if repository: cursor = self.connect.cursor() cursor.execute( """ DELETE FROM py_files WHERE user_id = %s AND rep = %s; """, (self.session[0], self.repositories.get(repository[0]))) self.connect.commit() cursor.close() self.get_repositories() messagebox.showinfo('PyFiles', 'Repository deleted successfully') else: messagebox.showerror('PyFiles', 'Need to select a repository') else: messagebox.showerror('PyFiles', 'Need to login') def update_password_open_window(self): """ метод создания виджета обновления пароля пользователя """ if self.session: if not self.update_password_window: self.update_password_window = PasswordUpdate( Toplevel(), self.update_password, self.remove_update_password_window) else: messagebox.showerror('PyFiles', 'Need to login') def remove_update_password_window(self): """ метод удаления виджета обновления пароля пользователя из памяти """ self.update_password_window = None def update_password(self, password, new_password): """ метод обновления пароля пользователя """ if b64encode(password.encode()).decode('utf-8') == self.session[2] and\ b64encode(new_password.encode()).decode('utf-8') != self.session[2]: cursor = self.connect.cursor() cursor.execute( """ UPDATE py_users SET password = %s WHERE id = %s; """, (b64encode( new_password.encode()).decode('utf-8'), self.session[0])) self.connect.commit() cursor.close() return True def close_root_process(self): """ метод закрытия приложения """ if self.connect: self.connect.close() self.root.destroy()
class Facade(Frame): """This is a Frame that contains group info, group order, apex and prime graph. """ def __init__(self, parent, group, show_graph=True, graph_class=None, **kw): """ Parameters: show_graph: whether to create and show graph instantly or provide a button to do that lately graph_factory: callable accepting one argument - the Group instance and returning Graph instance. Note that __str__ method of the callable is used in the UI. """ Frame.__init__(self, parent, **kw) self._group = group # self._show_apex = True self._show_graph = show_graph self._graph_class = graph_class # self._init_variables() # TODO: fix vertex label automatic positioning self._init_menu() self._init_components() @property def group(self): return self._group @property def apex_list_container(self): return self._apex_container @property def graph_canvas(self): return self._graph_canvas def _show_graph_canvas(self): self._show_graph_button.forget() # TODO: add different layouts and other options graph_class = self._graph_class self.graph = graph_class(self._group) self._graph_canvas = GraphCanvas(self._right_pane, SpringLayout(self.graph), caption=str(graph_class)) self._graph_canvas.pack(expand=True, fill='both') self._graph_canvas.vertex_label_mode = self.getvar( name=self.winfo_name() + ".vertexlabelposition") self._iterations_plugin = IterationsPlugin() self._iterations_plugin.apply(self._graph_canvas) self.update_layout() def _init_components(self): self._panes = PanedWindow(self, orient='horizontal', sashrelief='raised') self._panes.pack(expand=True, fill='both') self._left_pane = Frame(self._panes, padx=2, pady=2) self._right_pane = Frame(self._panes) self._panes.add(self._left_pane, width=250) self._panes.add(self._right_pane) # group name group_name_pane = LabelFrame(self._left_pane, text="Group", padx=10, pady=5) group_name_pane.pack(fill='x') self._group_name = GroupNameLabel(group_name_pane, self._group) self._group_name.pack(expand=True, fill='both') # group order group_order_pane = LabelFrame(self._left_pane, text="Order", padx=10, pady=5) group_order_pane.pack(fill='x') self._group_order = IntegerContainer(group_order_pane, integer=self._group.order()) self._group_order.pack(expand=True, fill='both') # apex self._apex_pane = LabelFrame(self._left_pane, text="Apex", padx=10, pady=5) self._apex_pane.pack(expand=True, fill='both') self._apex_container = ApexListContainer(self._apex_pane, apex=self._group.apex()) self._apex_container.pack(expand=True, fill='both') # graph controls cocliques_frame = LabelFrame(self._left_pane, text="Cocliques", padx=10, pady=5) cocliques_frame.pack(fill='x') self._cocliques_button = Button(cocliques_frame, text="Calculate", command=self._show_cocliques) self._cocliques_button.pack(anchor='nw') self._cocliques_container = ListContainer(cocliques_frame) self._cocliques_list = Listbox(self._cocliques_container) self._cocliques_container.set_listbox(self._cocliques_list) # Button(graph_controls, text='Group equivalent vertices').pack(anchor='nw') # this is a button that show up instead of graph canvas if we uncheck 'Show graph' checkbox. self._show_graph_button = Button(self._right_pane, text='Show graph', command=self._show_graph_canvas) self._graph_canvas = None if self._show_graph: self._show_graph_canvas() else: self._show_graph_button.pack() def _init_variables(self): def set_default_var(name): """Sets widget-specific var with same value as root. """ default_var = self.getvar(name) local_var_name = self.winfo_name() + "." + name self.setvar(local_var_name, default_var) return local_var_name local_name = set_default_var("vertexlabelposition") tools.trace_variable(self, local_name, "w", self._change_vertex_label_position) def _change_vertex_label_position(self, name, *arg): # override default value self.setvar("vertexlabelposition", self.getvar(name)) if self._graph_canvas is not None: self._graph_canvas.vertex_label_mode = self.getvar(name) def _init_menu(self): """Init menu bar content. """ toplevel = self.winfo_toplevel() if toplevel['menu']: self._menu = self.nametowidget(name=toplevel['menu']) else: self._menu = Menu(toplevel) toplevel['menu'] = self._menu graph_options = Menu(self._menu, tearoff=0) self._menu.add_cascade(label="Graph", menu=graph_options) self._menu_index = self._menu.index("end") vertex_label_position_menu = Menu(graph_options, tearoff=0) graph_options.add_cascade(label="Label position", menu=vertex_label_position_menu) menu_var = self.winfo_name() + ".vertexlabelposition" vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Auto", value="auto") vertex_label_position_menu.add_radiobutton(variable=menu_var, label="Center", value="center") graph_options.add_command(label="Save graph...", command=self.call_graph_save_dialog) self.bind("<Destroy>", self.__destroy_menu) #noinspection PyUnusedLocal def __destroy_menu(self, event): try: self._menu.delete(self._menu_index) except TclError: pass def _show_cocliques(self): cocliques = self.graph.max_cocliques() def select_coclique(*_): index = next(iter(self._cocliques_list.curselection()), None) if index is not None: selected = cocliques[int(index)] pick_state = self._graph_canvas.picked_vertex_state pick_state.clear() for value in selected: pick_state.pick(self._graph_canvas.get_vertex(value)) self._cocliques_list.insert(0, *[', '.join(map(str, coclique)) for coclique in cocliques]) self._cocliques_list.bind("<Double-Button-1>", select_coclique) self._cocliques_button.forget() self._cocliques_container.pack(expand=True, fill='both') def call_graph_save_dialog(self): file_name = filedialog.asksaveasfilename(defaultextension='.ps', filetypes=[('PostScript', '.ps')], parent=self.winfo_toplevel(), title="Save graph as image") if file_name: with codecs.open(file_name, 'w', encoding='utf-8') as f: f.write(self._graph_canvas.postscript()) def update_layout(self): try: self._iterations_plugin.iterate(50) except AttributeError: pass
class Sticky(Toplevel): """ Sticky note class """ def __init__(self, master, key, **kwargs): """ Create a new sticky note. master: main app key: key identifying this note in master.note_data kwargs: dictionnary of the other arguments (title, txt, category, color, tags, geometry, locked, checkboxes, images, rolled) """ Toplevel.__init__(self, master) # --- window properties self.id = key self.is_locked = not (kwargs.get("locked", False)) self.images = [] self.links = {} self.latex = {} self.nb_links = 0 self.title('mynotes%s' % key) self.attributes("-type", "splash") self.attributes("-alpha", CONFIG.getint("General", "opacity") / 100) self.focus_force() # window geometry self.update_idletasks() self.geometry(kwargs.get("geometry", '220x235')) self.save_geometry = kwargs.get("geometry", '220x235') self.update() self.rowconfigure(1, weight=1) self.minsize(10, 10) self.protocol("WM_DELETE_WINDOW", self.hide) # --- style self.style = Style(self) self.style.configure(self.id + ".TCheckbutton", selectbackground="red") self.style.map('TEntry', selectbackground=[('!focus', '#c3c3c3')]) selectbg = self.style.lookup('TEntry', 'selectbackground', ('focus', )) self.style.configure("sel.TCheckbutton", background=selectbg) self.style.map("sel.TCheckbutton", background=[("active", selectbg)]) # --- note elements # title font_title = "%s %s" % (CONFIG.get("Font", "title_family").replace( " ", "\ "), CONFIG.get("Font", "title_size")) style = CONFIG.get("Font", "title_style").split(",") if style: font_title += " " font_title += " ".join(style) self.title_var = StringVar(master=self, value=kwargs.get("title", _("Title"))) self.title_label = Label(self, textvariable=self.title_var, anchor="center", style=self.id + ".TLabel", font=font_title) self.title_entry = Entry(self, textvariable=self.title_var, exportselection=False, justify="center", font=font_title) # buttons/icons self.roll = Label(self, image="img_roll", style=self.id + ".TLabel") self.close = Label(self, image="img_close", style=self.id + ".TLabel") self.im_lock = PhotoImage(master=self, file=IM_LOCK) self.cadenas = Label(self, style=self.id + ".TLabel") # corner grip self.corner = Sizegrip(self, style=self.id + ".TSizegrip") # texte font_text = "%s %s" % (CONFIG.get("Font", "text_family").replace( " ", "\ "), CONFIG.get("Font", "text_size")) self.txt = Text(self, wrap='word', undo=True, selectforeground='white', inactiveselectbackground=selectbg, selectbackground=selectbg, tabs=(10, 'right', 21, 'left'), relief="flat", borderwidth=0, highlightthickness=0, font=font_text) # tags self.txt.tag_configure("bold", font="%s bold" % font_text) self.txt.tag_configure("italic", font="%s italic" % font_text) self.txt.tag_configure("bold-italic", font="%s bold italic" % font_text) self.txt.tag_configure("underline", underline=True, selectforeground="white") self.txt.tag_configure("overstrike", overstrike=True, selectforeground="white") self.txt.tag_configure("center", justify="center") self.txt.tag_configure("left", justify="left") self.txt.tag_configure("right", justify="right") self.txt.tag_configure("link", foreground="blue", underline=True, selectforeground="white") self.txt.tag_configure("list", lmargin1=0, lmargin2=21, tabs=(10, 'right', 21, 'left')) self.txt.tag_configure("todolist", lmargin1=0, lmargin2=21, tabs=(10, 'right', 21, 'left')) margin = 2 * Font(self, font=font_text).measure("m") self.txt.tag_configure("enum", lmargin1=0, lmargin2=margin + 5, tabs=(margin, 'right', margin + 5, 'left')) for coul in TEXT_COLORS.values(): self.txt.tag_configure(coul, foreground=coul, selectforeground="white") self.txt.tag_configure(coul + "-underline", foreground=coul, selectforeground="white", underline=True) self.txt.tag_configure(coul + "-overstrike", foreground=coul, overstrike=True, selectforeground="white") # --- menus # --- * menu on title self.menu = Menu(self, tearoff=False) # note color menu_note_color = Menu(self.menu, tearoff=False) colors = list(COLORS.keys()) colors.sort() for coul in colors: menu_note_color.add_command( label=coul, command=lambda key=coul: self.change_color(key)) # category self.category = StringVar( self, kwargs.get("category", CONFIG.get("General", "default_category"))) self.menu_categories = Menu(self.menu, tearoff=False) categories = CONFIG.options("Categories") categories.sort() for cat in categories: self.menu_categories.add_radiobutton(label=cat.capitalize(), value=cat, variable=self.category, command=self.change_category) # position: normal, always above, always below self.position = StringVar( self, kwargs.get("position", CONFIG.get("General", "position"))) menu_position = Menu(self.menu, tearoff=False) menu_position.add_radiobutton(label=_("Always above"), value="above", variable=self.position, command=self.set_position_above) menu_position.add_radiobutton(label=_("Always below"), value="below", variable=self.position, command=self.set_position_below) menu_position.add_radiobutton(label=_("Normal"), value="normal", variable=self.position, command=self.set_position_normal) # mode: note, list, todo list menu_mode = Menu(self.menu, tearoff=False) self.mode = StringVar(self, kwargs.get("mode", "note")) menu_mode.add_radiobutton(label=_("Note"), value="note", variable=self.mode, command=self.set_mode_note) menu_mode.add_radiobutton(label=_("List"), value="list", variable=self.mode, command=self.set_mode_list) menu_mode.add_radiobutton(label=_("ToDo List"), value="todolist", variable=self.mode, command=self.set_mode_todolist) menu_mode.add_radiobutton(label=_("Enumeration"), value="enum", variable=self.mode, command=self.set_mode_enum) self.menu.add_command(label=_("Delete"), command=self.delete) self.menu.add_cascade(label=_("Category"), menu=self.menu_categories) self.menu.add_cascade(label=_("Color"), menu=menu_note_color) self.menu.add_command(label=_("Lock"), command=self.lock) self.menu.add_cascade(label=_("Position"), menu=menu_position) self.menu.add_cascade(label=_("Mode"), menu=menu_mode) # --- * menu on main text self.menu_txt = Menu(self.txt, tearoff=False) # style menu_style = Menu(self.menu_txt, tearoff=False) menu_style.add_command(label=_("Bold"), command=lambda: self.toggle_text_style("bold")) menu_style.add_command( label=_("Italic"), command=lambda: self.toggle_text_style("italic")) menu_style.add_command(label=_("Underline"), command=self.toggle_underline) menu_style.add_command(label=_("Overstrike"), command=self.toggle_overstrike) # text alignment menu_align = Menu(self.menu_txt, tearoff=False) menu_align.add_command(label=_("Left"), command=lambda: self.set_align("left")) menu_align.add_command(label=_("Right"), command=lambda: self.set_align("right")) menu_align.add_command(label=_("Center"), command=lambda: self.set_align("center")) # text color menu_colors = Menu(self.menu_txt, tearoff=False) colors = list(TEXT_COLORS.keys()) colors.sort() for coul in colors: menu_colors.add_command(label=coul, command=lambda key=coul: self. change_sel_color(TEXT_COLORS[key])) # insert menu_insert = Menu(self.menu_txt, tearoff=False) menu_insert.add_command(label=_("Symbols"), command=self.add_symbols) menu_insert.add_command(label=_("Checkbox"), command=self.add_checkbox) menu_insert.add_command(label=_("Image"), command=self.add_image) menu_insert.add_command(label=_("Date"), command=self.add_date) menu_insert.add_command(label=_("Link"), command=self.add_link) if LATEX: menu_insert.add_command(label="LaTex", command=self.add_latex) self.menu_txt.add_cascade(label=_("Style"), menu=menu_style) self.menu_txt.add_cascade(label=_("Alignment"), menu=menu_align) self.menu_txt.add_cascade(label=_("Color"), menu=menu_colors) self.menu_txt.add_cascade(label=_("Insert"), menu=menu_insert) # --- restore note content/appearence self.color = kwargs.get("color", CONFIG.get("Categories", self.category.get())) self.txt.insert('1.0', kwargs.get("txt", "")) self.txt.edit_reset() # clear undo stack # restore inserted objects (images and checkboxes) # we need to restore objects with increasing index to avoid placment errors indexes = list(kwargs.get("inserted_objects", {}).keys()) indexes.sort(key=sorting) for index in indexes: kind, val = kwargs["inserted_objects"][index] if kind == "checkbox": ch = Checkbutton(self.txt, takefocus=False, style=self.id + ".TCheckbutton") if val: ch.state(("selected", )) self.txt.window_create(index, window=ch) elif kind == "image": if os.path.exists(val): self.images.append(PhotoImage(master=self.txt, file=val)) self.txt.image_create(index, image=self.images[-1], name=val) # restore tags for tag, indices in kwargs.get("tags", {}).items(): if indices: self.txt.tag_add(tag, *indices) for link in kwargs.get("links", {}).values(): self.nb_links += 1 self.links[self.nb_links] = link self.txt.tag_bind("link#%i" % self.nb_links, "<Button-1>", lambda e, l=link: open_url(l)) for img, latex in kwargs.get("latex", {}).items(): self.latex[img] = latex if LATEX: self.txt.tag_bind(img, '<Double-Button-1>', lambda e, im=img: self.add_latex(im)) mode = self.mode.get() if mode != "note": self.txt.tag_add(mode, "1.0", "end") self.txt.focus_set() self.lock() if kwargs.get("rolled", False): self.rollnote() if self.position.get() == "above": self.set_position_above() elif self.position.get() == "below": self.set_position_below() # --- placement # titlebar if CONFIG.get("General", "buttons_position") == "right": # right = lock icon - title - roll - close self.columnconfigure(1, weight=1) self.roll.grid(row=0, column=2, sticky="e") self.close.grid(row=0, column=3, sticky="e", padx=(0, 2)) self.cadenas.grid(row=0, column=0, sticky="w") self.title_label.grid(row=0, column=1, sticky="ew", pady=(1, 0)) else: # left = close - roll - title - lock icon self.columnconfigure(2, weight=1) self.roll.grid(row=0, column=1, sticky="w") self.close.grid(row=0, column=0, sticky="w", padx=(2, 0)) self.cadenas.grid(row=0, column=3, sticky="e") self.title_label.grid(row=0, column=2, sticky="ew", pady=(1, 0)) # body self.txt.grid(row=1, columnspan=4, column=0, sticky="ewsn", pady=(1, 4), padx=4) self.corner.lift(self.txt) self.corner.place(relx=1.0, rely=1.0, anchor="se") # --- bindings self.bind("<FocusOut>", self.save_note) self.bind('<Configure>', self.bouge) self.bind('<Button-1>', self.change_focus, True) self.close.bind("<Button-1>", self.hide) self.close.bind("<Enter>", self.enter_close) self.close.bind("<Leave>", self.leave_close) self.roll.bind("<Button-1>", self.rollnote) self.roll.bind("<Enter>", self.enter_roll) self.roll.bind("<Leave >", self.leave_roll) self.title_label.bind("<Double-Button-1>", self.edit_title) self.title_label.bind("<ButtonPress-1>", self.start_move) self.title_label.bind("<ButtonRelease-1>", self.stop_move) self.title_label.bind("<B1-Motion>", self.move) self.title_label.bind('<Button-3>', self.show_menu) self.title_entry.bind("<Return>", lambda e: self.title_entry.place_forget()) self.title_entry.bind("<FocusOut>", lambda e: self.title_entry.place_forget()) self.title_entry.bind("<Escape>", lambda e: self.title_entry.place_forget()) self.txt.tag_bind("link", "<Enter>", lambda event: self.txt.configure(cursor="hand1")) self.txt.tag_bind("link", "<Leave>", lambda event: self.txt.configure(cursor="")) self.txt.bind("<FocusOut>", self.save_note) self.txt.bind('<Button-3>', self.show_menu_txt) # add binding to the existing class binding so that the selected text # is erased on pasting self.txt.bind("<Control-v>", self.paste) self.corner.bind('<ButtonRelease-1>', self.resize) # --- keyboard shortcuts self.txt.bind('<Control-b>', lambda e: self.toggle_text_style('bold')) self.txt.bind('<Control-i>', lambda e: self.toggle_text_style('italic')) self.txt.bind('<Control-u>', lambda e: self.toggle_underline()) self.txt.bind('<Control-r>', lambda e: self.set_align('right')) self.txt.bind('<Control-l>', lambda e: self.set_align('left')) def __setattr__(self, name, value): object.__setattr__(self, name, value) if name == "color": self.style.configure(self.id + ".TSizegrip", background=self.color) self.style.configure(self.id + ".TLabel", background=self.color) self.style.configure("close" + self.id + ".TLabel", background=self.color) self.style.configure("roll" + self.id + ".TLabel", background=self.color) self.style.map(self.id + ".TLabel", background=[("active", self.color)]) self.style.configure(self.id + ".TCheckbutton", background=self.color) self.style.map(self.id + ".TCheckbutton", background=[("active", self.color), ("disabled", self.color)]) self.style.map("close" + self.id + ".TLabel", background=[("active", self.color)]) self.style.map("roll" + self.id + ".TLabel", background=[("active", self.color)]) self.configure(bg=self.color) self.txt.configure(bg=self.color) def paste(self, event): """ delete selected text before pasting """ if self.txt.tag_ranges("sel"): self.txt.delete("sel.first", "sel.last") def delete(self, confirmation=True): """ Delete this note """ if confirmation: rep = askokcancel(_("Confirmation"), _("Delete the note?")) else: rep = True if rep: del (self.master.note_data[self.id]) del (self.master.notes[self.id]) self.master.save() self.destroy() def lock(self): """ Put note in read-only mode to avoid unwanted text insertion """ if self.is_locked: selectbg = self.style.lookup('TEntry', 'selectbackground', ('focus', )) self.txt.configure(state="normal", selectforeground='white', selectbackground=selectbg, inactiveselectbackground=selectbg) self.style.configure("sel.TCheckbutton", background=selectbg) self.style.map("sel.TCheckbutton", background=[("active", selectbg)]) self.is_locked = False for checkbox in self.txt.window_names(): ch = self.txt.children[checkbox.split(".")[-1]] ch.configure(state="normal") self.cadenas.configure(image="") self.menu.entryconfigure(3, label=_("Lock")) self.title_label.bind("<Double-Button-1>", self.edit_title) self.txt.bind('<Button-3>', self.show_menu_txt) else: self.txt.configure(state="disabled", selectforeground='black', inactiveselectbackground='#c3c3c3', selectbackground='#c3c3c3') self.style.configure("sel.TCheckbutton", background='#c3c3c3') self.style.map("sel.TCheckbutton", background=[("active", '#c3c3c3')]) self.cadenas.configure(image=self.im_lock) for checkbox in self.txt.window_names(): ch = self.txt.children[checkbox.split(".")[-1]] ch.configure(state="disabled") self.is_locked = True self.menu.entryconfigure(3, label=_("Unlock")) self.title_label.unbind("<Double-Button-1>") self.txt.unbind('<Button-3>') self.save_note() def save_info(self): """ Return the dictionnary containing all the note data """ data = {} data["txt"] = self.txt.get("1.0", "end")[:-1] data["tags"] = {} for tag in self.txt.tag_names(): if tag not in ["sel", "todolist", "list", "enum"]: data["tags"][tag] = [ index.string for index in self.txt.tag_ranges(tag) ] data["title"] = self.title_var.get() data["geometry"] = self.save_geometry data["category"] = self.category.get() data["color"] = self.color data["locked"] = self.is_locked data["mode"] = self.mode.get() data["inserted_objects"] = {} data["rolled"] = not self.txt.winfo_ismapped() data["position"] = self.position.get() data["links"] = {} for i, link in self.links.items(): if self.txt.tag_ranges("link#%i" % i): data["links"][i] = link data["latex"] = {} for img, latex in self.latex.items(): if self.txt.tag_ranges(img): data["latex"][img] = latex for image in self.txt.image_names(): data["inserted_objects"][self.txt.index(image)] = ( "image", image.split('#')[0]) for checkbox in self.txt.window_names(): ch = self.txt.children[checkbox.split(".")[-1]] data["inserted_objects"][self.txt.index(checkbox)] = ( "checkbox", "selected" in ch.state()) return data def change_color(self, key): self.color = COLORS[key] self.save_note() def change_category(self, category=None): if category: self.category.set(category) self.color = CONFIG.get("Categories", self.category.get()) self.save_note() def set_position_above(self): e = ewmh.EWMH() for w in e.getClientList(): if w.get_wm_name() == 'mynotes%s' % self.id: e.setWmState(w, 1, '_NET_WM_STATE_ABOVE') e.setWmState(w, 0, '_NET_WM_STATE_BELOW') e.display.flush() self.save_note() def set_position_below(self): e = ewmh.EWMH() for w in e.getClientList(): if w.get_wm_name() == 'mynotes%s' % self.id: e.setWmState(w, 0, '_NET_WM_STATE_ABOVE') e.setWmState(w, 1, '_NET_WM_STATE_BELOW') e.display.flush() self.save_note() def set_position_normal(self): e = ewmh.EWMH() for w in e.getClientList(): if w.get_wm_name() == 'mynotes%s' % self.id: e.setWmState(w, 0, '_NET_WM_STATE_BELOW') e.setWmState(w, 0, '_NET_WM_STATE_ABOVE') e.display.flush() self.save_note() def set_mode_note(self): self.txt.tag_remove("list", "1.0", "end") self.txt.tag_remove("todolist", "1.0", "end") self.txt.tag_remove("enum", "1.0", "end") self.save_note() def set_mode_list(self): end = int(self.txt.index("end").split(".")[0]) lines = self.txt.get("1.0", "end").splitlines() for i, l in zip(range(1, end), lines): # remove checkboxes try: ch = self.txt.window_cget("%i.0" % i, "window") self.txt.children[ch.split('.')[-1]].destroy() self.txt.delete("%i.0" % i) except TclError: # there is no checkbox # remove enumeration res = re.match('^\t[0-9]+\.\t', l) if res: self.txt.delete("%i.0" % i, "%i.%i" % (i, res.end())) if self.txt.get("%i.0" % i, "%i.3" % i) != "\t•\t": self.txt.insert("%i.0" % i, "\t•\t") self.txt.tag_add("list", "1.0", "end") self.txt.tag_remove("todolist", "1.0", "end") self.txt.tag_remove("enum", "1.0", "end") self.save_note() def set_mode_enum(self): self.txt.configure(autoseparators=False) self.txt.edit_separator() end = int(self.txt.index("end").split(".")[0]) lines = self.txt.get("1.0", "end").splitlines() for i, l in zip(range(1, end), lines): # remove checkboxes try: ch = self.txt.window_cget("%i.0" % i, "window") self.txt.children[ch.split('.')[-1]].destroy() self.txt.delete("%i.0" % i) except TclError: # there is no checkbox # remove bullets if self.txt.get("%i.0" % i, "%i.3" % i) == "\t•\t": self.txt.delete("%i.0" % i, "%i.3" % i) if not re.match('^\t[0-9]+\.', l): self.txt.insert("%i.0" % i, "\t0.\t") self.txt.tag_add("enum", "1.0", "end") self.txt.tag_remove("todolist", "1.0", "end") self.txt.tag_remove("list", "1.0", "end") self.update_enum() self.txt.configure(autoseparators=True) self.txt.edit_separator() self.save_note() def set_mode_todolist(self): end = int(self.txt.index("end").split(".")[0]) lines = self.txt.get("1.0", "end").splitlines() for i, l in zip(range(1, end), lines): res = re.match('^\t[0-9]+\.\t', l) if res: self.txt.delete("%i.0" % i, "%i.%i" % (i, res.end())) elif self.txt.get("%i.0" % i, "%i.3" % i) == "\t•\t": self.txt.delete("%i.0" % i, "%i.3" % i) try: ch = self.txt.window_cget("%i.0" % i, "window") except TclError: ch = Checkbutton(self.txt, takefocus=False, style=self.id + ".TCheckbutton") self.txt.window_create("%i.0" % i, window=ch) self.txt.tag_remove("enum", "1.0", "end") self.txt.tag_remove("list", "1.0", "end") self.txt.tag_add("todolist", "1.0", "end") self.save_note() # --- bindings def enter_roll(self, event): """ mouse is over the roll icon """ self.roll.configure(image="img_rollactive") def leave_roll(self, event): """ mouse leaves the roll icon """ self.roll.configure(image="img_roll") def enter_close(self, event): """ mouse is over the close icon """ self.close.configure(image="img_closeactive") def leave_close(self, event): """ mouse leaves the close icon """ self.close.configure(image="img_close") def change_focus(self, event): if not self.is_locked: event.widget.focus_force() def show_menu(self, event): self.menu.tk_popup(event.x_root, event.y_root) def show_menu_txt(self, event): self.menu_txt.tk_popup(event.x_root, event.y_root) def resize(self, event): self.save_geometry = self.geometry() def bouge(self, event): geo = self.geometry().split("+")[1:] self.save_geometry = self.save_geometry.split("+")[0] \ + "+%s+%s" % tuple(geo) def edit_title(self, event): self.title_entry.place(x=self.title_label.winfo_x() + 5, y=self.title_label.winfo_y(), anchor="nw", width=self.title_label.winfo_width() - 10) def start_move(self, event): self.x = event.x self.y = event.y self.configure(cursor='fleur') def stop_move(self, event): self.x = None self.y = None self.configure(cursor='') def move(self, event): if self.x is not None and self.y is not None: deltax = event.x - self.x deltay = event.y - self.y x = self.winfo_x() + deltax y = self.winfo_y() + deltay self.geometry("+%s+%s" % (x, y)) def save_note(self, event=None): data = self.save_info() data["visible"] = True self.master.note_data[self.id] = data self.master.save() def rollnote(self, event=None): if self.txt.winfo_ismapped(): self.txt.grid_forget() self.corner.place_forget() self.geometry("%sx22" % self.winfo_width()) else: self.txt.grid(row=1, columnspan=4, column=0, sticky="ewsn", pady=(1, 4), padx=4) self.corner.place(relx=1.0, rely=1.0, anchor="se") self.geometry(self.save_geometry) self.save_note() def hide(self, event=None): """ Hide note (can be displayed again via app menu) """ cat = self.category.get() self.master.add_note_to_menu(self.id, self.title_var.get().strip(), cat) data = self.save_info() data["visible"] = False self.master.note_data[self.id] = data del (self.master.notes[self.id]) self.master.save() self.destroy() # --- Settings update def update_title_font(self): font = "%s %s" % (CONFIG.get("Font", "title_family").replace( " ", "\ "), CONFIG.get("Font", "title_size")) style = CONFIG.get("Font", "title_style").split(",") if style: font += " " font += " ".join(style) self.title_label.configure(font=font) def update_text_font(self): font = "%s %s" % (CONFIG.get("Font", "text_family").replace( " ", "\ "), CONFIG.get("Font", "text_size")) self.txt.configure(font=font) self.txt.tag_configure("bold", font="%s bold" % font) self.txt.tag_configure("italic", font="%s italic" % font) self.txt.tag_configure("bold-italic", font="%s bold italic" % font) margin = 2 * Font(self, font=font).measure("m") self.txt.tag_configure("enum", lmargin1=0, lmargin2=margin + 5, tabs=(margin, 'right', margin + 5, 'left')) def update_menu_cat(self, categories): """ Update the category submenu """ self.menu_categories.delete(0, "end") for cat in categories: self.menu_categories.add_radiobutton(label=cat.capitalize(), value=cat, variable=self.category, command=self.change_category) def update_titlebar(self): if CONFIG.get("General", "buttons_position") == "right": # right = lock icon - title - roll - close self.columnconfigure(1, weight=1) self.columnconfigure(2, weight=0) self.roll.grid_configure(row=0, column=2, sticky="e") self.close.grid_configure(row=0, column=3, sticky="e", padx=(0, 2)) self.cadenas.grid_configure(row=0, column=0, sticky="w") self.title_label.grid_configure(row=0, column=1, sticky="ew", pady=(1, 0)) else: # left = close - roll - title - lock icon self.columnconfigure(2, weight=1) self.columnconfigure(1, weight=0) self.roll.grid_configure(row=0, column=1, sticky="w") self.close.grid_configure(row=0, column=0, sticky="w", padx=(2, 0)) self.cadenas.grid_configure(row=0, column=3, sticky="e") self.title_label.grid_configure(row=0, column=2, sticky="ew", pady=(1, 0)) # --- Text edition def add_link(self): def ok(eveny=None): lien = link.get() txt = text.get() if lien: if not txt: txt = lien self.nb_links += 1 if self.txt.tag_ranges("sel"): index = self.txt.index("sel.first") self.txt.delete('sel.first', 'sel.last') else: index = "current" tags = self.txt.tag_names(index) + ("link", "link#%i" % self.nb_links) self.txt.insert("current", txt, tags) if not lien[:4] == "http": lien = "http://" + lien self.links[self.nb_links] = lien self.txt.tag_bind("link#%i" % self.nb_links, "<Button-1>", lambda e: open_url(lien)) top.destroy() top = Toplevel(self) top.transient(self) top.update_idletasks() top.geometry("+%i+%i" % top.winfo_pointerxy()) top.grab_set() top.resizable(True, False) top.title(_("Link")) top.columnconfigure(1, weight=1) text = Entry(top) link = Entry(top) if self.txt.tag_ranges('sel'): txt = self.txt.get('sel.first', 'sel.last') else: txt = '' text.insert(0, txt) text.icursor("end") Label(top, text=_("Text")).grid(row=0, column=0, sticky="e", padx=4, pady=4) Label(top, text=_("Link")).grid(row=1, column=0, sticky="e", padx=4, pady=4) text.grid(row=0, column=1, sticky="ew", padx=4, pady=4) link.grid(row=1, column=1, sticky="ew", padx=4, pady=4) Button(top, text="Ok", command=ok).grid(row=2, columnspan=2, padx=4, pady=4) text.focus_set() text.bind("<Return>", ok) link.bind("<Return>", ok) def add_checkbox(self): ch = Checkbutton(self.txt, takefocus=False, style=self.id + ".TCheckbutton") self.txt.window_create("current", window=ch) def add_date(self): self.txt.insert("current", strftime("%x")) def add_latex(self, img_name=None): def ok(event): latex = r'%s' % text.get() if latex: if img_name is None: l = [ int(os.path.splitext(f)[0]) for f in os.listdir(PATH_LATEX) ] l.sort() if l: i = l[-1] + 1 else: i = 0 img = "%i.png" % i self.txt.tag_bind(img, '<Double-Button-1>', lambda e: self.add_latex(img)) self.latex[img] = latex else: img = img_name im = os.path.join(PATH_LATEX, img) try: math_to_image(latex, im, fontsize=CONFIG.getint("Font", "text_size") - 2) self.images.append(PhotoImage(file=im, master=self)) if self.txt.tag_ranges("sel"): index = self.txt.index("sel.first") self.txt.delete('sel.first', 'sel.last') else: index = self.txt.index("current") self.txt.image_create(index, image=self.images[-1], name=im) self.txt.tag_add(img, index) top.destroy() except Exception as e: showerror(_("Error"), str(e)) top = Toplevel(self) top.transient(self) top.update_idletasks() top.geometry("+%i+%i" % top.winfo_pointerxy()) top.grab_set() top.resizable(True, False) top.title("LaTex") text = Entry(top, justify='center') if img_name is not None: text.insert(0, self.latex[img_name]) else: if self.txt.tag_ranges('sel'): text.insert(0, self.txt.get('sel.first', 'sel.last')) else: text.insert(0, '$$') text.icursor(1) text.pack(fill='x', expand=True) text.bind('<Return>', ok) text.focus_set() def add_image(self): fichier = askopenfilename(defaultextension=".png", filetypes=[("PNG", "*.png")], initialdir="", initialfile="", title=_('Select PNG image')) if os.path.exists(fichier): self.images.append(PhotoImage(master=self.txt, file=fichier)) self.txt.image_create("current", image=self.images[-1], name=fichier) elif fichier: showerror("Erreur", "L'image %s n'existe pas" % fichier) def add_symbols(self): symbols = pick_symbol( self, CONFIG.get("Font", "text_family").replace(" ", "\ "), CONFIG.get("General", "symbols")) self.txt.insert("current", symbols) def toggle_text_style(self, style): '''Toggle the style of the selected text''' if self.txt.tag_ranges("sel"): current_tags = self.txt.tag_names("sel.first") if style in current_tags: # first char is in style so 'unstyle' the range self.txt.tag_remove(style, "sel.first", "sel.last") elif style == "bold" and "bold-italic" in current_tags: self.txt.tag_remove("bold-italic", "sel.first", "sel.last") self.txt.tag_add("italic", "sel.first", "sel.last") elif style == "italic" and "bold-italic" in current_tags: self.txt.tag_remove("bold-italic", "sel.first", "sel.last") self.txt.tag_add("bold", "sel.first", "sel.last") elif style == "bold" and "italic" in current_tags: self.txt.tag_remove("italic", "sel.first", "sel.last") self.txt.tag_add("bold-italic", "sel.first", "sel.last") elif style == "italic" and "bold" in current_tags: self.txt.tag_remove("bold", "sel.first", "sel.last") self.txt.tag_add("bold-italic", "sel.first", "sel.last") else: # first char is normal, so apply style to the whole selection self.txt.tag_add(style, "sel.first", "sel.last") def toggle_underline(self): if self.txt.tag_ranges("sel"): current_tags = self.txt.tag_names("sel.first") if "underline" in current_tags: # first char is in style so 'unstyle' the range self.txt.tag_remove("underline", "sel.first", "sel.last") for coul in TEXT_COLORS.values(): self.txt.tag_remove(coul + "-underline", "sel.first", "sel.last") else: self.txt.tag_add("underline", "sel.first", "sel.last") for coul in TEXT_COLORS.values(): r = text_ranges(self.txt, coul, "sel.first", "sel.last") if r: for deb, fin in zip(r[::2], r[1::2]): self.txt.tag_add(coul + "-underline", "sel.first", "sel.last") def toggle_overstrike(self): if self.txt.tag_ranges("sel"): current_tags = self.txt.tag_names("sel.first") if "overstrike" in current_tags: # first char is in style so 'unstyle' the range self.txt.tag_remove("overstrike", "sel.first", "sel.last") for coul in TEXT_COLORS.values(): self.txt.tag_remove(coul + "-overstrike", "sel.first", "sel.last") else: self.txt.tag_add("overstrike", "sel.first", "sel.last") for coul in TEXT_COLORS.values(): r = text_ranges(self.txt, coul, "sel.first", "sel.last") if r: for deb, fin in zip(r[::2], r[1::2]): self.txt.tag_add(coul + "-overstrike", "sel.first", "sel.last") def change_sel_color(self, color): """ change the color of the selection """ if self.txt.tag_ranges("sel"): for coul in TEXT_COLORS.values(): self.txt.tag_remove(coul, "sel.first", "sel.last") self.txt.tag_remove(coul + "-overstrike", "sel.first", "sel.last") self.txt.tag_remove(coul + "-underline", "sel.first", "sel.last") if not color == "black": self.txt.tag_add(color, "sel.first", "sel.last") underline = text_ranges(self.txt, "underline", "sel.first", "sel.last") overstrike = text_ranges(self.txt, "overstrike", "sel.first", "sel.last") for deb, fin in zip(underline[::2], underline[1::2]): self.txt.tag_add(color + "-underline", deb, fin) for deb, fin in zip(overstrike[::2], overstrike[1::2]): self.txt.tag_add(color + "-overstrike", deb, fin) def set_align(self, alignment): """ Align the text according to alignment (left, right, center) """ if self.txt.tag_ranges("sel"): line = self.txt.index("sel.first").split(".")[0] line2 = self.txt.index("sel.last").split(".")[0] deb, fin = line + ".0", line2 + ".end" if not "\t" in self.txt.get(deb, fin): # tabulations don't support right/center alignment # remove old alignment tag self.txt.tag_remove("left", deb, fin) self.txt.tag_remove("right", deb, fin) self.txt.tag_remove("center", deb, fin) # set new alignment tag self.txt.tag_add(alignment, deb, fin) def update_enum(self): """ update enumeration numbers """ lines = self.txt.get("1.0", "end").splitlines() indexes = [] for i, l in enumerate(lines): res = re.match('^\t[0-9]+\.\t', l) res2 = re.match('^\t[0-9]+\.', l) if res: indexes.append((i, res.end())) elif res2: indexes.append((i, res2.end())) for j, (i, end) in enumerate(indexes): self.txt.delete("%i.0" % (i + 1), "%i.%i" % (i + 1, end)) self.txt.insert("%i.0" % (i + 1), "\t%i.\t" % (j + 1)) self.txt.tag_add("enum", "1.0", "end")
class PlaylistHandlerSet(Frame): def __init__(self, w: PlaylistControl, *args, **kwargs): self.EMPTY_MENUBTN = _("Select Playlist") #After defined _ by gettext self.playlist = w.playlist self.playlist_control = w super().__init__(w, *args, **kwargs) #___ self.menubtn = Menubutton(self, direction="above", width=13, text=config.general["playlist"]) self.menubtn.pack() self.menu = Menu(self.menubtn, bg=config.colors["BG"], activebackground=config.colors["TV_BG_HOVER"], activeforeground=config.colors["FG"], tearoff=False) self.menu.add_command(label=_("Import Playlist"), command=self._newPlaylist) self.menu.add_separator() for playlist in config.user_config["Playlists"]: #https://stackoverflow.com/questions/11723217/python-lambda-doesnt-remember-argument-in-for-loop func_get_set_playlist = lambda playlist=playlist: self.setPlaylist( playlist) self.menu.add_command(label=playlist, command=func_get_set_playlist) self.menubtn.configure(menu=self.menu) #__________________________________________________ def _newPlaylist(self): folder: str = filedialog.askdirectory( title=_("Select folder for new playlist")) if folder == "": return playlist = os.path.basename(folder) if playlist not in config.user_config["Playlists"]: config.user_config["Playlists"][playlist] = { "path": os.path.normcase(folder), "orderby": ["title", 1], "filter": "" } self.menu.add_command(label=playlist, command=lambda: self.setPlaylist(playlist)) self.setPlaylist(playlist) def setPlaylist(self, playlist: str): ''' There will be no playlist when starting the application if the playlist had been destroyed with "self.delPlaylist()" and closed without selecting one playlist ''' if playlist == "": self.menubtn["text"] = self.EMPTY_MENUBTN return playlist_path = config.user_config["Playlists"][playlist]["path"] if not os.path.exists(playlist_path): messagebox.showerror(_("Load failed"), _("The folder does not to exist")) self.delPlaylist(playlist) return config.general["playlist"] = playlist config.playlist = config.user_config["Playlists"][playlist] self.menubtn["text"] = playlist self.playlist.setPlaylist(playlist_path) self.playlist_control.sortPlaylistForced(config.playlist["orderby"][0], config.playlist["orderby"][1]) self.playlist_control.setSearch(config.playlist["filter"]) def delPlaylist(self, playlist: str, in_tv: bool = True): config.user_config["Playlists"].pop(playlist) self.menu.delete(playlist) if in_tv: config.general["playlist"] = "" self.menubtn["text"] = self.EMPTY_MENUBTN self.playlist.delPlaylist() def renamePlaylist(self, playlist_new: str, playlist_new_path: str): playlist_old = config.general["playlist"] config.general["playlist"] = playlist_new config.user_config["Playlists"][playlist_new] = config.user_config[ "Playlists"].pop(playlist_old) config.playlist = config.user_config["Playlists"][playlist_new] config.playlist["path"] = playlist_new_path self.menu.entryconfig(self.menu.index(playlist_old), label=playlist_new, command=lambda: self.setPlaylist(playlist_new)) self.menubtn["text"] = playlist_new #Change the path of each song in the playlist for song in self.playlist.getAllSongs(): song.path = os.path.join(playlist_new_path, song.name) + song.extension
class mgmainmenu: # init application menubar, the Dump Options menu and the Database Select options + another Info/help/about # the callbacks are in a dictionary like "newfilefn":MGU_newfilefn ... # the statevars are in a dictionary like "dumpfileopt":MGU_dumpfileoptval # caller provides them... menu state changes update them/ call them def __init__(self, topwin, keyvalcallbacks, keyvalstatevars): self.parent = topwin self.menubar = Menu(self.parent) self.keyvalstatevars = keyvalstatevars #???self.view_menu = Menu(self.menubar) # the file menu Command_button = Menubutton(self.menubar, text='Simple Button Commands', underline=0) Command_button.menu = Menu(Command_button) #Command_button.menu = Menu(self.menubar) Command_button.menu.add_command(label="Undo") # undo is the 0th entry... Command_button.menu.entryconfig(0, state=DISABLED) Command_button.menu.add_command(label='New...', underline=0, command=keyvalcallbacks["newfilefn"]) Command_button.menu.add_command(label='Open...', underline=0, command=keyvalcallbacks["openfilefn"]) Command_button.menu.add_command( label='ReScan...', underline=0, command=keyvalcallbacks["rescanmenufn"]) # alternate font example Command_button.menu.add_command( label='Print', underline=0, font='-*-helvetica-*-r-*-*-*-180-*-*-*-*-*-*', command=keyvalcallbacks["printmenufn"]) # separator example Command_button.menu.add('separator') Command_button.menu.add_command( label='Options', underline=0, font='-*-helvetica-*-r-*-*-*-180-*-*-*-*-*-*', command=keyvalcallbacks["optionmenufn"]) # aLternate color example Command_button.menu.add_command(label='Quit', underline=0, background='red', activebackground='green', command=keyvalcallbacks["quitmenufn"]) self.menubar.add_cascade(label='File', menu=Command_button.menu) #self.menubar.add_cascade(label='View', menu=self.view_menu) # add a menubar menu to select option for dumping all elements, or only Dups or only Unique self.dumpselect_menu = Menu(self.menubar) self.dumpradioval = IntVar() self.dumpradioval.set(1) self.dumpselect_menu.add_radiobutton( label="Dump All", value=1, variable=keyvalstatevars['DumpOptions']) self.dumpselect_menu.add_radiobutton( label="Dump only unique", value=2, variable=keyvalstatevars['DumpOptions']) self.dumpselect_menu.add_radiobutton( label="Dump only Dups", value=3, variable=keyvalstatevars['DumpOptions']) self.dumpselect_menu.add('separator') self.dumpselect_menu.add_command( label='Default header log path', underline=0, background='red', activebackground='green', command=keyvalcallbacks["dumppath1menufn"]) self.menubar.add_cascade(label='Dump File options', menu=self.dumpselect_menu) self.dumpdbselect_menu1 = Menu(self.menubar) self.menubar.add_cascade(label='Dump DB selection', menu=self.dumpdbselect_menu1) # add a placeholder for this menu with zero databases added in memory # this DUMP DB menu will be dynamically extended as new databases are scanned. # the parent owner of the class instance must call the add method of this class to extend the menu self.dumpdbselect_menu1.add_command(label="<None>") # <None> is the 0th entry... self.dumpdbselect_menu1.entryconfig(0, state=DISABLED) # set a flag just so we can delete this <None> placeholder during first menu add operation self.newmenu = True self.dumpvolselect_menu1 = Menu(self.menubar) self.menubar.add_cascade(label='Dump Volume selection', menu=self.dumpvolselect_menu1) # add a placeholder for this menu with zero databases added in memory # this DUMP DB menu will be dynamically extended as new databases are scanned. # the parent owner of the class instance must call the add method of this class to extend the menu self.dumpvolselect_menu1.add_command(label="<None>") # <None> is the 0th entry... self.dumpvolselect_menu1.entryconfig(0, state=DISABLED) # set a flag just so we can delete this <None> placeholder during first menu add operation self.newvolmenu = True # menu begins with only one option- dump all #self.dumpdbselect_menu1.add_checkbutton(label="<None>", onvalue=False, offvalue=False, variable=self.var01)#variable=self.show_all) # # add a menubar menu for the app info Help/ about/ etc self.show_all = IntVar() self.show_all.set(True) self.vendorhelp_menu = Menu(self.menubar) self.help001 = {"Help": self.show_all} # menu begins with only one option- dump all self.vendorhelp_menu.add_checkbutton( label="Show _Hints", onvalue=True, offvalue=False, variable=keyvalstatevars['HintsFlag']) self.vendorhelp_menu.add_checkbutton( label="Monitor volumes (10second timer activity)", onvalue=True, offvalue=False, variable=keyvalstatevars['TimerCheck']) self.vendorhelp_menu.add_command(label='Help...', underline=0, command=keyvalcallbacks["helpmenufn"]) # separator example self.vendorhelp_menu.add('separator') self.vendorhelp_menu.add_command(label='Dbg info dump...', underline=0, command=self.MGmenudumpdbg) self.vendorhelp_menu.add_checkbutton( label='Dbg runtime info@HIGH', onvalue=True, offvalue=False, variable=keyvalstatevars['DebuginfohighFlag']) self.vendorhelp_menu.add_command( label='About...', underline=0, command=keyvalcallbacks["aboutmenufn"]) self.menubar.add_cascade(label='Help', menu=self.vendorhelp_menu) # register all this with the callers parent window frame self.parent.config(menu=self.menubar) # add a menuitem to the check options in the database selection menu list # this option allows the databases to be added as they are specified & scanned by user # later used as criteria for which source data to include in the dump of memory database # NOTE that the caller MUST extend the @keyvalstatevars[] list to include the newlabel Key-Value pair for this new item with an associated BooleanVar() # and init the associated variable (I default to False i.e. unchecked) def mgmenuitem_adddb(self, newlabel, addseparator=False): # if this is the first database added, lets remove the <None> menu option! if self.newmenu: self.newmenu = False self.dumpdbselect_menu1.delete(0, END) self.dumpdbselect_menu1.add_checkbutton( label=newlabel, onvalue=True, offvalue=False, variable=self.keyvalstatevars[newlabel]) if addseparator: self.dumpdbselect_menu1.add('separator') def mgmenuitem_addvol(self, newlabel, addseparator=False): # if this is the first database added, lets remove the <None> menu option! if self.newmenu: self.newmenu = False self.dumpdbselect_menu1.delete(0, END) self.dumpvolselect_menu1.add_checkbutton( label=newlabel, onvalue=True, offvalue=False, variable=self.keyvalstatevars[newlabel]) if addseparator: self.dumpvolselect_menu1.add( 'separator' ) # get the menu radio selection for including All/Unique/Duplicate files in the dump #def mgmenuselection_dumpoptions_get(self): # return self.dumpradioval.get() ALL OF THE MENU STATES SHOULD LIE IN PARENT OF THIS INSTANCE! see keyvalstatevars{} # def MGmenugroups_get(self): # return self.mgmenuselection_dumpoptions_get(), self.keyvalstatevars.it # tutorial dump of the state of the menu vars dictionary def MGmenudumpdbg(self): for i, (k, v) in enumerate(self.keyvalstatevars.items()): print(i, k, v.get())
def _delete_menu_options(menu: Menu) -> None: """Deletes all items/commands from the Menu""" start, end = 0, "end" menu.delete(start, end)
class Gui: def __init__(self, root): global CONFIG, CURPATH self.root = root root.geometry( "%dx%d+0+0" % (round(root.winfo_screenwidth() * 0.8), round(root.winfo_screenheight() * 0.8))) #default window size 80% root.title('Chords Autoscroll 0.9b') root.iconphoto( True, PhotoImage(file=os.path.join(CURPATH, "media", "icon.png"))) root.option_add("*Font", "Helvetica 12") #default font root.protocol("WM_DELETE_WINDOW", self.onClose) #general vars if CONFIG.get("recent"): self.file = FileManager(os.path.dirname(CONFIG.get("recent")[0])) else: self.file = FileManager() self.speed = IntVar() self.speed.set(30) self.runningScroll = False self.settingsPattern = re.compile( '\n\nChordsAutoscrollSettings:(\{.*\})') self.settings = {} #menu self.menubar = Menu(self.root) self.filemenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="File", menu=self.filemenu) self.filemenu.add_command(label="Open...", command=lambda: self.openNewFile()) self.filemenu.add_separator() self.filemenu.add_command(label="Save (Ctrl+S)", command=lambda: self.saveFile(True)) self.filemenu.add_command(label="Save as...", command=lambda: self.saveFile()) self.filemenu.add_separator() self.filemenu.add_command(label="Close", command=lambda: self.closeFile()) #recent files (I should update this at runtime...) self.filemenu.add_separator() self.recent = Menu(self.filemenu, tearoff=0) self.filemenu.add_cascade(label="Recent files", menu=self.recent) if CONFIG.get("recent") and len(CONFIG.get("recent")) > 0: for n, p in enumerate(CONFIG.get("recent")): self.recent.add_command( label=str(n + 1) + ": " + str(p), command=lambda p=p: self.openNewFile(str(p))) self.root.config(menu=self.menubar) #root frame froot = Frame(root) froot.pack(side=c.TOP, pady=5, padx=5, fill=c.BOTH, expand=1) #main frame fmain = Frame(froot) fmain.pack(side=c.TOP, fill=c.BOTH, expand=1, anchor=c.N) f1 = Frame(fmain) #text window frame f1.pack(side=c.LEFT, fill=c.BOTH, expand=1) self.txtMain = Text( f1, height=1, width=1, font=("Courier", 14), undo=True) #maybe we can set a DARK MODE to help reading self.txtMain.pack(side=c.LEFT, fill=c.BOTH, expand=1) self.scrollbar = Scrollbar(f1, command=self.txtMain.yview) self.scrollbar.pack(side=c.LEFT, fill=c.Y) self.txtMain.config(yscrollcommand=self.scrollbar.set) f2 = Frame(fmain, width=100) #right buttons panel f2.pack(side=c.RIGHT, anchor=c.N, padx=5, fill=c.X) self.btnPlay = Button(f2, text="Play", relief=c.RAISED, font=(None, 0, "bold")) self.btnPlay.pack(side=c.TOP, padx=5, pady=5, fill=c.BOTH, expand=1, ipady=6) self.btnPlay['command'] = lambda: self.autoscroll() f2_1 = Frame(f2) #child frame SPEED CONTROL f2_1.pack(side=c.TOP, anchor=c.N, pady=(10, 0), fill=c.X) Label(f2_1, text="Speed:", font=("*", 8), anchor=c.E).pack(side=c.LEFT, padx=(2, 0)) Label(f2_1, font=("*", 8), anchor=c.W, textvariable=self.speed).pack(side=c.LEFT, padx=(0, 2)) self.btnSpeedUp = Button(f2, text="+") self.btnSpeedUp.pack(side=c.TOP, padx=5, pady=2, fill=c.BOTH, ipady=6) self.btnSpeedUp['command'] = lambda: self.speedAdd(1) self.btnSpeedDown = Button(f2, text="-") self.btnSpeedDown.pack(side=c.TOP, padx=5, pady=(2, 5), fill=c.BOTH, ipady=6) self.btnSpeedDown['command'] = lambda: self.speedAdd(-1) f2_2 = Frame(f2, width=5) #child frame FONT SIZE #f2_2.pack_propagate(0) f2_2.pack(side=c.TOP, anchor=c.N, pady=(10, 0), fill=c.X) self.btnTextUp = Button(f2, text="A", font=(None, 18)) self.btnTextUp.pack(side=c.TOP, padx=5, pady=2, fill=c.BOTH, ipady=0) self.btnTextUp['command'] = lambda: self.changeFontSize(1) self.btnTextDown = Button(f2, text="A", font=(None, 10)) self.btnTextDown.pack(side=c.TOP, padx=5, pady=(2, 5), fill=c.BOTH, ipady=8) self.btnTextDown['command'] = lambda: self.changeFontSize(-1) #credits f4 = Frame(root) f4.pack(side=c.BOTTOM, pady=0, padx=0, fill=c.X, anchor=c.S) Label( f4, text= "© 2017 Pasquale Lafiosca. Distributed under the terms of the Apache License 2.0.", fg='#111111', bg='#BBBBBB', font=('', 9), bd=0, padx=10).pack(fill=c.X, ipady=2, ipadx=2) #shortcuts root.bind('<Control-s>', lambda e: self.saveFile(True)) root.bind('<Control-S>', lambda e: self.saveFile(True)) def startStop(e): if self.runningScroll: self.stopAutoscroll() else: self.autoscroll() root.bind('<Control-space>', startStop) def openNewFile(self, path=None): global CONFIG filename = None if not path: filename = filedialog.askopenfilename( initialdir=self.file.getLastUsedDir(), filetypes=[("Text files", "*.*")], title="Select a text file to open") else: if os.path.isfile(path): filename = path else: messagebox.showwarning("Not found", "Selected file was not found. Sorry.") if filename: self.closeFile() self.recent.delete(0, len(CONFIG.get("recent")) - 1) CONFIG.insertRecentFile(filename) for n, p in enumerate(CONFIG.get("recent")): self.recent.add_command( label=str(n + 1) + ": " + str(p), command=lambda p=p: self.openNewFile(str(p))) self.file.open(filename) self.txtMain.delete(1.0, c.END) content = self.file.getContent() #Settings m = re.search(self.settingsPattern, content) if m and m.group(1): try: self.settings = json.loads( m.group(1)) # Loads settings from file self.speed.set(self.settings["Speed"]) self._setFontSize(self.settings["Size"]) except: messagebox.showwarning("Warning", "Cannot load setting data. Sorry.") self._setSettingsData() else: self._setSettingsData() content = re.sub( self.settingsPattern, '', content) # Remove settings string before write on screen self.txtMain.insert(1.0, content) def _setSettingsData(self): self.settings = { "Speed": self.speed.get(), "Size": self._getFontSize() } def _settingsChanged(self): if "Speed" in self.settings and "Size" in self.settings and ( self.settings["Speed"] != self.speed.get() or self.settings["Size"] != self._getFontSize()): return True else: return False def saveFile(self, current=False): global CONFIG if current: filename = self.file.getLastFile() if not current or not filename: filename = filedialog.asksaveasfilename( initialdir=self.file.getLastUsedDir(), initialfile=self.file.getLastFile(), filetypes=[("Text files", "*.txt")], title="Select destionation", defaultextension=".txt") if filename: CONFIG.insertRecentFile(filename) self.file.open(filename) self._setSettingsData() self.file.writeContent( self.txtMain.get(1.0, c.END)[:-1] + "\n\nChordsAutoscrollSettings:" + json.dumps(self.settings)) def closeFile(self): if not self.txtMain.get(1.0, c.END)[:-1]: # Empty view return True if self.file.hasChanged( hashlib.md5( (self.txtMain.get(1.0, c.END)[:-1] + "\n\nChordsAutoscrollSettings:" + json.dumps(self.settings) ).encode()).hexdigest()) or self._settingsChanged(): if messagebox.askyesno( "Save changes", "Current document has been modified. Do you want to save changes?" ): self.saveFile() self.txtMain.delete(1.0, c.END) self.file.close() return True def mainloop(self): self.root.mainloop() def onClose(self): if messagebox.askokcancel("Quit", "Do you want to quit?"): self.closeFile() self.root.destroy() def _getFontSize(self): return font.Font(font=self.txtMain["font"])["size"] def _setFontSize(self, newsize): f = font.Font(font=self.txtMain["font"]) f.config(size=newsize) self.txtMain.config(font=f) self.txtMain.update_idletasks() def changeFontSize(self, a): f = font.Font(font=self.txtMain["font"]) newsize = f["size"] + a if (newsize < 8 or newsize > 72): #limits return f.config(size=newsize) self.txtMain.config(font=f) self.txtMain.update_idletasks() def autoscroll(self): if not self.runningScroll and threading.active_count( ) < 2: # Check to avoid multiple scrolling threads if (float(self.scrollbar.get()[1]) == 1 ): #if we are at the end, let's start from beginning self.txtMain.see(1.0) self.runningScroll = True #INITIAL DELAY self.txtMain.mark_set("initialDelay", 1.0) self.txtMain.mark_gravity("initialDelay", c.RIGHT) self.txtMain.insert("initialDelay", os.linesep * 20) # SET CONSTANT HERE self.txtMain.config(state=c.DISABLED) self.txtMain.update_idletasks() threading.Thread(target=self.autoscroll_callback, name="ScrollingThread", daemon=True).start() self.btnPlay.config(text="Stop", relief=c.SUNKEN, command=lambda: self.stopAutoscroll()) self.btnPlay.update_idletasks() def autoscroll_callback(self): while (float(self.scrollbar.get()[1]) < 1 and self.runningScroll): self.txtMain.yview(c.SCROLL, 1, c.UNITS) end = time.time() + 60 / self.speed.get() while (time.time() < end and self.runningScroll): # trick to stop immediately time.sleep(.1) if self.runningScroll: self.stopAutoscroll() def stopAutoscroll(self): self.runningScroll = False self.txtMain.config(state=c.NORMAL) self.txtMain.delete(1.0, "initialDelay") self.txtMain.mark_unset("initialDelay") self.txtMain.update_idletasks() self.btnPlay.config(text="Play", relief=c.RAISED, command=lambda: self.autoscroll()) self.btnPlay.update_idletasks() def speedAdd(self, n): n = self.speed.get() + n if (n > 0 and n < 1000): self.speed.set(n)
class Pomodoro(BaseWidget): """ Chronometre de temps de travail pour plus d'efficacité """ def __init__(self, master): BaseWidget.__init__(self, 'Pomodoro', master) def create_content(self, **kw): self.minsize(190, 190) self.on = False # is the timer on? if not CONFIG.options("Tasks"): CONFIG.set("Tasks", _("Work"), CMAP[0]) self._stats = None # --- colors self.background = { _("Work"): CONFIG.get("Pomodoro", "work_bg"), _("Break"): CONFIG.get("Pomodoro", "break_bg"), _("Rest"): CONFIG.get("Pomodoro", "rest_bg") } self.foreground = { _("Work"): CONFIG.get("Pomodoro", "work_fg"), _("Break"): CONFIG.get("Pomodoro", "break_fg"), _("Rest"): CONFIG.get("Pomodoro", "rest_fg") } self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) # nombre de séquence de travail effectuées d'affilée (pour # faire des pauses plus longues tous les 4 cycles) self.nb_cycles = 0 self.pomodori = IntVar(self, 0) # --- images self.im_go = PhotoImage(master=self, file=IM_START) self.im_stop = PhotoImage(master=self, file=IM_STOP) self.im_tomate = PhotoImage(master=self, file=IM_POMODORO) self.im_graph = PhotoImage(master=self, file=IM_GRAPH) # --- tasks list tasks_frame = Frame(self, style='pomodoro.TFrame') tasks_frame.grid(row=3, column=0, columnspan=3, sticky="wnse") tasks = [t.capitalize() for t in CONFIG.options("PomodoroTasks")] tasks.sort() self.task = StringVar(self, tasks[0]) self.menu_tasks = Menu(tasks_frame, relief='sunken', activeborderwidth=0) self.choose_task = Menubutton(tasks_frame, textvariable=self.task, menu=self.menu_tasks, style='pomodoro.TMenubutton') Label(tasks_frame, text=_("Task: "), style='pomodoro.TLabel', font="TkDefaultFont 12", width=6, anchor="e").pack(side="left", padx=4) self.choose_task.pack(side="right", fill="x", pady=4) # --- display self.tps = [CONFIG.getint("Pomodoro", "work_time"), 0] # time: min, sec self.activite = StringVar(self, _("Work")) self.titre = Label(self, textvariable=self.activite, font='TkDefaultFont 14', style='timer.pomodoro.TLabel', anchor="center") self.titre.grid(row=0, column=0, columnspan=2, sticky="we", pady=(4, 0), padx=4) self.temps = Label(self, text="{0:02}:{1:02}".format(self.tps[0], self.tps[1]), style='timer.pomodoro.TLabel', anchor="center") self.temps.grid(row=1, column=0, columnspan=2, sticky="nswe", padx=4) self.aff_pomodori = Label(self, textvariable=self.pomodori, anchor='e', padding=(20, 4, 20, 4), image=self.im_tomate, compound="left", style='timer.pomodoro.TLabel', font='TkDefaultFont 14') self.aff_pomodori.grid(row=2, columnspan=2, sticky="ew", padx=4) # --- buttons self.b_go = Button(self, image=self.im_go, command=self.go, style='pomodoro.TButton') self.b_go.grid(row=4, column=0, sticky="ew") self.b_stats = Button(self, image=self.im_graph, command=self.display_stats, style='pomodoro.TButton') self.b_stats.grid(row=4, column=1, sticky="ew") self._corner = Sizegrip(self, style="pomodoro.TSizegrip") self._corner.place(relx=1, rely=1, anchor='se') # --- bindings self.bind('<3>', lambda e: self.menu.tk_popup(e.x_root, e.y_root)) tasks_frame.bind('<ButtonPress-1>', self._start_move) tasks_frame.bind('<ButtonRelease-1>', self._stop_move) tasks_frame.bind('<B1-Motion>', self._move) self.titre.bind('<ButtonPress-1>', self._start_move) self.titre.bind('<ButtonRelease-1>', self._stop_move) self.titre.bind('<B1-Motion>', self._move) self.temps.bind('<ButtonPress-1>', self._start_move) self.temps.bind('<ButtonRelease-1>', self._stop_move) self.temps.bind('<B1-Motion>', self._move) self.b_stats.bind('<Enter>', self._on_enter) self.b_stats.bind('<Leave>', self._on_leave) def update_style(self): self.menu_tasks.delete(0, 'end') tasks = [t.capitalize() for t in CONFIG.options('PomodoroTasks')] tasks.sort() for task in tasks: self.menu_tasks.add_radiobutton(label=task, value=task, variable=self.task) if self.task.get() not in tasks: self.stop(False) self.task.set(tasks[0]) self.attributes('-alpha', CONFIG.get(self.name, 'alpha', fallback=0.85)) bg = CONFIG.get('Pomodoro', 'background') fg = CONFIG.get('Pomodoro', 'foreground') active_bg = active_color(*self.winfo_rgb(bg)) self.style.configure('pomodoro.TMenubutton', background=bg, relief='flat', foreground=fg, borderwidth=0, arrowcolor=fg) self.style.configure('pomodoro.TButton', background=bg, relief='flat', foreground=fg, borderwidth=0) self.style.configure('pomodoro.TLabel', background=bg, foreground=fg) self.style.configure('pomodoro.TFrame', background=bg) self.style.configure('pomodoro.TSizegrip', background=bg) self.style.map('pomodoro.TSizegrip', background=[('active', active_bg)]) self.style.map('pomodoro.TButton', background=[('disabled', bg), ('!disabled', 'active', active_bg)]) self.style.map('pomodoro.TMenubutton', background=[('disabled', bg), ('!disabled', 'active', active_bg)]) self.configure(bg=bg) self.menu.configure(bg=bg, fg=fg, selectcolor=fg, activeforeground=fg, activebackground=active_bg) self.menu_pos.configure(bg=bg, fg=fg, selectcolor=fg, activeforeground=fg, activebackground=active_bg) self.menu_tasks.configure(bg=bg, activebackground=active_bg, fg=fg, selectcolor=fg, activeforeground=fg) self.background = { _("Work"): CONFIG.get("Pomodoro", "work_bg"), _("Break"): CONFIG.get("Pomodoro", "break_bg"), _("Rest"): CONFIG.get("Pomodoro", "rest_bg") } self.foreground = { _("Work"): CONFIG.get("Pomodoro", "work_fg"), _("Break"): CONFIG.get("Pomodoro", "break_fg"), _("Rest"): CONFIG.get("Pomodoro", "rest_fg") } act = self.activite.get() self.style.configure('timer.pomodoro.TLabel', font=CONFIG.get("Pomodoro", "font"), foreground=self.foreground[act], background=self.background[act]) def _on_enter(self, event=None): self._corner.state(('active', )) def _on_leave(self, event=None): self._corner.state(('!active', )) def _start_move(self, event): self.x = event.x self.y = event.y def _stop_move(self, event): self.x = None self.y = None self.configure(cursor='arrow') def _move(self, event): if self.x is not None and self.y is not None: self.configure(cursor='fleur') deltax = event.x - self.x deltay = event.y - self.y x = self.winfo_x() + deltax y = self.winfo_y() + deltay self.geometry("+%s+%s" % (x, y)) def hide(self): if self._stats is not None: self._stats.destroy() BaseWidget.hide(self) def stats(self, time=None): """Save stats.""" if time is None: time = CONFIG.getint("Pomodoro", "work_time") today = dt.date.today().toordinal() task = self.task.get().lower().replace(' ', '_') db = sqlite3.connect(PATH_STATS) cursor = db.cursor() try: cursor.execute('SELECT * FROM {} ORDER BY id DESC LIMIT 1'.format( scrub(task))) key, date, work = cursor.fetchone() except sqlite3.OperationalError: cursor.execute('''CREATE TABLE {} (id INTEGER PRIMARY KEY, date INTEGER, work INTEGER)'''.format( scrub(task))) cursor.execute( 'INSERT INTO {}(date, work) VALUES (?, ?)'.format(scrub(task)), (today, time)) else: if today != date: cursor.execute( 'INSERT INTO {}(date, work) VALUES (?, ?)'.format( scrub(task)), (today, time)) else: # update day's value cursor.execute( 'UPDATE {} SET work=? WHERE id=?'.format(scrub(task)), (work + time, key)) finally: db.commit() db.close() def display_stats(self): """ affiche les statistiques """ if self._stats is None: self._stats = Stats(self) self._stats.bind('<Destroy>', self._on_close_stats) else: self._stats.lift() def _on_close_stats(self, event): self._stats = None def go(self): if self.on: self.stop() else: self.on = True self.choose_task.state(["disabled"]) self.b_go.configure(image=self.im_stop) self.after(1000, self.affiche) logging.info('Start work cycle for task ' + self.task.get()) def stop(self, confirmation=True): """ Arrête le décompte du temps et le réinitialise, demande une confirmation avant de le faire si confirmation=True """ tps = int( CONFIG.getint("Pomodoro", "work_time") - self.tps[0] - self.tps[1] / 60) self.on = False rep = True if confirmation: rep = askyesno( title=_("Confirmation"), message=_( "Are you sure you want to give up the current session?")) if rep: self.choose_task.state(["!disabled"]) self.b_go.configure(image=self.im_go) if self.activite.get() == _("Work"): self.stats(tps) self.pomodori.set(0) self.nb_cycles = 0 self.tps = [CONFIG.getint("Pomodoro", "work_time"), 0] self.temps.configure( text="{0:02}:{1:02}".format(self.tps[0], self.tps[1])) act = _("Work") self.activite.set(act) self.style.configure('timer.pomodoro.TLabel', background=self.background[act], foreground=self.foreground[act]) logging.info('Pomodoro session interrupted.') else: self.on = True self.affiche() return rep @staticmethod def ting(): """ joue le son marquant le changement de période """ if not CONFIG.getboolean("Pomodoro", "mute", fallback=False): Popen([ CONFIG.get("General", "soundplayer"), CONFIG.get("Pomodoro", "beep") ]) def affiche(self): if self.on: self.tps[1] -= 1 if self.tps[1] == 0: if self.tps[0] == 0: self.ting() if self.activite.get() == _("Work"): self.pomodori.set(self.pomodori.get() + 1) self.nb_cycles += 1 self.choose_task.state(["!disabled"]) logging.info( 'Pomodoro: completed work session for task ' + self.task.get()) if self.nb_cycles % 4 == 0: # pause longue self.stats() self.activite.set(_("Rest")) self.tps = [ CONFIG.getint("Pomodoro", "rest_time"), 0 ] else: # pause courte self.stats() self.activite.set(_("Break")) self.tps = [ CONFIG.getint("Pomodoro", "break_time"), 0 ] else: self.choose_task.state(["disabled"]) self.activite.set(_("Work")) self.tps = [CONFIG.getint("Pomodoro", "work_time"), 0] act = self.activite.get() self.style.configure('timer.pomodoro.TLabel', background=self.background[act], foreground=self.foreground[act]) elif self.tps[1] == -1: self.tps[0] -= 1 self.tps[1] = 59 self.temps.configure(text="{0:02}:{1:02}".format(*self.tps)) self.after(1000, self.affiche)
class CimApp(Frame): def __init__(self): Frame.__init__(self) self.file = None; self.master.title("Tiborcim") self.master.iconphoto(True, PhotoImage(file=ICON_PNG)) self.files = [] self.current_tab = StringVar() self.pack(expand=1, fill="both") self.master.minsize(300,300) self.master.geometry("500x500") self.menubar = Menu(self.master) self.fileMenu = Menu(self.master, tearoff=0) self.fileMenu.add_command(label="New", command=self.new_file, underline=0, accelerator="Ctrl+N") self.fileMenu.add_command(label="Open...", command=self.load_file, underline=0, accelerator="Ctrl+O") self.fileMenu.add_command(label="Save", command=self.file_save, underline=0, accelerator="Ctrl+S") self.fileMenu.add_command(label="Save As...", command=self.file_save_as, underline=5, accelerator="Ctrl+Alt+S") self.fileMenu.add_command(label="Close", command=self.close_file, underline=0, accelerator="Ctrl+W") self.fileMenu.add_separator() self.fileMenu.add_command(label="Exit", command=self.file_quit, underline=1) self.menubar.add_cascade(label="File", menu=self.fileMenu, underline=0) self.edit_program = Menu(self.master, tearoff=0) self.edit_program.add_command(label="Undo", command=self.edit_undo, underline=0, accelerator="Ctrl+Z") self.edit_program.add_command(label="Redo", command=self.edit_redo, underline=0, accelerator="Ctrl+Y") self.edit_program.add_separator() self.edit_program.add_command(label="Cut", command=self.edit_cut, underline=2, accelerator="Ctrl+X") self.edit_program.add_command(label="Copy", command=self.edit_copy, underline=0, accelerator="Ctrl+C") self.edit_program.add_command(label="Paste", command=self.edit_paste, underline=0, accelerator="Ctrl+V") self.menubar.add_cascade(label="Edit", menu=self.edit_program, underline=0) self.menu_program = Menu(self.master, tearoff=0) self.menu_program.add_command(label="Convert", command=self.convert_file, underline=0, accelerator="Ctrl+T") self.menu_program.add_command(label="Flash", command=self.flash_file, underline=0, accelerator="Ctrl+B") self.menu_program.add_separator() self.menubar.add_cascade(label="Program", menu=self.menu_program, underline=0) self.menu_view = Menu(self.master, tearoff=0) self.viewmode = StringVar() self.viewmode.set("tiborcim") self.menu_view.add_radiobutton(label="Tiborcim", command=self.view_tiborcim, variable=self.viewmode, value="tiborcim", underline=0) self.menu_view.add_radiobutton(label="Python", command=self.view_python, variable=self.viewmode, value="python", underline=0) self.menubar.add_cascade(label="View", menu=self.menu_view, underline=0) self.menu_help = Menu(self.master, tearoff=0) self.menu_samples = Menu(self.master, tearoff=0) samples = tiborcim.resources.samples_list() def add_sample (sample): self.menu_samples.add_command(label=sample, command=lambda: self.help_sample(sample)) for sample in samples: add_sample(sample) self.menu_help.add_cascade(label="Samples", menu=self.menu_samples, underline=0) self.menu_help.add_separator() self.menu_help.add_command(label="README", command=self.help_readme, underline=0) self.menu_help.add_separator() self.menu_help.add_command(label="About", command=self.help_about, underline=0) self.menubar.add_cascade(label="Help", menu=self.menu_help, underline=0) self.master.config(width=450, height=400, menu=self.menubar) self.bind_all("<Control-o>", self.load_file) self.bind_all("<Control-s>", self.file_save) self.bind_all("<Control-Alt-s>", self.file_save_as) self.bind_all("<Control-t>", self.convert_file) self.bind_all("<Control-b>", self.flash_file) self.bind_all("<Control-w>", self.close_file) self.master.protocol("WM_DELETE_WINDOW", self.file_quit) self.file_tabs = Notebook(self) self.file_tabs.bind_all("<<NotebookTabChanged>>", self.file_changed) self.file_tabs.pack(expand=1, fill="both") def file_changed(self, event): if len(self.file_tabs.tabs()) <= 0: self.add_file() return title = str(event.widget.tab(event.widget.index("current"),"text")).upper().strip() self.menu_program.delete(3, END) for tab in self.file_tabs.tabs(): tabtext = self.file_tabs.tab(self.file_tabs.index(tab),"text") if tabtext.upper().strip() == title: self.current_tab.set(tab) self.menu_program.add_radiobutton(label=tabtext, command=self.program_switch, underline=1, value=tab, variable=self.current_tab) if title != "PYTHON" or title != "TIBORCIM": if self.current_file().filename is not None: self.master.title(self.current_file().get_file() + " - Tiborcim") else: self.master.title("Tiborcim") if str(self.current_file().tab(self.current_file().index("current"),"text")).upper().strip() == "TIBORCIM": self.menubar.entryconfig("Edit", state=NORMAL) else: self.menubar.entryconfig("Edit", state=DISABLED) self.viewmode.set(self.current_file().viewmode) if title == "PYTHON": self.menubar.entryconfig("Edit", state=DISABLED) self.current_file().viewmode = "python"; self.viewmode.set("python"); if title == "TIBORCIM": self.menubar.entryconfig("Edit", state=NORMAL) self.current_file().viewmode = "tiborcim"; self.viewmode.set("tiborcim"); def add_file(self, file=None): filepage = CimFilePage(self.file_tabs) if file is None: self.file_tabs.add(filepage, text="Unsaved Program") else: filepage.load_file(file) self.file_tabs.add(filepage, text=filepage.get_file()) self.files.append(filepage) self.file_tabs.select(filepage) def view_tiborcim(self, event=None): self.current_file().view_tiborcim() def view_python(self, event=None): self.current_file().view_python() def program_switch(self): self.file_tabs.select(self.current_tab.get()) def new_file(self, event=None): self.add_file() def load_file(self, event=None): fname = askopenfilename(filetypes=(("Tiborcim", "*.tibas"),("All files", "*.*") ), parent=self.master) if fname: self.add_file(fname) def file_save(self, event=None): self.current_file().save_file() self.file_tabs.tab(self.current_file(), text=self.current_file().get_file()) def file_save_as(self, event=None): self.current_file().save_file_as() self.file_tabs.tab(self.current_file(), text=self.current_file().get_file()) def convert_file(self, event=None): self.current_file().convert_file() def current_file(self, event=None): return self.files[int(self.file_tabs.index(self.file_tabs.select()))] def flash_file(self, event=None): from tiborcim.tibc import compiler as tibc from tiborcim.tibc import flash from tiborcim.tibc import TibcStatus as status com = tibc(self.current_file().text_tiborcim.get("1.0", "end")) res = flash(''.join(com.output)) if res is status.SUCCESS: showinfo(title='Success', message='File Flashed', parent=self.master) else: showerror(title='Failure', message='An Error Occured. Code: %s' % res, parent=self.master) def close_file(self, event=None): logging.debug("Close File") file = self.current_file() if file.close(): self.file_tabs.forget(file) self.files.remove(file) def edit_cut(self, event=None): self.current_file().text_tiborcim.event_generate('<Control-x>') def edit_copy(self, event=None): self.current_file().text_tiborcim.event_generate('<Control-c>') def edit_paste(self, event=None): self.current_file().text_tiborcim.event_generate('<Control-v>') def edit_redo(self, event=None): self.current_file().text_tiborcim.edit_redo() def edit_undo(self, event=None): self.current_file().text_tiborcim.edit_undo() def help_about(self, event=None): CimAbout.show(self) def help_readme(self, event=None): CimReadme.show(self) def help_sample(self, sam): print(sam) filepage = CimFilePage(self.file_tabs) filepage.load_file(tiborcim.resources.sample_path(sam)) filepage.filename = None self.file_tabs.add(filepage, text="Unsaved Program") self.files.append(filepage) self.file_tabs.select(filepage) def file_quit(self, event=None): for ndx, member in enumerate(self.files): logging.debug(self.files[ndx].saved) if not self.files[ndx].close(): return self.quit()
class ShiftReduceApp(object): """ A graphical tool for exploring the shift-reduce parser. The tool displays the parser's stack and the remaining text, and allows the user to control the parser's operation. In particular, the user can shift tokens onto the stack, and can perform reductions on the top elements of the stack. A "step" button simply steps through the parsing process, performing the operations that ``nltk.parse.ShiftReduceParser`` would use. """ def __init__(self, grammar, sent, trace=0): self._sent = sent self._parser = SteppingShiftReduceParser(grammar, trace) # Set up the main window. self._top = Tk() self._top.title('Shift Reduce Parser Application') # Animations. animating_lock is a lock to prevent the demo # from performing new operations while it's animating. self._animating_lock = 0 self._animate = IntVar(self._top) self._animate.set(10) # = medium # The user can hide the grammar. self._show_grammar = IntVar(self._top) self._show_grammar.set(1) # Initialize fonts. self._init_fonts(self._top) # Set up key bindings. self._init_bindings() # Create the basic frames. self._init_menubar(self._top) self._init_buttons(self._top) self._init_feedback(self._top) self._init_grammar(self._top) self._init_canvas(self._top) # A popup menu for reducing. self._reduce_menu = Menu(self._canvas, tearoff=0) # Reset the demo, and set the feedback frame to empty. self.reset() self._lastoper1['text'] = '' ######################################### ## Initialization Helpers ######################################### def _init_fonts(self, root): # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html> self._sysfont = tkinter.font.Font(font=Button()["font"]) root.option_add("*Font", self._sysfont) # TWhat's our font size (default=same as sysfont) self._size = IntVar(root) self._size.set(self._sysfont.cget('size')) self._boldfont = tkinter.font.Font(family='helvetica', weight='bold', size=self._size.get()) self._font = tkinter.font.Font(family='helvetica', size=self._size.get()) def _init_grammar(self, parent): # Grammar view. self._prodframe = listframe = Frame(parent) self._prodframe.pack(fill='both', side='left', padx=2) self._prodlist_label = Label(self._prodframe, font=self._boldfont, text='Available Reductions') self._prodlist_label.pack() self._prodlist = Listbox(self._prodframe, selectmode='single', relief='groove', background='white', foreground='#909090', font=self._font, selectforeground='#004040', selectbackground='#c0f0c0') self._prodlist.pack(side='right', fill='both', expand=1) self._productions = list(self._parser.grammar().productions()) for production in self._productions: self._prodlist.insert('end', (' %s' % production)) self._prodlist.config(height=min(len(self._productions), 25)) # Add a scrollbar if there are more than 25 productions. if 1:#len(self._productions) > 25: listscroll = Scrollbar(self._prodframe, orient='vertical') self._prodlist.config(yscrollcommand = listscroll.set) listscroll.config(command=self._prodlist.yview) listscroll.pack(side='left', fill='y') # If they select a production, apply it. self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select) # When they hover over a production, highlight it. self._hover = -1 self._prodlist.bind('<Motion>', self._highlight_hover) self._prodlist.bind('<Leave>', self._clear_hover) def _init_bindings(self): # Quit self._top.bind('<Control-q>', self.destroy) self._top.bind('<Control-x>', self.destroy) self._top.bind('<Alt-q>', self.destroy) self._top.bind('<Alt-x>', self.destroy) # Ops (step, shift, reduce, undo) self._top.bind('<space>', self.step) self._top.bind('<s>', self.shift) self._top.bind('<Alt-s>', self.shift) self._top.bind('<Control-s>', self.shift) self._top.bind('<r>', self.reduce) self._top.bind('<Alt-r>', self.reduce) self._top.bind('<Control-r>', self.reduce) self._top.bind('<Delete>', self.reset) self._top.bind('<u>', self.undo) self._top.bind('<Alt-u>', self.undo) self._top.bind('<Control-u>', self.undo) self._top.bind('<Control-z>', self.undo) self._top.bind('<BackSpace>', self.undo) # Misc self._top.bind('<Control-p>', self.postscript) self._top.bind('<Control-h>', self.help) self._top.bind('<F1>', self.help) self._top.bind('<Control-g>', self.edit_grammar) self._top.bind('<Control-t>', self.edit_sentence) # Animation speed control self._top.bind('-', lambda e,a=self._animate:a.set(20)) self._top.bind('=', lambda e,a=self._animate:a.set(10)) self._top.bind('+', lambda e,a=self._animate:a.set(4)) def _init_buttons(self, parent): # Set up the frames. self._buttonframe = buttonframe = Frame(parent) buttonframe.pack(fill='none', side='bottom') Button(buttonframe, text='Step', background='#90c0d0', foreground='black', command=self.step,).pack(side='left') Button(buttonframe, text='Shift', underline=0, background='#90f090', foreground='black', command=self.shift).pack(side='left') Button(buttonframe, text='Reduce', underline=0, background='#90f090', foreground='black', command=self.reduce).pack(side='left') Button(buttonframe, text='Undo', underline=0, background='#f0a0a0', foreground='black', command=self.undo).pack(side='left') def _init_menubar(self, parent): menubar = Menu(parent) filemenu = Menu(menubar, tearoff=0) filemenu.add_command(label='Reset Parser', underline=0, command=self.reset, accelerator='Del') filemenu.add_command(label='Print to Postscript', underline=0, command=self.postscript, accelerator='Ctrl-p') filemenu.add_command(label='Exit', underline=1, command=self.destroy, accelerator='Ctrl-x') menubar.add_cascade(label='File', underline=0, menu=filemenu) editmenu = Menu(menubar, tearoff=0) editmenu.add_command(label='Edit Grammar', underline=5, command=self.edit_grammar, accelerator='Ctrl-g') editmenu.add_command(label='Edit Text', underline=5, command=self.edit_sentence, accelerator='Ctrl-t') menubar.add_cascade(label='Edit', underline=0, menu=editmenu) rulemenu = Menu(menubar, tearoff=0) rulemenu.add_command(label='Step', underline=1, command=self.step, accelerator='Space') rulemenu.add_separator() rulemenu.add_command(label='Shift', underline=0, command=self.shift, accelerator='Ctrl-s') rulemenu.add_command(label='Reduce', underline=0, command=self.reduce, accelerator='Ctrl-r') rulemenu.add_separator() rulemenu.add_command(label='Undo', underline=0, command=self.undo, accelerator='Ctrl-u') menubar.add_cascade(label='Apply', underline=0, menu=rulemenu) viewmenu = Menu(menubar, tearoff=0) viewmenu.add_checkbutton(label="Show Grammar", underline=0, variable=self._show_grammar, command=self._toggle_grammar) viewmenu.add_separator() viewmenu.add_radiobutton(label='Tiny', variable=self._size, underline=0, value=10, command=self.resize) viewmenu.add_radiobutton(label='Small', variable=self._size, underline=0, value=12, command=self.resize) viewmenu.add_radiobutton(label='Medium', variable=self._size, underline=0, value=14, command=self.resize) viewmenu.add_radiobutton(label='Large', variable=self._size, underline=0, value=18, command=self.resize) viewmenu.add_radiobutton(label='Huge', variable=self._size, underline=0, value=24, command=self.resize) menubar.add_cascade(label='View', underline=0, menu=viewmenu) animatemenu = Menu(menubar, tearoff=0) animatemenu.add_radiobutton(label="No Animation", underline=0, variable=self._animate, value=0) animatemenu.add_radiobutton(label="Slow Animation", underline=0, variable=self._animate, value=20, accelerator='-') animatemenu.add_radiobutton(label="Normal Animation", underline=0, variable=self._animate, value=10, accelerator='=') animatemenu.add_radiobutton(label="Fast Animation", underline=0, variable=self._animate, value=4, accelerator='+') menubar.add_cascade(label="Animate", underline=1, menu=animatemenu) helpmenu = Menu(menubar, tearoff=0) helpmenu.add_command(label='About', underline=0, command=self.about) helpmenu.add_command(label='Instructions', underline=0, command=self.help, accelerator='F1') menubar.add_cascade(label='Help', underline=0, menu=helpmenu) parent.config(menu=menubar) def _init_feedback(self, parent): self._feedbackframe = feedbackframe = Frame(parent) feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3) self._lastoper_label = Label(feedbackframe, text='Last Operation:', font=self._font) self._lastoper_label.pack(side='left') lastoperframe = Frame(feedbackframe, relief='sunken', border=1) lastoperframe.pack(fill='x', side='right', expand=1, padx=5) self._lastoper1 = Label(lastoperframe, foreground='#007070', background='#f0f0f0', font=self._font) self._lastoper2 = Label(lastoperframe, anchor='w', width=30, foreground='#004040', background='#f0f0f0', font=self._font) self._lastoper1.pack(side='left') self._lastoper2.pack(side='left', fill='x', expand=1) def _init_canvas(self, parent): self._cframe = CanvasFrame(parent, background='white', width=525, closeenough=10, border=2, relief='sunken') self._cframe.pack(expand=1, fill='both', side='top', pady=2) canvas = self._canvas = self._cframe.canvas() self._stackwidgets = [] self._rtextwidgets = [] self._titlebar = canvas.create_rectangle(0,0,0,0, fill='#c0f0f0', outline='black') self._exprline = canvas.create_line(0,0,0,0, dash='.') self._stacktop = canvas.create_line(0,0,0,0, fill='#408080') size = self._size.get()+4 self._stacklabel = TextWidget(canvas, 'Stack', color='#004040', font=self._boldfont) self._rtextlabel = TextWidget(canvas, 'Remaining Text', color='#004040', font=self._boldfont) self._cframe.add_widget(self._stacklabel) self._cframe.add_widget(self._rtextlabel) ######################################### ## Main draw procedure ######################################### def _redraw(self): scrollregion = self._canvas['scrollregion'].split() (cx1, cy1, cx2, cy2) = [int(c) for c in scrollregion] # Delete the old stack & rtext widgets. for stackwidget in self._stackwidgets: self._cframe.destroy_widget(stackwidget) self._stackwidgets = [] for rtextwidget in self._rtextwidgets: self._cframe.destroy_widget(rtextwidget) self._rtextwidgets = [] # Position the titlebar & exprline (x1, y1, x2, y2) = self._stacklabel.bbox() y = y2-y1+10 self._canvas.coords(self._titlebar, -5000, 0, 5000, y-4) self._canvas.coords(self._exprline, 0, y*2-10, 5000, y*2-10) # Position the titlebar labels.. (x1, y1, x2, y2) = self._stacklabel.bbox() self._stacklabel.move(5-x1, 3-y1) (x1, y1, x2, y2) = self._rtextlabel.bbox() self._rtextlabel.move(cx2-x2-5, 3-y1) # Draw the stack. stackx = 5 for tok in self._parser.stack(): if isinstance(tok, Tree): attribs = {'tree_color': '#4080a0', 'tree_width': 2, 'node_font': self._boldfont, 'node_color': '#006060', 'leaf_color': '#006060', 'leaf_font':self._font} widget = tree_to_treesegment(self._canvas, tok, **attribs) widget.label()['color'] = '#000000' else: widget = TextWidget(self._canvas, tok, color='#000000', font=self._font) widget.bind_click(self._popup_reduce) self._stackwidgets.append(widget) self._cframe.add_widget(widget, stackx, y) stackx = widget.bbox()[2] + 10 # Draw the remaining text. rtextwidth = 0 for tok in self._parser.remaining_text(): widget = TextWidget(self._canvas, tok, color='#000000', font=self._font) self._rtextwidgets.append(widget) self._cframe.add_widget(widget, rtextwidth, y) rtextwidth = widget.bbox()[2] + 4 # Allow enough room to shift the next token (for animations) if len(self._rtextwidgets) > 0: stackx += self._rtextwidgets[0].width() # Move the remaining text to the correct location (keep it # right-justified, when possible); and move the remaining text # label, if necessary. stackx = max(stackx, self._stacklabel.width()+25) rlabelwidth = self._rtextlabel.width()+10 if stackx >= cx2-max(rtextwidth, rlabelwidth): cx2 = stackx + max(rtextwidth, rlabelwidth) for rtextwidget in self._rtextwidgets: rtextwidget.move(4+cx2-rtextwidth, 0) self._rtextlabel.move(cx2-self._rtextlabel.bbox()[2]-5, 0) midx = (stackx + cx2-max(rtextwidth, rlabelwidth))/2 self._canvas.coords(self._stacktop, midx, 0, midx, 5000) (x1, y1, x2, y2) = self._stacklabel.bbox() # Set up binding to allow them to shift a token by dragging it. if len(self._rtextwidgets) > 0: def drag_shift(widget, midx=midx, self=self): if widget.bbox()[0] < midx: self.shift() else: self._redraw() self._rtextwidgets[0].bind_drag(drag_shift) self._rtextwidgets[0].bind_click(self.shift) # Draw the stack top. self._highlight_productions() def _draw_stack_top(self, widget): # hack.. midx = widget.bbox()[2]+50 self._canvas.coords(self._stacktop, midx, 0, midx, 5000) def _highlight_productions(self): # Highlight the productions that can be reduced. self._prodlist.selection_clear(0, 'end') for prod in self._parser.reducible_productions(): index = self._productions.index(prod) self._prodlist.selection_set(index) ######################################### ## Button Callbacks ######################################### def destroy(self, *e): if self._top is None: return self._top.destroy() self._top = None def reset(self, *e): self._parser.initialize(self._sent) self._lastoper1['text'] = 'Reset App' self._lastoper2['text'] = '' self._redraw() def step(self, *e): if self.reduce(): return True elif self.shift(): return True else: if list(self._parser.parses()): self._lastoper1['text'] = 'Finished:' self._lastoper2['text'] = 'Success' else: self._lastoper1['text'] = 'Finished:' self._lastoper2['text'] = 'Failure' def shift(self, *e): if self._animating_lock: return if self._parser.shift(): tok = self._parser.stack()[-1] self._lastoper1['text'] = 'Shift:' self._lastoper2['text'] = '%r' % tok if self._animate.get(): self._animate_shift() else: self._redraw() return True return False def reduce(self, *e): if self._animating_lock: return production = self._parser.reduce() if production: self._lastoper1['text'] = 'Reduce:' self._lastoper2['text'] = '%s' % production if self._animate.get(): self._animate_reduce() else: self._redraw() return production def undo(self, *e): if self._animating_lock: return if self._parser.undo(): self._redraw() def postscript(self, *e): self._cframe.print_to_file() def mainloop(self, *args, **kwargs): """ Enter the Tkinter mainloop. This function must be called if this demo is created from a non-interactive program (e.g. from a secript); otherwise, the demo will close as soon as the script completes. """ if in_idle(): return self._top.mainloop(*args, **kwargs) ######################################### ## Menubar callbacks ######################################### def resize(self, size=None): if size is not None: self._size.set(size) size = self._size.get() self._font.configure(size=-(abs(size))) self._boldfont.configure(size=-(abs(size))) self._sysfont.configure(size=-(abs(size))) #self._stacklabel['font'] = ('helvetica', -size-4, 'bold') #self._rtextlabel['font'] = ('helvetica', -size-4, 'bold') #self._lastoper_label['font'] = ('helvetica', -size) #self._lastoper1['font'] = ('helvetica', -size) #self._lastoper2['font'] = ('helvetica', -size) #self._prodlist['font'] = ('helvetica', -size) #self._prodlist_label['font'] = ('helvetica', -size-2, 'bold') self._redraw() def help(self, *e): # The default font's not very legible; try using 'fixed' instead. try: ShowText(self._top, 'Help: Shift-Reduce Parser Application', (__doc__ or '').strip(), width=75, font='fixed') except: ShowText(self._top, 'Help: Shift-Reduce Parser Application', (__doc__ or '').strip(), width=75) def about(self, *e): ABOUT = ("NLTK Shift-Reduce Parser Application\n"+ "Written by Edward Loper") TITLE = 'About: Shift-Reduce Parser Application' try: from tkinter.messagebox import Message Message(message=ABOUT, title=TITLE).show() except: ShowText(self._top, TITLE, ABOUT) def edit_grammar(self, *e): CFGEditor(self._top, self._parser.grammar(), self.set_grammar) def set_grammar(self, grammar): self._parser.set_grammar(grammar) self._productions = list(grammar.productions()) self._prodlist.delete(0, 'end') for production in self._productions: self._prodlist.insert('end', (' %s' % production)) def edit_sentence(self, *e): sentence = " ".join(self._sent) title = 'Edit Text' instr = 'Enter a new sentence to parse.' EntryDialog(self._top, sentence, instr, self.set_sentence, title) def set_sentence(self, sent): self._sent = sent.split() #[XX] use tagged? self.reset() ######################################### ## Reduce Production Selection ######################################### def _toggle_grammar(self, *e): if self._show_grammar.get(): self._prodframe.pack(fill='both', side='left', padx=2, after=self._feedbackframe) self._lastoper1['text'] = 'Show Grammar' else: self._prodframe.pack_forget() self._lastoper1['text'] = 'Hide Grammar' self._lastoper2['text'] = '' def _prodlist_select(self, event): selection = self._prodlist.curselection() if len(selection) != 1: return index = int(selection[0]) production = self._parser.reduce(self._productions[index]) if production: self._lastoper1['text'] = 'Reduce:' self._lastoper2['text'] = '%s' % production if self._animate.get(): self._animate_reduce() else: self._redraw() else: # Reset the production selections. self._prodlist.selection_clear(0, 'end') for prod in self._parser.reducible_productions(): index = self._productions.index(prod) self._prodlist.selection_set(index) def _popup_reduce(self, widget): # Remove old commands. productions = self._parser.reducible_productions() if len(productions) == 0: return self._reduce_menu.delete(0, 'end') for production in productions: self._reduce_menu.add_command(label=str(production), command=self.reduce) self._reduce_menu.post(self._canvas.winfo_pointerx(), self._canvas.winfo_pointery()) ######################################### ## Animations ######################################### def _animate_shift(self): # What widget are we shifting? widget = self._rtextwidgets[0] # Where are we shifting from & to? right = widget.bbox()[0] if len(self._stackwidgets) == 0: left = 5 else: left = self._stackwidgets[-1].bbox()[2]+10 # Start animating. dt = self._animate.get() dx = (left-right)*1.0/dt self._animate_shift_frame(dt, widget, dx) def _animate_shift_frame(self, frame, widget, dx): if frame > 0: self._animating_lock = 1 widget.move(dx, 0) self._top.after(10, self._animate_shift_frame, frame-1, widget, dx) else: # but: stacktop?? # Shift the widget to the stack. del self._rtextwidgets[0] self._stackwidgets.append(widget) self._animating_lock = 0 # Display the available productions. self._draw_stack_top(widget) self._highlight_productions() def _animate_reduce(self): # What widgets are we shifting? numwidgets = len(self._parser.stack()[-1]) # number of children widgets = self._stackwidgets[-numwidgets:] # How far are we moving? if isinstance(widgets[0], TreeSegmentWidget): ydist = 15 + widgets[0].label().height() else: ydist = 15 + widgets[0].height() # Start animating. dt = self._animate.get() dy = ydist*2.0/dt self._animate_reduce_frame(dt/2, widgets, dy) def _animate_reduce_frame(self, frame, widgets, dy): if frame > 0: self._animating_lock = 1 for widget in widgets: widget.move(0, dy) self._top.after(10, self._animate_reduce_frame, frame-1, widgets, dy) else: del self._stackwidgets[-len(widgets):] for widget in widgets: self._cframe.remove_widget(widget) tok = self._parser.stack()[-1] if not isinstance(tok, Tree): raise ValueError() label = TextWidget(self._canvas, str(tok.label()), color='#006060', font=self._boldfont) widget = TreeSegmentWidget(self._canvas, label, widgets, width=2) (x1, y1, x2, y2) = self._stacklabel.bbox() y = y2-y1+10 if not self._stackwidgets: x = 5 else: x = self._stackwidgets[-1].bbox()[2] + 10 self._cframe.add_widget(widget, x, y) self._stackwidgets.append(widget) # Display the available productions. self._draw_stack_top(widget) self._highlight_productions() # # Delete the old widgets.. # del self._stackwidgets[-len(widgets):] # for widget in widgets: # self._cframe.destroy_widget(widget) # # # Make a new one. # tok = self._parser.stack()[-1] # if isinstance(tok, Tree): # attribs = {'tree_color': '#4080a0', 'tree_width': 2, # 'node_font': bold, 'node_color': '#006060', # 'leaf_color': '#006060', 'leaf_font':self._font} # widget = tree_to_treesegment(self._canvas, tok.type(), # **attribs) # widget.node()['color'] = '#000000' # else: # widget = TextWidget(self._canvas, tok.type(), # color='#000000', font=self._font) # widget.bind_click(self._popup_reduce) # (x1, y1, x2, y2) = self._stacklabel.bbox() # y = y2-y1+10 # if not self._stackwidgets: x = 5 # else: x = self._stackwidgets[-1].bbox()[2] + 10 # self._cframe.add_widget(widget, x, y) # self._stackwidgets.append(widget) #self._redraw() self._animating_lock = 0 ######################################### ## Hovering. ######################################### def _highlight_hover(self, event): # What production are we hovering over? index = self._prodlist.nearest(event.y) if self._hover == index: return # Clear any previous hover highlighting. self._clear_hover() # If the production corresponds to an available reduction, # highlight the stack. selection = [int(s) for s in self._prodlist.curselection()] if index in selection: rhslen = len(self._productions[index].rhs()) for stackwidget in self._stackwidgets[-rhslen:]: if isinstance(stackwidget, TreeSegmentWidget): stackwidget.label()['color'] = '#00a000' else: stackwidget['color'] = '#00a000' # Remember what production we're hovering over. self._hover = index def _clear_hover(self, *event): # Clear any previous hover highlighting. if self._hover == -1: return self._hover = -1 for stackwidget in self._stackwidgets: if isinstance(stackwidget, TreeSegmentWidget): stackwidget.label()['color'] = 'black' else: stackwidget['color'] = 'black'
class GUI(Frame): def __init__(self, parent): Frame.__init__(self, parent) self.parent = parent self.initUI() def initUI(self): self.tkbarrier2 = [] self.ndoublet_barrier = 0 self.ecrire_wfn = [] self.wfn = [] self.decompo = [] self.ener = [] self.multiplet = [] self.kramer = '' self.gtensor = [] self.color = [] self.barrier_range = tk.IntVar() self.barrier_range.set(2) self.ndoub_barrier_val = tk.IntVar() self.ndoub_barrier_val.set(0) self.pot_color = tk.IntVar() self.pot_color.set(2) self.ndigit_barrier = tk.IntVar() self.ndigit_barrier.set(0) self.ndoub_barrier = tk.IntVar() self.ndoub_barrier.set(0) self.multip = tk.IntVar() self.multip.set(1) self.sampling = tk.IntVar() self.sampling.set(60) self.surf = tk.IntVar() self.thres_extract = tk.DoubleVar() self.bar_xmin = tk.DoubleVar() self.bar_xmin.set(0.0) self.bar_xmax = tk.DoubleVar() self.bar_xmax.set(0.0) self.bar_ymin = tk.DoubleVar() self.bar_ymin.set(0.0) self.bar_ymax = tk.DoubleVar() self.bar_ymax.set(0.0) self.smin = tk.DoubleVar() self.smax = tk.DoubleVar() self.smin.set(0.0) self.smax.set(0.0) self.smin2 = tk.DoubleVar() self.smax2 = tk.DoubleVar() self.smin2.set(0.0) self.smax2.set(0.0) self.smin3 = tk.DoubleVar() self.smax3 = tk.DoubleVar() self.smin3.set(0.0) self.smax3.set(0.0) self.smin4 = tk.DoubleVar() self.smax4 = tk.DoubleVar() self.smin4.set(0.0) self.smax4.set(0.0) self.thres_extract.set(0.0) self.surf.set(1) self.easy = tk.IntVar() self.easy.set(2) self.gx = tk.IntVar() self.gx.set(0) self.gy = tk.IntVar() self.gy.set(0) self.gz = tk.IntVar() self.gz.set(0) self.type_plot = tk.IntVar() self.type_plot.set(1) self.axis = tk.IntVar() self.axis.set(1) self.coord = tk.IntVar() self.coord.set(4) self.dosspindec = tk.IntVar() self.dosspindec.set(0) self.attype = [] self.v = tk.IntVar() self.v.set(1) self.withaxis = tk.IntVar() self.supera = tk.IntVar() self.superb = tk.IntVar() self.superc = tk.IntVar() self.supera.set(1) self.superb.set(1) self.superc.set(1) self.withaxis.set(1) self.radius = tk.DoubleVar() self.radius.set(0.0) self.parent.title("MOLCAS Toolbox") self.parent.geometry("%dx%d%+d%+d" % (300, 200, 0, 0)) self.pack(fill=BOTH, expand=1) self.typedecomp = tk.IntVar() self.typedecomp.set(1) self.fatdecom = [] self.testdict = {} self.butnodosdec = {} self.buttdecomptype = {} self.fatdecom = dict() self.fatspin = tk.IntVar() self.fatspin.set(1) self.menubar = Menu(self.parent) self.parent.config(menu=self.menubar) fileMenu = Menu(self.menubar) self.computeMenu = Menu(self.menubar) fileMenu.add_command(label="Open", command=self.onOpen) self.menubar.add_cascade(label="File", menu=fileMenu) #self.computeMenu.add_command(label="Show struct", command=self.showstruct) # self.atlist=atome_list(self.path) # print(self.atlist) # for i in range(0,len(self.atlist)): # self.fatdecom.append("") # self.txt = Text(self) # self.txt.pack(fill=BOTH, expand=1) def create_window(self, name, text): self.name = tk.Toplevel() self.name.geometry("%dx%d%+d%+d" % (800, 300, 800, 125)) self.parent.title("Test") self.name.title(text) def create_window2(self, name, text): self.name = tk.Toplevel() self.name.geometry("%dx%d%+d%+d" % (1000, 1000, 800, 125)) self.parent.title("Test") self.name.title(text) def create_window3(self, name, text): self.name = tk.Toplevel() self.name.geometry("%dx%d%+d%+d" % (100, 300, 1000, 525)) self.parent.title("Test") self.name.title(text) ploti() Label(name, text=text).pack(padx=30, pady=30) # def nothing(self): # pass def extract_path(self, filename): for i in range(len(filename) - 1, 0, -1): if filename[i] == '/': path = filename[0:i + 1] break return (path) def readFile(self, filename): f = open(filename, "r") text = f.read() return text def onOpen(self): try: self.menubar.delete("Compute") except: pass self.computeMenu = Menu(self.menubar) ftypes = [('Molcas output', '*out *.log')] currentPath = os.getcwd() dlg = filedialog.Open(self, filetypes=ftypes) fl = dlg.show() self.fileto = fl #print(self.fileto) self.menubar.add_cascade(label="Compute", menu=self.computeMenu) self.kramer = 'y' self.ndoublets = ndoublet_extract(self.fileto) #print("TEST",self.ndoublets) ndoublets2 = int(self.ndoublets / 2) + self.ndoublets % 2 if self.ndoublets % 2 == 1: self.kramer = "n" #print(self.kramer) if self.kramer == "y": self.gtensor = extract_tensor(np.zeros((ndoublets2, 3)), self.fileto) self.ener = energies(self.fileto, ndoublets2) self.wfn = extract_wfn( np.zeros((2 * ndoublets2, 2 * ndoublets2, 2)), self.fileto) self.decompo = extract_decomp(self.wfn, np.zeros((ndoublets2, ndoublets2))) else: self.multiplet = mult(self.ndoublets, self.fileto) self.gtensor = extract_tensor_nonkramer( np.zeros((len(self.multiplet), 3)), self.fileto, self.multiplet, np.zeros((2 * (self.ndoublets - 1) + 1, 3))) #print(self.multiplet) self.ener = energies_nonkramer(self.fileto, 2 * (ndoublets2 - 1) + 1) self.wfn = extract_wfn_nonkramer( np.zeros( (2 * (ndoublets2 - 1) + 1, 2 * (ndoublets2 - 1) + 1, 2)), self.fileto) self.decompo = extract_decomp_nonkramer( self.wfn, np.zeros((2 * (ndoublets2 - 1) + 1, 2 * (ndoublets2 - 1) + 1))) #print(self.decompo) if len(self.decompo) > 0: self.computeMenu.add_command(label="Extract WaveFunction", state="normal", command=self.extract_window) else: self.computeMenu.add_command(label="Extract WaveFunction", state="disabled") f = os.popen( "grep -i -A1 'Temperature depEndence of the magnetic susceptibility calculated in' " + self.fileto + " | wc -l").readlines() test_succept = int(f[0]) if test_succept > 0: self.computeMenu.add_command(label="Succeptibily", state="normal", command=self.plot_succept) else: self.computeMenu.add_command(label="Succeptibily", state="disabled") f = os.popen("grep 'CALCULATION OF THE MOLAR MAGNETIZATION' " + self.fileto + " | wc -l").readlines() test_magnet = int(f[0]) if test_magnet > 0: self.computeMenu.add_command(label="Magnetization", state="normal", command=self.plot_magnet) else: self.computeMenu.add_command(label="Magnetization", state="disabled") if self.kramer == 'y': f = os.popen("grep 'BARRIER' " + self.fileto + " | wc -l").readlines() test_barrier = int(f[0]) if test_barrier > 0: self.computeMenu.add_command(label="Magnetization barrier", state="normal", command=self.plot_barrier_window) else: self.computeMenu.add_command(label="Magnetization barrier", state="disabled") f = os.popen("grep 'ATOMIC DOMAIN' " + self.fileto + " | wc -l").readlines() test_potential = int(f[0]) if test_potential > 0: self.computeMenu.add_command(label="Potential", state="normal", command=self.potential_window) else: self.computeMenu.add_command(label="Potential", state="disabled") def plot_barrier_window(self): try: for i in range(0, len(self.tkbarrier)): self.tkbarrier.destroy() except: pass name = "Window" self.create_window(name, "Transition Barrier") self.ndoublet_barrier = ndoublet_magnetization(self.fileto) self.tkbarrier = [] self.tkbarrier.append( tk.Label(self.name, text=str(self.ndoublet_barrier) + " doublets where fouund")) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=1, column=1, columnspan=2) self.tkbarrier.append( tk.Label(self.name, text="How much doublet do you want to consider")) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=2, column=1) self.tkbarrier.append( tk.Entry(self.name, bd=5, textvariable=self.ndoub_barrier)) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=2, column=2) self.tkbarrier.append( tk.Label( self.name, text="How much doublet do you want to consider for the values") ) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=3, column=1) self.tkbarrier.append( tk.Entry(self.name, bd=5, textvariable=self.ndoub_barrier_val)) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=3, column=2) self.tkbarrier.append( tk.Label(self.name, text="How much digits for the values")) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=4, column=1) self.tkbarrier.append( tk.Entry(self.name, bd=5, textvariable=self.ndigit_barrier)) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=4, column=2) function_test = partial(self.bar_range, 6) self.tkbarrier.append(tk.Label(self.name, text="Specific axis range")) self.tkbarrier[len(self.color) - 1].grid(row=5, column=1) self.tkbarrier.append( tk.Radiobutton(self.name, text='Yes ', variable=self.barrier_range, value=1, command=function_test)) self.tkbarrier[len(self.color) - 1].grid(row=5, column=2) self.tkbarrier.append( tk.Radiobutton(self.name, text='No ', variable=self.barrier_range, value=2, command=function_test)) self.tkbarrier[len(self.color) - 1].grid(row=5, column=3) self.tkbarrier.append( tk.Button(self.name, text="Show barrier", command=self.plot_barrier)) self.tkbarrier[len(self.tkbarrier) - 1].grid(row=8, column=1, columnspan=2) def bar_range(self, value): try: for i in range(0, len(self.tkbarrier2)): self.tkbarrier2[i].destroy() self.tkbarrier2 = [] except: pass if self.barrier_range.get() == 1: self.tkbarrier2.append(tk.Label(self.name, text="xmin")) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value, column=1) self.tkbarrier2.append(tk.Label(self.name, text="xmax")) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value, column=3) self.tkbarrier2.append( tk.Entry(self.name, bd=5, textvariable=self.bar_xmin)) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value, column=2) self.tkbarrier2.append( tk.Entry(self.name, bd=5, textvariable=self.bar_xmax)) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value, column=4) self.tkbarrier2.append(tk.Label(self.name, text="ymin")) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value + 1, column=1) self.tkbarrier2.append(tk.Label(self.name, text="ymax")) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value + 1, column=3) self.tkbarrier2.append( tk.Entry(self.name, bd=5, textvariable=self.bar_ymin)) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value + 1, column=2) self.tkbarrier2.append( tk.Entry(self.name, bd=5, textvariable=self.bar_ymax)) self.tkbarrier2[len(self.tkbarrier2) - 1].grid(row=value + 1, column=4) def plot_barrier(self): barrier(self.fileto, self.ndoublet_barrier, self.ndoub_barrier.get() - 1, self.ndigit_barrier.get(), self.ndoub_barrier_val.get() - 1, self.barrier_range.get(), self.bar_xmin.get(), self.bar_xmax.get(), self.bar_ymin.get(), self.bar_ymax.get()) def plot_magnet(self): magnetization(self.fileto) def plot_succept(self): succpetibility(self.fileto) def des_easy(self, value): if self.easy.get() == 1: try: self.rad1.destroy() self.rad2.destroy() self.rad3.destroy() self.label.destroy() self.label2.destroy() self.entry.destroy() except: pass self.rad1 = tk.Checkbutton(self.name, text='gX ', variable=self.gx) self.rad2 = tk.Checkbutton(self.name, text='gY ', variable=self.gy) self.rad3 = tk.Checkbutton(self.name, text='gZ ', variable=self.gz) self.rad1.grid(row=value + 1, column=2) self.rad2.grid(row=value + 1, column=3) self.rad3.grid(row=value + 1, column=4) self.label2 = tk.Label(self.name, text='Which multiplet') self.label2.grid(row=value + 2, column=1) self.entry = tk.Entry(self.name, bd=5, textvariable=self.multip) self.entry.grid(row=value + 2, column=2) self.label = tk.Label(self.name, text="Which axis") self.label.grid(row=value + 1, column=1) else: try: self.rad1.destroy() self.rad2.destroy() self.rad3.destroy() self.label.destroy() self.label2.destroy() self.entry.destroy() except: pass def potential_window(self): name = "Window" f = os.popen("echo $CAMMEL").readlines() self.create_window2(name, "Electrostatic potential") self.photo = ImageTk.PhotoImage(file=str.split(f[0])[0] + '/source/cammel_color2.png') espace_image = tk.Canvas(self.name, width=340, height=340) espace_image.grid(row=1, column=1, columnspan=23, padx=10, pady=10) espace_image.create_image(170, 170, image=self.photo) tk.Label(self.name, text="Radius around the \n Lantanide atom (bohr)").grid( row=2, column=1) tk.Entry(self.name, bd=5, textvariable=self.radius).grid(row=2, column=2) tk.Label( self.name, text="How much values for \n the sampling (60 is a good compromise)" ).grid(row=3, column=1) tk.Entry(self.name, bd=5, textvariable=self.sampling).grid(row=3, column=2) tk.Label(self.name, text="Which surface \n for the mapping").grid(row=4, column=1) tk.Radiobutton(self.name, text='The potential ', variable=self.surf, value=1).grid(row=4, column=2) tk.Radiobutton(self.name, text='A sphere ', variable=self.surf, value=2).grid(row=4, column=3) # tk.Label(self.name, text="Do you want to fix specific \n extrema for the potential?").grid(row=5,column=1) # tk.Radiobutton(self.name, text='Yes ', variable=self.pot_color, value=1, command=self.represente_color).grid(row=5,column=2) # tk.Radiobutton(self.name, text='No ', variable=self.pot_color, value=2, command=self.represente_color).grid(row=5,column=3) self.color = [] self.row = 6 function_test = partial(self.des_easy, self.row) self.color.append(tk.Label(self.name, text="Show the easy axis?")) self.color[len(self.color) - 1].grid(row=6, column=1) self.color.append( tk.Radiobutton(self.name, text='Yes ', variable=self.easy, value=1, command=function_test)) self.color[len(self.color) - 1].grid(row=6, column=2) self.color.append( tk.Radiobutton(self.name, text='No ', variable=self.easy, value=2, command=function_test)) self.color[len(self.color) - 1].grid(row=6, column=3) self.color.append(tk.Label(self.name, text="Do you want to plot")) self.color[len(self.color) - 1].grid(row=self.row + 3, column=1) self.color.append( tk.Radiobutton(self.name, text='The potential only ', variable=self.type_plot, value=1)) self.color[len(self.color) - 1].grid(row=self.row + 3, column=2) self.color.append( tk.Radiobutton(self.name, text='The potential and its decomposition ', variable=self.type_plot, value=2)) self.color[len(self.color) - 1].grid(row=self.row + 3, column=3) self.color.append( tk.Label(self.name, text="Do you want to \n see the molecule")) self.color[len(self.color) - 1].grid(row=self.row + 4, column=1) self.color.append( tk.Radiobutton(self.name, text='All ', variable=self.coord, value=1)) self.color[len(self.color) - 1].grid(row=self.row + 4, column=2) self.color.append( tk.Radiobutton(self.name, text='First coordination sphere ', variable=self.coord, value=3)) self.color[len(self.color) - 1].grid(row=self.row + 4, column=4) self.color.append( tk.Radiobutton(self.name, text='No ', variable=self.coord, value=4)) self.color[len(self.color) - 1].grid(row=self.row + 4, column=5) self.color.append( tk.Radiobutton(self.name, text='All except H atoms', variable=self.coord, value=2)) self.color[len(self.color) - 1].grid(row=self.row + 4, column=3) self.color.append( tk.Button(self.name, text="Map the potential", command=self.computepotential)) self.color[len(self.color) - 1].grid(row=self.row + 5, column=1, columnspan=23) self.color.append(tk.Label(self.name, anchor="w", text="OpenGL button")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 6, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="l for removing/showing atom legend")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 7, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="z for removing/showing zoom and rotation values")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 8, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="c for removing/showing cartesian axes")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 9, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="Shift+mouse for rotation over z")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 10, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="+/- zooming and unzooming the potential")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 11, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="p/m zooming and unzooming the molecule")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 12, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="P/M zooming and unzooming the easy axis")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 13, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="h/f right/left translation")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 14, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="t/b up/down translation")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 15, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="s for a screenshot")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 16, column=1, columnspan=23) self.color.append( tk.Label(self.name, anchor="w", text="g remove easy axis legend")) self.color[len(self.color) - 1].grid(sticky="W", row=self.row + 17, column=1, columnspan=23) def extract_window(self): name = "Window" self.create_window(name, "WaveFunction Extraction") tk.Label(self.name, text='Threshold for the \n wavefunction coeff').grid( row=1, column=0, columnspan=4) tk.Entry(self.name, bd=5, textvariable=self.thres_extract).grid(row=1, column=4, columnspan=4) tk.Button(self.name, text="Extract", command=self.extract_output).grid(row=1, column=8, columnspan=2) def extract_output(self): #print("test thre",float(self.thres_extract.get())) if self.kramer == 'y': self.ecrire_wfn = ecriture(self.decompo, self.gtensor, self.ener, float(self.thres_extract.get()), self.kramer) else: self.ecrire_wfn = ecriture_nonkramer( self.decompo, self.gtensor, self.ener, float(self.thres_extract.get()), self.kramer, self.multiplet) try: for i in range(0, len(self.tableau_extract)): self.tableau_extract[i].destroy() except: pass ecrire = [] maxlong = np.zeros(5) for i in range(0, len(self.ecrire_wfn)): for j in range(0, len(self.ecrire_wfn[i]) - 1): if len(self.ecrire_wfn[i][j]) > maxlong[j]: maxlong[j] = len(self.ecrire_wfn[i][j]) self.ecrire_wfn[i][j] = self.ecrire_wfn[i][j] for i in range(0, len(self.ecrire_wfn)): ecrire2 = '' for j in range(0, len(self.ecrire_wfn[i]) - 1): for k in range(len(self.ecrire_wfn[i][j]), int(maxlong[j]) + 2): self.ecrire_wfn[i][j] = self.ecrire_wfn[i][j] + " " ecrire2 = ecrire2 + self.ecrire_wfn[i][j] + '|' ecrire2 = ecrire2 + self.ecrire_wfn[i][len(self.ecrire_wfn[i]) - 1] ecrire.append(ecrire2) #print(ecrire2) self.tableau_extract = [] self.tableau_extract.append( tk.Label(self.name, text='Results are printed in the' + self.fileto + '_wfn file', anchor='w', justify='left')) self.tableau_extract[len(self.tableau_extract) - 1].grid(row=2, column=0, columnspan=23, sticky='we') # for i in range(0,len(self.ecrire_wfn)): # for j in range(0,len(self.ecrire_wfn[i])): # self.tableau_extract.append(tk.Label(self.name, text=ecrire[i], anchor='w', justify='left')) # self.tableau_extract[len(self.tableau_extract)-1].grid(row=i+3,column=1,columnspan=3, sticky='we') for i in range(0, len(self.ecrire_wfn)): col = 0 for j in range(0, len(self.ecrire_wfn[i])): self.tableau_extract.append( tk.Label(self.name, text=self.ecrire_wfn[i][j], anchor='w', justify='left')) self.tableau_extract[len(self.tableau_extract) - 1].grid( row=i + 3, column=col, sticky='we') col = col + 1 if j < len(self.ecrire_wfn[i]) - 1: self.tableau_extract.append( tk.Label(self.name, text="|", anchor='w', justify='left')) self.tableau_extract[len(self.tableau_extract) - 1].grid( row=i + 3, column=col, sticky='we') col = col + 1 # pm=chr(177) # maxi=np.zeros(5) #for i in range(0,len(self.ecrire_wfn[0])-1): # maxi[i]=len(ecrire[0][i]) ecriture2 = open(self.fileto + "_wfnfile", 'w') for i in range(0, len(self.ecrire_wfn)): for j in range(0, len(self.ecrire_wfn[i]) - 1): ecriture2.write(self.ecrire_wfn[i][j] + '|') ecriture2.write(self.ecrire_wfn[i][len(self.ecrire_wfn[i]) - 1]) ecriture2.write('\n') # print(self.ecrire_wfn) def computepotential(self): if self.radius.get() != 0.0: if self.surf.get() == 1: surf = 'p' else: surf = 's' if self.easy.get() == 1: easy = 'y' else: easy = 'n' if self.type_plot.get() == 1: type_plot = 'p' else: type_plot = 'a' if self.coord.get() == 1: coord = 'y' elif self.coord.get() == 3: coord = 's' elif self.coord.get() == 2: coord = 'ynh' else: coord = 'n' if self.axis.get() == 1: axis = 'y' else: axis = 'n' # selfa.test() os.system("$CAMMEL/source/prog_pot_opengl.py " + self.fileto + " " + str(self.radius.get()) + " " + str(self.sampling.get()) + " " + surf + " " + easy + " " + type_plot + " " + coord + " " + axis + " " + str(self.gx.get()) + " " + str(self.gy.get()) + " " + str(self.gz.get()) + " " + str(self.multip.get() - 1) + " " + str(self.smin.get()) + " " + str(self.smax.get()) + " " + str(self.smin2.get()) + " " + str(self.smax2.get()) + " " + str(self.smin3.get()) + " " + str(self.smax3.get()) + " " + str(self.smin4.get()) + " " + str(self.smax4.get()))
class Fenetre: def __init__(self, root, job): # Récupération de l'objet Jeu self.jeu = job self.saved = True # Création de la fenêtre principale self.root = root self.root.title('Rêve de Dragon') self.root.resizable(True, True) # Création des menus # On a 4 menu principaux : filemenu, cmdmenu, viewmenu et helpmenu self.menubar = Menu(root) self.root.config(menu=self.menubar) # filemenu: menu de manipulation des fichiers contenant les personnages self.filemenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Fichier", menu=self.filemenu) self.filemenu.add_command(label="Nouveau", command=self.nouveau) self.filemenu.add_command(label="Ouvrir", command=self.ouvrir) self.filemenu.add_separator() self.filemenu.add_command(label="Enregistrer", command=self.jeu.enregistrer) self.filemenu.add_command(label="Enregistrer sous...", command=self.jeu.enregistrer_sous) self.filemenu.add_separator() self.filemenu.add_command(label="Fermer", command=self.fermer) self.filemenu.add_separator() self.filemenu.add_command(label="Imprimer", command=self.void, state='disabled') self.filemenu.add_separator() self.filemenu.add_command(label="Quitter", command=self.quitter) # cmdmenu: menu des commandes sur les personnages self.cmdmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Commande", menu=self.cmdmenu) self.cmdmenu.add_command(label="Nouvelle Partie", command=self.partie) self.cmdmenu.add_separator() self.cmdmenu.add_command(label="Nouveau Personnage", command=self.creer) self.cmdmenu.add_separator() self.cmdmenu.add_command(label="Valider le Personnage", command=self.valider) # viewmenu: menu de sélection du personnage à l'affichage # Ce menu est vide en l'absence de personnage # Il est rempli au chargement ou à la création d'un personnage self.viewmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Personnage", menu=self.viewmenu) # helpmenu: menu d'aide self.helpmenu = Menu(self.menubar, tearoff=0) self.menubar.add_cascade(label="Aide", menu=self.helpmenu) self.helpmenu.add_command(label="Règles du Jeu", command=self.regles) self.helpmenu.add_command(label="Utilisation du Programme", command=self.utilise) self.helpmenu.add_command(label="A Propos...", command=self.a_propos) # frame1 : Fiche du personnage frame1 = Frame(root, borderwidth=0, relief='flat', height=200, width=600) frame1.grid(row=0, column=0, sticky='NW', padx="10", pady="5") # Nom self.Entry_Nom = StringVar() self.Old_Nom = "" Label(frame1, text='Nom:').grid(row=0, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Nom, justify='left', width=34)\ .grid(row=0, column=2, columnspan=4, sticky='W', padx="5") # Age self.Entry_Age = IntVar() Label(frame1, text='Age:').grid(row=1, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Age, justify='right', width=3)\ .grid(row=1, column=2, sticky='W', padx="5") # Heure de naissance (pour hauts-rêvants) self.Entry_Heure = IntVar() Label(frame1, text='Heure de Naissance:').grid(row=1, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Heure, justify='right', width=3) \ .grid(row=1, column=4, sticky='W', padx="5") # Taille self.Entry_Taille = IntVar() Label(frame1, text='Taille:').grid(row=1, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_Taille, justify='right', width=3)\ .grid(row=1, column=6, sticky='W', padx="5") # Poids self.Entry_Poids = IntVar() Label(frame1, text='Poids:').grid(row=1, column=7, sticky='E') Entry(frame1, textvariable=self.Entry_Poids, justify='right', width=3)\ .grid(row=1, column=8, sticky='W', padx="5") # Beauté self.Entry_Beaute = IntVar() Label(frame1, text='Beauté:').grid(row=2, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Beaute, justify='right', width=3) \ .grid(row=2, column=2, sticky='W', padx="5") # Cheveux self.Entry_Cheveux = StringVar() Label(frame1, text='Cheveux:').grid(row=2, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Cheveux, justify='left', width=8)\ .grid(row=2, column=4, sticky='W', padx="5") # Yeux self.Entry_Yeux = StringVar() Label(frame1, text='Yeux:').grid(row=2, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_Yeux, justify='left', width=8)\ .grid(row=2, column=6, sticky='W', padx="5") # Haut rêvant self.Entry_HRevant = IntVar() Checkbutton(frame1, text="Haut-Rêvant", variable=self.Entry_HRevant, command=self.sel_revant) \ .grid(row=2, column=7, columnspan=2, sticky='W', padx="5") # Sexe self.Entry_Sexe = StringVar() Label(frame1, text='Sexe:').grid(row=3, column=0, columnspan=2, sticky='E') Entry(frame1, textvariable=self.Entry_Sexe, justify='left', width=2)\ .grid(row=3, column=2, sticky='W', padx="5") # Ambidextre self.Entry_Ambidextre = IntVar() Label(frame1, text='Ambidextre:').grid(row=3, column=3, sticky='E') Entry(frame1, textvariable=self.Entry_Ambidextre, justify='right', width=3)\ .grid(row=3, column=4, sticky='W', padx="5") # Signes Particuliers self.Entry_SignesP = StringVar() Label(frame1, text='Signes Particuliers:').grid(row=3, column=5, sticky='E') Entry(frame1, textvariable=self.Entry_SignesP, justify='left', width=37)\ .grid(row=3, column=6, columnspan=3, sticky='W', padx="5") # Frame 2 : Caractéristiques frame2 = LabelFrame(root, text=" Caractéristiques ", borderwidth=2, relief='ridge', height=200, width=600) frame2.grid(row=1, column=0, sticky='NW', padx="10", pady="5") frame20 = LabelFrame(frame2, text=' Physiques ', borderwidth=2, relief='ridge', height=200, width=200) frame20.grid(row=0, column=0, sticky='NW', padx="5", pady="5") frame21 = LabelFrame(frame2, text=' Mentales ', borderwidth=2, relief='ridge', height=200, width=200) frame21.grid(row=0, column=1, sticky='NW', padx="5", pady="5") frame22 = LabelFrame(frame2, text=' Pouvoirs ', borderwidth=2, relief='ridge', height=200, width=200) frame22.grid(row=0, column=2, sticky='NW', padx="5", pady="5") frame23 = LabelFrame(frame2, text=' Dérivées ', borderwidth=2, relief='ridge', height=200, width=200) frame23.grid(row=0, column=3, sticky='NW', padx="5", pady="5") self.Entry_C = [] # Colonne 0 de taille à Dextérité for i in range(0, 6): self.Entry_C.append(IntVar()) Label(frame20, text=" "+personnage.caracteristique(i, 1)+':')\ .grid(row=i, column=0, sticky='E') Entry(frame20, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i, column=1, sticky='W', padx="5") Label(frame20, text=' ').grid(row=6, column=0, sticky='E') # Colonne 1 de Vue à Empathie for i in range(6, 12): self.Entry_C.append(IntVar()) Label(frame21, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-6, column=0, sticky='E') Entry(frame21, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i-6, column=1, sticky='W', padx="5") Label(frame21, text=' ').grid(row=6, column=0, sticky='E') # Colonne 2 de Rêve à Chance for i in range(12, 14): self.Entry_C.append(IntVar()) Label(frame22, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-12, column=0, sticky='E') Entry(frame22, textvariable=self.Entry_C[i], justify='right', width=3)\ .grid(row=i-12, column=1, sticky='W', padx="5") for i in range(2, 7): Label(frame22, text=' ').grid(row=i, column=0, sticky='E') # Colonne 3 de Tir à Dérobée (ne peuvent être saisies) for i in range(14, 18): self.Entry_C.append(IntVar()) Label(frame23, text=" "+personnage.caracteristique(i, 1) + ':')\ .grid(row=i-14, column=0, sticky='E') Entry(frame23, textvariable=self.Entry_C[i], justify='right', width=3, state='disabled')\ .grid(row=i-14, column=1, sticky='W', padx="5") for i in range(4, 7): Label(frame23, text=' ').grid(row=i, column=0, sticky='E') # frame 3 : Points et Seuils (ne peuvent être saisis) frame3 = Frame(root, borderwidth=0, relief='flat', height=200, width=600, padx="5", pady="5") frame3.grid(row=2, column=0, sticky='NW', padx="10") self.Entry_P = [] # Vie - Endurance - Encombrement for i in range(0, 3): self.Entry_P.append(IntVar()) Label(frame3, text=personnage.point(i, 1) + ':').grid(row=0, column=2 * i, sticky='E') Entry(frame3, textvariable=self.Entry_P[i], justify='right', width=3, state='disabled')\ .grid(row=0, column=2*i+1, sticky='W', padx="5") # Bonus aux Dommages - Malus Armure - Seuil de Constitution - Seuil de Sustentation for i in range(3, 7): self.Entry_P.append(IntVar()) Label(frame3, text=personnage.point(i, 1) + ':').grid(row=1, column=2 * i - 6, sticky='E') Entry(frame3, textvariable=self.Entry_P[i], justify='right', width=3, state='disabled')\ .grid(row=1, column=2*i-5, sticky='W', padx="5") # frame 4 : Compétences frame4 = LabelFrame(root, text=" Compétences ", borderwidth=2, relief='ridge', height=200, width=800) frame4.grid(row=3, column=0, columnspan=2, sticky='NW', padx="10", pady="5") frame40 = LabelFrame(frame4, text=' Générales ', borderwidth=2, relief='ridge', height=200, width=300) frame40.grid(row=0, column=0, rowspan=2, sticky='NW', padx="5", pady="5") frame41 = LabelFrame(frame4, text=' Particulières ', borderwidth=2, relief='ridge', height=200, width=300) frame41.grid(row=0, column=1, rowspan=2, sticky='NW', padx="5", pady="5") frame42 = LabelFrame(frame4, text=' Spécialisées ', borderwidth=2, relief='ridge', height=200, width=300) frame42.grid(row=0, column=2, rowspan=2, sticky='NW', padx="5", pady="5") frame43 = LabelFrame(frame4, text=' Connaissances ', borderwidth=2, relief='ridge', height=200, width=300) frame43.grid(row=0, column=3, sticky='NW', padx="5", pady="5") frame44 = LabelFrame(frame4, text=' Draconic ', borderwidth=2, relief='ridge', height=200, width=300) frame44.grid(row=1, column=3, sticky='SW', padx="5", pady="5") frame45 = LabelFrame(frame4, text=' Combat Mélée ', borderwidth=2, relief='ridge', height=200, width=300) frame45.grid(row=0, column=4, rowspan=2, sticky='NW', padx="5", pady="5") frame46 = LabelFrame(frame4, text=' Combat Tir-Lancer ', borderwidth=2, relief='ridge', height=200, width=300) frame46.grid(row=0, column=5, rowspan=2, sticky='NW', padx="5", pady="5") self.Entry_A = [] # Colonne 0 : Générales for i in range(0, 11): self.Entry_A.append(IntVar()) Label(frame40, text=" " + personnage.competence(i, 2) + ':').grid( row=i, column=0, sticky='E') Entry(frame40, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i, column=1, sticky='W', padx="5") for i in range(11, 15): Label(frame40, text=' ').grid(row=i, column=0, sticky='E') # Colonne 1 : Particulières for i in range(11, 26): self.Entry_A.append(IntVar()) Label(frame41, text=" " + personnage.competence(i, 2) + ':').grid(row=i - 11, column=0, sticky='E') Entry(frame41, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-11, column=1, sticky='W', padx="5") # Colonne 2 : Spécialisées for i in range(26, 36): self.Entry_A.append(IntVar()) Label(frame42, text=" "+personnage.competence(i, 2)+':')\ .grid(row=i-25, column=0, sticky='E') Entry(frame42, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-25, column=1, sticky='W', padx="5") for i in range(10, 15): Label(frame42, text=' ').grid(row=i + 1, column=0, sticky='E') # Colonne 3: Connaissances for i in range(36, 43): self.Entry_A.append(IntVar()) Label(frame43, text=" "+personnage.competence(i, 2)+':')\ .grid(row=i-35, column=0, sticky='E') Entry(frame43, textvariable=self.Entry_A[i], justify='right', width=3)\ .grid(row=i-35, column=1, sticky='W', padx="5") Label(frame43, text=' ').grid(row=8, column=0, sticky='E') # Colonne 3 : Draconic self.Draconic = [] for i in range(0, 4): self.Entry_A.append(IntVar()) Label(frame44, text=" "+personnage.competence(i+43, 2)+':')\ .grid(row=i, column=0, sticky='E') self.Draconic.append( Entry(frame44, textvariable=self.Entry_A[i + 43], justify='right', width=3)) self.Draconic[i].grid(row=i, column=1, sticky='W', padx="5") Label(frame44, text=' ').grid(row=4, column=0, sticky='E') # Colonne 4 : Combat Mélée for i in range(47, 60): self.Entry_A.append(IntVar()) Label(frame45, text=personnage.competence(i, 2) + ':') \ .grid(row=i - 46, column=0, sticky='E') Entry(frame45, textvariable=self.Entry_A[i], justify='right', width=3) \ .grid(row=i - 46, column=1, sticky='W', padx="5") for i in range(13, 15): Label(frame45, text=' ').grid(row=i + 1, column=0, sticky='E') # Colonne 5 : Combat Tir for i in range(60, 66): self.Entry_A.append(IntVar()) Label(frame46, text=" " + personnage.competence(i, 2) + ':') \ .grid(row=i - 59, column=0, sticky='E') Entry(frame46, textvariable=self.Entry_A[i], justify='right', width=3) \ .grid(row=i - 59, column=1, sticky='W', padx="5") for i in range(6, 15): Label(frame46, text=' ').grid(row=i + 1, column=0, sticky='E') # frame5 : table de résolution et lancer de dé frame5 = LabelFrame(root, text=" Résolution et Lancer de Dés ", borderwidth=2, relief='ridge', height=200, width=600) frame5.grid(row=0, column=1, rowspan=3, columnspan=2, sticky='NW', padx="10", pady="5") # Listbox caractéristiques Label(frame5, text=' Caractéristique:').grid(row=0, column=0, columnspan=2, padx="10", sticky='NW') self.liste1 = Listbox(frame5, height=13, width=18, relief='sunken') self.liste1.grid(row=1, column=1, sticky='NW', pady="5") for i in range(0, 18): self.liste1.insert(i, personnage.caracteristique(i, 1)) self.liste1.bind('<<ListboxSelect>>', self.sel_liste1) # Listbox compétences Label(frame5, text='Compétence:').grid(row=0, column=2, columnspan=2, padx="10", sticky='NW') self.liste2 = Listbox(frame5, height=13, width=18, relief='sunken') self.liste2.grid(row=1, column=3, sticky='NW', pady="5") for i in range(0, 66): self.liste2.insert(i, personnage.competence(i, 2)) self.liste2.bind('<<ListboxSelect>>', self.sel_liste2) # Zone de résulats self.Entry_R_C_Val = IntVar() Entry(frame5, textvariable=self.Entry_R_C_Val, justify='right', width=3,) \ .grid(row=16, column=0, sticky='E', padx="10") self.Entry_R_C_Name = StringVar() Entry(frame5, textvariable=self.Entry_R_C_Name, justify='left', width=18, state='disabled') \ .grid(row=16, column=1, sticky='W') self.Entry_R_A_Val = IntVar() Entry(frame5, textvariable=self.Entry_R_A_Val, justify='right', width=3,) \ .grid(row=16, column=2, sticky='E', padx="10") self.Entry_R_A_Name = StringVar() Entry(frame5, textvariable=self.Entry_R_A_Name, justify='left', width=18, state='disabled') \ .grid(row=16, column=3, sticky='W') Label(frame5, text=' Seuil de Réussite:').grid(row=17, column=0, sticky='NE') self.Entry_R_Seuil = IntVar() Entry(frame5, textvariable=self.Entry_R_Seuil, justify='right', width=3, state='disabled')\ .grid(row=17, column=1, sticky='W', padx="10") Label(frame5, text='Tirage:').grid(row=17, column=2, sticky='NE') self.Entry_R_Tirage = IntVar() Entry(frame5, textvariable=self.Entry_R_Tirage, justify='right', width=3, state='disabled') \ .grid(row=17, column=3, sticky='W', padx="10") Label(frame5, text='Résultat Spécial:').grid(row=18, column=0, sticky='NE') self.Entry_R_Special = StringVar() Entry(frame5, textvariable=self.Entry_R_Special, justify='left', width=30, state='disabled') \ .grid(row=18, column=1, columnspan=2, sticky='W', padx="10") Label(frame5, text=' ').grid(row=19, column=4, sticky='NE') # Bouton pour le lancer de Dés Button(frame5, text="Lancer les Dés", command=self.lancer) \ .grid(row=18, column=3, columnspan=3, sticky='W', padx="10") # La mascote # On la fait déborder sur le frame4 pour gagner en largeur totale self.dragon = PhotoImage(file='./dragon3.gif') logo = Canvas(root, width=200, height=181, bd=1, relief='ridge') logo.grid(row=3, column=1, columnspan=2, sticky='SE', padx="10", pady="3") logo.create_image(0, 0, image=self.dragon, anchor='nw') # L'ecran étant initialisé, on peut créér un premier personnage par défaut self.creer() return # Fonction de recopie de la sélection depuis la Listbox des caractéristiques # Met à jour les 2 champs points et nom de caractéristique pour le calcul de résolution def sel_liste1(self, event): if self.liste1.curselection() != (): index = self.liste1.curselection()[0] self.Entry_R_C_Name.set(self.liste1.get(index)) self.Entry_R_C_Val.set(self.Entry_C[index].get()) return # Fonction de recopie de la sélection depuis la Listbox des compétences # Met à jour les 2 champs points et nom de compétence pour le calcul de résolution def sel_liste2(self, event): if self.liste2.curselection() != (): index = self.liste2.curselection()[0] self.Entry_R_A_Name.set(self.liste2.get(index)) self.Entry_R_A_Val.set(self.Entry_A[index].get()) return # Fonction de changement d'etat haut-rêvant def sel_revant(self): if self.Entry_HRevant.get() != 1: for i in range(0, 4): self.Entry_A[i + 42].set(-11) self.Draconic[i].configure(state='disabled') else: for i in range(0, 4): self.Draconic[i].configure(state='normal') return # Nouveau jeu # Il faut préalablement fermer le jeu en cours def nouveau(self): if self.fermer(): self.jeu.nouveau() return # Ouvrir jeu # Il faut préalablement fermer le jeu en cours # On reçoit le nom du jeu suivi d'une liste de personnages ou None si rien d'ouvert par le jeu def ouvrir(self): if self.fermer(): names = self.jeu.ouvrir() if names != None: numero = -1 for person in names: # index 0 : nom du fichier jeu if numero < 0: self.root.title('Rêve de Dragon - ' + person) # autres index : personnages # index vaudra le nombre de personnages reçus else: self.viewmenu.add_command(label=person, command=lambda index=numero: self.selectionner(index)) numero += 1 # On affiche le premier personnage if numero > 0: self.selectionner(0) return # Fermer le jeu en cours # On efface tous les personnages # on cree un nouveau personnage vide pour obtenir un affichage vierge def fermer(self): if not self.saved: self.saved = askyesno( 'Fermer', 'Voulez-vous vraiment fermer ce Jeu ?\nLes données non enregistrées seront perdues' ) if self.saved: last = self.viewmenu.index("end") if last is not None: for i in range(last + 1): self.viewmenu.delete(0) self.root.title('Rêve de Dragon') self.jeu.fermer() self.creer() return self.saved # Quitter le programme # onh détruit la fenêtre et on quitte def quitter(self): if askyesno('Quitter', 'Voulez-vous vraiment quitter le programme ?'): self.root.destroy() self.root.quit() return # Fonction interne d'affichage des données d'un personnage # Copie toutes les données du dictionnaire local dans les variables associées aux champs de saisie def affiche(self): self.Entry_Nom.set(self.pod["Fiche"]["Nom"]) self.Entry_Age.set(self.pod["Fiche"]["Age"]) self.Entry_Heure.set(self.pod["Fiche"]["Heure_Naissance"]) self.Entry_Taille.set(self.pod["Fiche"]["Taille"]) self.Entry_Poids.set(self.pod["Fiche"]["Poids"]) self.Entry_Sexe.set(self.pod["Fiche"]["Sexe"]) self.Entry_Cheveux.set(self.pod["Fiche"]["Cheveux"]) self.Entry_Yeux.set(self.pod["Fiche"]["Yeux"]) self.Entry_Beaute.set(self.pod["Fiche"]["Beaute"]) self.Entry_Ambidextre.set(self.pod["Fiche"]["Ambidextre"]) self.Entry_HRevant.set(self.pod["Fiche"]["Haut_Revant"]) self.Entry_SignesP.set(self.pod["Fiche"]["Signes_Particulier"]) for i in range(0, 18): self.Entry_C[i].set( self.pod["Caracteristique"][personnage.caracteristique(i, 0)]) for i in range(0, 7): self.Entry_P[i].set(self.pod["Point"][personnage.point(i, 0)]) for i in range(0, 66): self.Entry_A[i].set(self.pod["Competence"][personnage.competence( i, 0)][personnage.competence(i, 1)]) if self.Entry_HRevant.get() != 1: for i in range(0, 4): self.Draconic[i].configure(state='disabled') return # Création d'un nouveau personnage # On demande au jeu de créer un nouveau personnage dans la liste # On initialise toutes les variables de saisie aux valeur reçues def creer(self): self.pod = self.jeu.creer() self.affiche() return # Validation des données du personnage # On reconstitue le dictionnaire qui est envoyé au jeu pour vérification # Le jeu répond avec un dictionnaire contenant # - l'index du personnage # - Le nom de personnage # - Un message d'erreur ou d'acceptation def valider(self): if len(self.Entry_Nom.get()) < 1: return self.pod["Fiche"]["Nom"] = self.Entry_Nom.get() self.pod["Fiche"]["Age"] = self.Entry_Age.get() self.pod["Fiche"]["Heure_Naissance"] = self.Entry_Heure.get() self.pod["Fiche"]["Taille"] = self.Entry_Taille.get() self.pod["Fiche"]["Poids"] = self.Entry_Poids.get() self.pod["Fiche"]["Sexe"] = self.Entry_Sexe.get() self.pod["Fiche"]["Cheveux"] = self.Entry_Cheveux.get() self.pod["Fiche"]["Yeux"] = self.Entry_Yeux.get() self.pod["Fiche"]["Beaute"] = self.Entry_Beaute.get() self.pod["Fiche"]["Ambidextre"] = self.Entry_Ambidextre.get() self.pod["Fiche"]["Haut_Revant"] = self.Entry_HRevant.get() self.pod["Fiche"]["Signes_Particulier"] = self.Entry_SignesP.get() for i in range(0, 18): self.pod["Caracteristique"][personnage.caracteristique( i, 0)] = self.Entry_C[i].get() for i in range(0, 7): self.pod["Point"][personnage.point(i, 0)] = self.Entry_P[i].get() for i in range(0, 65): self.pod["Competence"][personnage.competence( i, 0)][personnage.competence(i, 1)] = self.Entry_A[i].get() retour = self.jeu.valider(self.pod) index = retour["index"] # On a bien un index valide : alors on met à jour le menu et on va chercher les données du personnage if index is not None: if self.viewmenu.entrycget(index, 'label') != self.Old_Nom: self.viewmenu.entryconfigure(index, label=retour["nom"]) elif self.viewmenu.entrycget(index, 'label') != retour["nom"]: self.viewmenu.add_command( label=retour["nom"], command=lambda index=index: self.selectionner(index)) self.pod = self.jeu.selectionner(index) self.affiche() # En cas d'erreur ou retour ok, il y a un message du jeu if len(retour["message"]): showerror("Validation", retour["message"]) self.saved = False return # Sélection d'un personnage depuis le menu # On envoie au jeu l'index du menu qui correspond à l'index de la liste du jeu # Les données du personnage reçu sont ensuite affichées def selectionner(self, code): self.pod = self.jeu.selectionner(code) self.affiche() return # Nouvelle partie # On demande au Jeu de changer de partie # Le jeu renvoie le contenu du personnage courant def partie(self): if askyesno( 'Nouvelle Partie', 'Voulez-vous vraiment terminer cette partie ?\nLes données non enregistrées seront perdues' ): self.pod = self.jeu.partie() self.affiche() return # Lancer de dé # Calcul de résolution puis lancer des dés # On passe au jeu les valeurs de caractéristiques et compétences sélectionnées # Le résultat sera récupéré en retour et affiché def lancer(self): resultat = self.jeu.lancer(self.Entry_R_C_Val.get(), self.Entry_R_A_Val.get()) self.Entry_R_Seuil.set(resultat["seuil"]) self.Entry_R_Tirage.set(resultat["tirage"]) self.Entry_R_Special.set(resultat["special"]) self.saved = False return # Affichage de la boite de dialogue A propos # Le texte est dans le fichier A_PROPOS.TXT def a_propos(self): fp = open("A_PROPOS.TXT", "r") texte_a_propos = fp.read() fp.close() showinfo("A Propos de...", texte_a_propos) return # Dialogue d'aide pour connaitre les règles du jeu # Le texte est dans le fichier REGLES.TXT def regles(self): file = "REGLE.TXT" titre = "Règles du Jeu Rêve de Dragon." self.aide(file, titre) return # Le texte est dans le fichier AIDE.TXT def utilise(self): file = "AIDE.TXT" titre = "Utilisation de Rêve de Dragon." self.aide(file, titre) return # Aide du jeu # Affiche une boite de dialogue avec un widget texte contenant l'aide def aide(self, file, titre): # On ouvre une fenêtre fille de celle du jeu self.wdw = Toplevel() self.wdw.geometry('+400+100') self.wdw.title(titre) # Le texte de l'aide est stocké dans un fichier Atexte fp = open(file, "r") texte_aide = fp.read() fp.close() # On l'affiche dans un widget Text avec une barre de défilement # Ne fonctionne que si on utilise grid pour placer les Widgets # Il faut mettre le widget en état disabled pour éviter que l'on y entre du texte self.S = Scrollbar(self.wdw, orient='vertical') self.T = Text(self.wdw, height=50, width=100, font=('TkDefaultFont', 10)) self.T.grid(row=0, column=0, sticky='NW') self.S.configure(command=self.T.yview) self.T.configure(yscrollcommand=self.S.set) self.S.grid(row=0, column=1, sticky='SN') self.T.configure(state='normal') self.T.insert('end', texte_aide) self.T.configure(state='disabled') # La nouvelle fenêtre est ouverte en modal # Il faudra la fermer pour reprendre le contrôle de la fenêtre principale self.wdw.transient(self.root) self.wdw.grab_set() self.root.wait_window(self.wdw) return # fonction qui ne fait rien (pour les tests) def void(self): return
class Timer(Tk): """ Chronométre de temps de travail pour plus d'efficacité """ def __init__(self): Tk.__init__(self, className="WorkHourGlass") self.on = False # is the timer on? if not CONFIG.options("Tasks"): CONFIG.set("Tasks", _("Work"), CMAP[0]) # colors self.background = { _("Work"): CONFIG.get("Work", "bg"), _("Break"): CONFIG.get("Break", "bg"), _("Rest"): CONFIG.get("Rest", "bg") } self.foreground = { _("Work"): CONFIG.get("Work", "fg"), _("Break"): CONFIG.get("Break", "fg"), _("Rest"): CONFIG.get("Rest", "fg") } # window configuration if PL[0] == "w": self.iconbitmap(ICON_WIN, default=ICON_WIN) else: self.icon = PhotoImage(master=self, file=ICON) self.iconphoto(True, self.icon) self.title("WorkHourGlass") self.protocol("WM_DELETE_WINDOW", self.exit) self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) self.columnconfigure(1, weight=1) self.minsize(181, 190) self.geometry("200x190+%i+%i" % ((self.winfo_screenwidth() - 200) // 2, (self.winfo_screenheight() - 190) // 2)) self.configure(background=self.background[_("Work")]) # style self.style = Style(self) self.style.theme_use(STYLE) self.style.configure('fen.TLabel', foreground=self.foreground[_("Work")], background=self.background[_("Work")]) # nombre de séquence de travail effectuées d'affilée (pour # faire des pauses plus longues tous les 4 cycles) self.nb_cycles = 0 self.pomodori = IntVar(self, 0) # images self.im_go = PhotoImage(master=self, file=GO) self.im_stop = PhotoImage(master=self, file=STOP) self.im_plus = PhotoImage(master=self, file=PLUS) self.im_moins = PhotoImage(master=self, file=MOINS) self.im_params = PhotoImage(master=self, file=PARAMS) self.im_tomate = PhotoImage(master=self, file=TOMATE) self.im_graph = PhotoImage(master=self, file=GRAPH) # tasks list tasks_frame = Frame(self) tasks_frame.grid(row=3, column=0, columnspan=3, sticky="wnse") tasks = [t.capitalize() for t in CONFIG.options("Tasks")] self.task = StringVar(self, tasks[0]) self.menu_tasks = Menu(tasks_frame, tearoff=False) for task in tasks: self.menu_tasks.add_radiobutton(label=task, value=task, variable=self.task) self.menu_tasks.add_command(label=_("New task"), image=self.im_plus, compound="left", command=self.add_task) self.menu_tasks.add_command(label=_("Remove task"), image=self.im_moins, compound="left", command=self.del_task) self.menu_tasks.add_command(label=_("Statistics"), image=self.im_graph, compound="left", command=self.display_stats) self.choose_task = Menubutton(tasks_frame, textvariable=self.task, menu=self.menu_tasks) Label(tasks_frame, text=_("Task: "), font="CMU\ Sans\ Serif\ Demi\ Condensed 12", width=6, anchor="e").pack(side="left") self.choose_task.pack(side="right", fill="x") # display self.tps = [CONFIG.getint("Work", "time"), 0] # time: min, sec self.activite = StringVar(self, _("Work")) self.titre = Label(self, textvariable=self.activite, font='CMU\ Sans\ Serif\ Demi\ Condensed 14', style='fen.TLabel', anchor="center") self.titre.grid(row=0, column=0, columnspan=2, sticky="we") self.temps = Label( self, text="{0:02}:{1:02}".format(self.tps[0], self.tps[1]), font="%s %i" % (CONFIG.get( "General", "font"), CONFIG.getint("General", "fontsize")), style='fen.TLabel', anchor="center") self.temps.grid(row=1, column=0, columnspan=2, sticky="nswe", pady=(0, 10)) self.aff_pomodori = Label(self, textvariable=self.pomodori, image=self.im_tomate, compound="left", style='fen.TLabel', font='CMU\ Sans\ Serif\ Demi\ Condensed 14') self.aff_pomodori.grid(row=2, columnspan=2, sticky="e", padx=20) # buttons self.b_go = Button(self, image=self.im_go, command=self.go) self.b_go.grid(row=4, column=0, sticky="ew") self.b_params = Button(self, image=self.im_params, command=self.params) self.b_params.grid(row=4, column=1, sticky="ew") # --- make window sticky self.update_idletasks() e = EWMH() try: for w in e.getClientList(): if w.get_wm_name() == self.title(): e.setWmState(w, 1, '_NET_WM_STATE_STICKY') e.display.flush() except ewmh.display.error.BadWindow: pass def set_config(self): self.background = { _("Work"): CONFIG.get("Work", "bg"), _("Break"): CONFIG.get("Break", "bg"), _("Rest"): CONFIG.get("Rest", "bg") } self.foreground = { _("Work"): CONFIG.get("Work", "fg"), _("Break"): CONFIG.get("Break", "fg"), _("Rest"): CONFIG.get("Rest", "fg") } act = self.activite.get() self.configure(background=self.background[act]) self.style.configure('fen.TLabel', foreground=self.foreground[act], background=self.background[act]) self.temps.configure(font="%s %i" % (CONFIG.get("General", "font"), CONFIG.getint("General", "fontsize"))) def add_task(self): def ajoute(event=None): task = nom.get() if task and not CONFIG.has_option("Tasks", task): index = len(CONFIG.options("Tasks")) self.menu_tasks.insert_radiobutton(index, label=task, value=task, variable=self.task) CONFIG.set("Tasks", task, CMAP[index % len(CMAP)]) top.destroy() with open(PATH_CONFIG, "w") as file: CONFIG.write(file) self.menu_tasks.invoke(index) else: nom.delete(0, "end") top = Toplevel(self) top.title(_("New task")) top.transient(self) top.grab_set() nom = Entry(top, width=20) nom.grid(row=0, columnspan=2, sticky="ew") nom.focus_set() nom.bind('<Key-Return>', ajoute) Button(top, text=_("Cancel"), command=top.destroy).grid(row=1, column=0) Button(top, text=_("Ok"), command=ajoute).grid(row=1, column=1) top.wait_window(top) def del_task(self): """ Suppression de tâches """ def supprime(): rep = askyesno(_("Confirmation"), _("Are you sure you want to delete these tasks?")) if rep: for i in range(len(boutons) - 1, -1, -1): # l'ordre de parcours permet de supprimer les derniers # éléments en premier afin de ne pas modifier les index des # autres éléments lors des suppressions task = tasks[i] if "selected" in boutons[i].state(): # suppression de la tâche de la liste des tâches CONFIG.remove_option("Tasks", task) tasks.remove(task) # suppression de l'entrée correspondante dans le menu self.menu_tasks.delete(i) if not tasks: CONFIG.set("Tasks", _("Work"), CMAP[0]) tasks.append(_("Work")) self.menu_tasks.insert_radiobutton( 0, label=_("Work"), value=_("Work"), variable=self.task) if self.task.get() == task: self.task.set(tasks[0]) # suppression des stats associées chemin = PATH_STATS + "_" + "_".join(task.split(" ")) if os.path.exists(chemin): os.remove(chemin) top.destroy() with open(PATH_CONFIG, "w") as file: CONFIG.write(file) else: top.destroy() tasks = [t.capitalize() for t in CONFIG.options("Tasks")] top = Toplevel(self) top.title(_("Remove task")) top.transient(self) top.grab_set() style = Style(top) style.theme_use(STYLE) boutons = [] for i, task in enumerate(tasks): boutons.append(Checkbutton(top, text=task)) boutons[-1].grid(row=i, columnspan=2, sticky="w") Button(top, text=_("Cancel"), command=top.destroy).grid(row=i + 1, column=0) Button(top, text=_("Delete"), command=supprime).grid(row=i + 1, column=1) def stats(self): """ Enregistre la durée de travail (en min) effectuée ce jour pour la tâche qui vient d'être interrompue. Seul les pomodori complets sont pris en compte. """ # TODO: translate, correct date/time format pom = self.pomodori.get() if pom: # la tâche en cours a été travaillée, il faut enregistrer les stats date = dt.date.today() task = self.task.get() chemin = PATH_STATS + "_" + "_".join(task.split(" ")) if not os.path.exists(chemin): with open(chemin, 'w') as fich: fich.write( "# tâche : %s\n# jour\tmois\tannée\ttemps de travail (min)\n" % task) with open(chemin, 'r') as fich: stats = fich.readlines() if len(stats) > 2: last = stats[-1][:10], stats[-1][:-1].split("\t")[-1] else: last = "", 0 if last[0] != date.strftime("%d\t%m\t%Y"): with open(chemin, 'a') as fich: fich.write("%s\t%i\n" % (date.strftime("%d\t%m\t%Y"), pom * CONFIG.getint("Work", "time"))) else: # un nombre a déjà été enregistré plus tôt dans la journée # il faut les additioner with open(chemin, 'w') as fich: fich.writelines(stats[:-1]) fich.write( "%s\t%i\n" % (date.strftime("%d\t%m\t%Y"), pom * CONFIG.getint("Work", "time") + int(last[1]))) def display_stats(self): """ affiche les statistiques """ plt.figure("Statistiques") tasks = [t.capitalize() for t in CONFIG.options("Tasks")] coul = [CONFIG.get("Tasks", task) for task in tasks] stats_x = [] stats_y = [] demain = dt.date.today().toordinal() + 1 min_x = demain # récupération des données no_data = True for i, task in enumerate(tasks): chemin = PATH_STATS + "_" + "_".join(task.split(" ")) if os.path.exists(chemin): no_data = False stat = loadtxt(chemin, dtype=int) if len(stat.shape) == 1: stat = stat.reshape(1, 4) x = [ dt.date(an, mois, jour).toordinal() for jour, mois, an in stat[:, :3] ] y = stat[:, -1] / 60 # temps de travail min_x = min(x[0], min_x) stats_x.append(x) stats_y.append(y) else: # la taĉhe n'a jamais été travaillée stats_x.append([demain - 1]) stats_y.append(array([0])) # plots xx = arange(min_x, demain, dtype=float) yy0 = zeros_like(xx) # pour empiler les stats if not no_data: for (i, task), x, y in zip(enumerate(tasks), stats_x, stats_y): ax0 = plt.subplot(111) plt.ylabel(_("time (h)")) plt.xlabel(_("date")) yy = array([], dtype=int) # comble les trous par des 0 # ainsi, les jours où une tâche n'a pas été travaillée correspondent # à des 0 sur le graph xxx = arange(min_x, x[0]) yy = concatenate((yy, zeros_like(xxx, dtype=int))) for j in range(len(x) - 1): xxx = arange(x[j], x[j + 1]) yy = concatenate((yy, [y[j]], zeros(len(xxx) - 1, dtype=int))) xxx = arange(x[-1], demain) yy = concatenate((yy, [y[-1]], zeros(len(xxx) - 1, dtype=int))) plt.bar(xx - 0.4, yy, bottom=yy0, width=0.8, label=task, color=coul[i]) yy0 += yy axx = array( [int(xt) for xt in ax0.get_xticks() if xt.is_integer()]) ax0.set_xticks(axx) ax0.set_xticklabels( [dt.date.fromordinal(i).strftime("%x") for i in axx]) plt.gcf().autofmt_xdate() ax0.set_xlim(min_x - 0.5, demain - 0.5) lgd = plt.legend(fontsize=10) lgd.draggable() plt.subplots_adjust(top=0.95) max_y = yy0.max() ax0.set_ylim(0, max_y + 0.1 * max_y) plt.show() def go(self): if self.on: self.on = False if self.activite.get() == _("Work"): # rep = askyesno(title=_("Confirmation"), # message=_("You should not interrupt your work if you want to be efficient. Do you still want to suspend the timer?"), # icon="warning") # else: # rep = True self.stop() # if rep: # self.b_go.configure(image=self.im_go) # else: # self.on = True # self.affiche() else: self.on = True self.choose_task.config(state="disabled") self.b_go.configure(image=self.im_stop) self.after(1000, self.affiche) def stop(self, confirmation=True): """ Arrête le décompte du temps et le réinitialise, demande une confirmation avant de le faire si confirmation=True """ self.on = False if confirmation: rep = askyesno( title=_("Confirmation"), message=_( "Are you sure you want to give up the current session?")) else: rep = True if rep: self.stats() self.pomodori.set(0) self.nb_cycles = 0 self.b_go.configure(image=self.im_go) self.tps = [CONFIG.getint("Work", "time"), 0] self.temps.configure( text="{0:02}:{1:02}".format(self.tps[0], self.tps[1])) act = _("Work") self.activite.set(act) self.style.configure('fen.TLabel', background=self.background[act], foreground=self.foreground[act]) self.configure(background=self.background[act]) self.choose_task.config(state="normal") else: self.on = True self.affiche() def ting(self): """ joue le son marquant le changement de période """ if not CONFIG.getboolean("Sound", "mute", fallback=False): if PL[0] == "w": Popen([ "powershell", "-c", '(New-Object Media.SoundPlayer "%s").PlaySync();' % (CONFIG.get("Sound", "beep")) ]) else: Popen([ CONFIG.get("Sound", "player"), CONFIG.get("Sound", "beep") ]) def affiche(self): if self.on: self.tps[1] -= 1 if self.tps[1] == 0: if self.tps[0] == 0: self.ting() if self.activite.get() == _("Work"): self.pomodori.set(self.pomodori.get() + 1) self.nb_cycles += 1 if self.nb_cycles % 4 == 0: # pause longue self.activite.set(_("Rest")) self.tps = [CONFIG.getint("Rest", "time"), 0] else: # pause courte self.activite.set(_("Break")) self.tps = [CONFIG.getint("Break", "time"), 0] else: self.activite.set(_("Work")) self.tps = [CONFIG.getint("Work", "time"), 0] act = self.activite.get() self.style.configure('fen.TLabel', background=self.background[act], foreground=self.foreground[act]) self.configure(background=self.background[act]) elif self.tps[1] == -1: self.tps[0] -= 1 self.tps[1] = 59 self.temps.configure( text="{0:02}:{1:02}".format(self.tps[0], self.tps[1])) self.after(1000, self.affiche) def params(self): on = self.on self.on = False self.b_go.configure(image=self.im_go) p = Params(self) self.wait_window(p) if on: self.on = True self.choose_task.config(state="disabled") self.b_go.configure(image=self.im_stop) self.after(1000, self.affiche) def exit(self): self.stats() plt.close() self.destroy()
class ShiftReduceApp(object): """ A graphical tool for exploring the shift-reduce parser. The tool displays the parser's stack and the remaining text, and allows the user to control the parser's operation. In particular, the user can shift tokens onto the stack, and can perform reductions on the top elements of the stack. A "step" button simply steps through the parsing process, performing the operations that ``nltk.parse.ShiftReduceParser`` would use. """ def __init__(self, grammar, sent, trace=0): self._sent = sent self._parser = SteppingShiftReduceParser(grammar, trace) # Set up the main window. self._top = Tk() self._top.title('Shift Reduce Parser Application') # Animations. animating_lock is a lock to prevent the demo # from performing new operations while it's animating. self._animating_lock = 0 self._animate = IntVar(self._top) self._animate.set(10) # = medium # The user can hide the grammar. self._show_grammar = IntVar(self._top) self._show_grammar.set(1) # Initialize fonts. self._init_fonts(self._top) # Set up key bindings. self._init_bindings() # Create the basic frames. self._init_menubar(self._top) self._init_buttons(self._top) self._init_feedback(self._top) self._init_grammar(self._top) self._init_canvas(self._top) # A popup menu for reducing. self._reduce_menu = Menu(self._canvas, tearoff=0) # Reset the demo, and set the feedback frame to empty. self.reset() self._lastoper1['text'] = '' ######################################### ## Initialization Helpers ######################################### def _init_fonts(self, root): # See: <http://www.astro.washington.edu/owen/ROTKFolklore.html> self._sysfont = tkinter.font.Font(font=Button()["font"]) root.option_add("*Font", self._sysfont) # TWhat's our font size (default=same as sysfont) self._size = IntVar(root) self._size.set(self._sysfont.cget('size')) self._boldfont = tkinter.font.Font(family='helvetica', weight='bold', size=self._size.get()) self._font = tkinter.font.Font(family='helvetica', size=self._size.get()) def _init_grammar(self, parent): # Grammar view. self._prodframe = listframe = Frame(parent) self._prodframe.pack(fill='both', side='left', padx=2) self._prodlist_label = Label(self._prodframe, font=self._boldfont, text='Available Reductions') self._prodlist_label.pack() self._prodlist = Listbox(self._prodframe, selectmode='single', relief='groove', background='white', foreground='#909090', font=self._font, selectforeground='#004040', selectbackground='#c0f0c0') self._prodlist.pack(side='right', fill='both', expand=1) self._productions = list(self._parser.grammar().productions()) for production in self._productions: self._prodlist.insert('end', (' %s' % production)) self._prodlist.config(height=min(len(self._productions), 25)) # Add a scrollbar if there are more than 25 productions. if 1: #len(self._productions) > 25: listscroll = Scrollbar(self._prodframe, orient='vertical') self._prodlist.config(yscrollcommand=listscroll.set) listscroll.config(command=self._prodlist.yview) listscroll.pack(side='left', fill='y') # If they select a production, apply it. self._prodlist.bind('<<ListboxSelect>>', self._prodlist_select) # When they hover over a production, highlight it. self._hover = -1 self._prodlist.bind('<Motion>', self._highlight_hover) self._prodlist.bind('<Leave>', self._clear_hover) def _init_bindings(self): # Quit self._top.bind('<Control-q>', self.destroy) self._top.bind('<Control-x>', self.destroy) self._top.bind('<Alt-q>', self.destroy) self._top.bind('<Alt-x>', self.destroy) # Ops (step, shift, reduce, undo) self._top.bind('<space>', self.step) self._top.bind('<s>', self.shift) self._top.bind('<Alt-s>', self.shift) self._top.bind('<Control-s>', self.shift) self._top.bind('<r>', self.reduce) self._top.bind('<Alt-r>', self.reduce) self._top.bind('<Control-r>', self.reduce) self._top.bind('<Delete>', self.reset) self._top.bind('<u>', self.undo) self._top.bind('<Alt-u>', self.undo) self._top.bind('<Control-u>', self.undo) self._top.bind('<Control-z>', self.undo) self._top.bind('<BackSpace>', self.undo) # Misc self._top.bind('<Control-p>', self.postscript) self._top.bind('<Control-h>', self.help) self._top.bind('<F1>', self.help) self._top.bind('<Control-g>', self.edit_grammar) self._top.bind('<Control-t>', self.edit_sentence) # Animation speed control self._top.bind('-', lambda e, a=self._animate: a.set(20)) self._top.bind('=', lambda e, a=self._animate: a.set(10)) self._top.bind('+', lambda e, a=self._animate: a.set(4)) def _init_buttons(self, parent): # Set up the frames. self._buttonframe = buttonframe = Frame(parent) buttonframe.pack(fill='none', side='bottom') Button( buttonframe, text='Step', background='#90c0d0', foreground='black', command=self.step, ).pack(side='left') Button(buttonframe, text='Shift', underline=0, background='#90f090', foreground='black', command=self.shift).pack(side='left') Button(buttonframe, text='Reduce', underline=0, background='#90f090', foreground='black', command=self.reduce).pack(side='left') Button(buttonframe, text='Undo', underline=0, background='#f0a0a0', foreground='black', command=self.undo).pack(side='left') def _init_menubar(self, parent): menubar = Menu(parent) filemenu = Menu(menubar, tearoff=0) filemenu.add_command(label='Reset Parser', underline=0, command=self.reset, accelerator='Del') filemenu.add_command(label='Print to Postscript', underline=0, command=self.postscript, accelerator='Ctrl-p') filemenu.add_command(label='Exit', underline=1, command=self.destroy, accelerator='Ctrl-x') menubar.add_cascade(label='File', underline=0, menu=filemenu) editmenu = Menu(menubar, tearoff=0) editmenu.add_command(label='Edit Grammar', underline=5, command=self.edit_grammar, accelerator='Ctrl-g') editmenu.add_command(label='Edit Text', underline=5, command=self.edit_sentence, accelerator='Ctrl-t') menubar.add_cascade(label='Edit', underline=0, menu=editmenu) rulemenu = Menu(menubar, tearoff=0) rulemenu.add_command(label='Step', underline=1, command=self.step, accelerator='Space') rulemenu.add_separator() rulemenu.add_command(label='Shift', underline=0, command=self.shift, accelerator='Ctrl-s') rulemenu.add_command(label='Reduce', underline=0, command=self.reduce, accelerator='Ctrl-r') rulemenu.add_separator() rulemenu.add_command(label='Undo', underline=0, command=self.undo, accelerator='Ctrl-u') menubar.add_cascade(label='Apply', underline=0, menu=rulemenu) viewmenu = Menu(menubar, tearoff=0) viewmenu.add_checkbutton(label="Show Grammar", underline=0, variable=self._show_grammar, command=self._toggle_grammar) viewmenu.add_separator() viewmenu.add_radiobutton(label='Tiny', variable=self._size, underline=0, value=10, command=self.resize) viewmenu.add_radiobutton(label='Small', variable=self._size, underline=0, value=12, command=self.resize) viewmenu.add_radiobutton(label='Medium', variable=self._size, underline=0, value=14, command=self.resize) viewmenu.add_radiobutton(label='Large', variable=self._size, underline=0, value=18, command=self.resize) viewmenu.add_radiobutton(label='Huge', variable=self._size, underline=0, value=24, command=self.resize) menubar.add_cascade(label='View', underline=0, menu=viewmenu) animatemenu = Menu(menubar, tearoff=0) animatemenu.add_radiobutton(label="No Animation", underline=0, variable=self._animate, value=0) animatemenu.add_radiobutton(label="Slow Animation", underline=0, variable=self._animate, value=20, accelerator='-') animatemenu.add_radiobutton(label="Normal Animation", underline=0, variable=self._animate, value=10, accelerator='=') animatemenu.add_radiobutton(label="Fast Animation", underline=0, variable=self._animate, value=4, accelerator='+') menubar.add_cascade(label="Animate", underline=1, menu=animatemenu) helpmenu = Menu(menubar, tearoff=0) helpmenu.add_command(label='About', underline=0, command=self.about) helpmenu.add_command(label='Instructions', underline=0, command=self.help, accelerator='F1') menubar.add_cascade(label='Help', underline=0, menu=helpmenu) parent.config(menu=menubar) def _init_feedback(self, parent): self._feedbackframe = feedbackframe = Frame(parent) feedbackframe.pack(fill='x', side='bottom', padx=3, pady=3) self._lastoper_label = Label(feedbackframe, text='Last Operation:', font=self._font) self._lastoper_label.pack(side='left') lastoperframe = Frame(feedbackframe, relief='sunken', border=1) lastoperframe.pack(fill='x', side='right', expand=1, padx=5) self._lastoper1 = Label(lastoperframe, foreground='#007070', background='#f0f0f0', font=self._font) self._lastoper2 = Label(lastoperframe, anchor='w', width=30, foreground='#004040', background='#f0f0f0', font=self._font) self._lastoper1.pack(side='left') self._lastoper2.pack(side='left', fill='x', expand=1) def _init_canvas(self, parent): self._cframe = CanvasFrame(parent, background='white', width=525, closeenough=10, border=2, relief='sunken') self._cframe.pack(expand=1, fill='both', side='top', pady=2) canvas = self._canvas = self._cframe.canvas() self._stackwidgets = [] self._rtextwidgets = [] self._titlebar = canvas.create_rectangle(0, 0, 0, 0, fill='#c0f0f0', outline='black') self._exprline = canvas.create_line(0, 0, 0, 0, dash='.') self._stacktop = canvas.create_line(0, 0, 0, 0, fill='#408080') size = self._size.get() + 4 self._stacklabel = TextWidget(canvas, 'Stack', color='#004040', font=self._boldfont) self._rtextlabel = TextWidget(canvas, 'Remaining Text', color='#004040', font=self._boldfont) self._cframe.add_widget(self._stacklabel) self._cframe.add_widget(self._rtextlabel) ######################################### ## Main draw procedure ######################################### def _redraw(self): scrollregion = self._canvas['scrollregion'].split() (cx1, cy1, cx2, cy2) = [int(c) for c in scrollregion] # Delete the old stack & rtext widgets. for stackwidget in self._stackwidgets: self._cframe.destroy_widget(stackwidget) self._stackwidgets = [] for rtextwidget in self._rtextwidgets: self._cframe.destroy_widget(rtextwidget) self._rtextwidgets = [] # Position the titlebar & exprline (x1, y1, x2, y2) = self._stacklabel.bbox() y = y2 - y1 + 10 self._canvas.coords(self._titlebar, -5000, 0, 5000, y - 4) self._canvas.coords(self._exprline, 0, y * 2 - 10, 5000, y * 2 - 10) # Position the titlebar labels.. (x1, y1, x2, y2) = self._stacklabel.bbox() self._stacklabel.move(5 - x1, 3 - y1) (x1, y1, x2, y2) = self._rtextlabel.bbox() self._rtextlabel.move(cx2 - x2 - 5, 3 - y1) # Draw the stack. stackx = 5 for tok in self._parser.stack(): if isinstance(tok, Tree): attribs = { 'tree_color': '#4080a0', 'tree_width': 2, 'node_font': self._boldfont, 'node_color': '#006060', 'leaf_color': '#006060', 'leaf_font': self._font } widget = tree_to_treesegment(self._canvas, tok, **attribs) widget.label()['color'] = '#000000' else: widget = TextWidget(self._canvas, tok, color='#000000', font=self._font) widget.bind_click(self._popup_reduce) self._stackwidgets.append(widget) self._cframe.add_widget(widget, stackx, y) stackx = widget.bbox()[2] + 10 # Draw the remaining text. rtextwidth = 0 for tok in self._parser.remaining_text(): widget = TextWidget(self._canvas, tok, color='#000000', font=self._font) self._rtextwidgets.append(widget) self._cframe.add_widget(widget, rtextwidth, y) rtextwidth = widget.bbox()[2] + 4 # Allow enough room to shift the next token (for animations) if len(self._rtextwidgets) > 0: stackx += self._rtextwidgets[0].width() # Move the remaining text to the correct location (keep it # right-justified, when possible); and move the remaining text # label, if necessary. stackx = max(stackx, self._stacklabel.width() + 25) rlabelwidth = self._rtextlabel.width() + 10 if stackx >= cx2 - max(rtextwidth, rlabelwidth): cx2 = stackx + max(rtextwidth, rlabelwidth) for rtextwidget in self._rtextwidgets: rtextwidget.move(4 + cx2 - rtextwidth, 0) self._rtextlabel.move(cx2 - self._rtextlabel.bbox()[2] - 5, 0) midx = (stackx + cx2 - max(rtextwidth, rlabelwidth)) / 2 self._canvas.coords(self._stacktop, midx, 0, midx, 5000) (x1, y1, x2, y2) = self._stacklabel.bbox() # Set up binding to allow them to shift a token by dragging it. if len(self._rtextwidgets) > 0: def drag_shift(widget, midx=midx, self=self): if widget.bbox()[0] < midx: self.shift() else: self._redraw() self._rtextwidgets[0].bind_drag(drag_shift) self._rtextwidgets[0].bind_click(self.shift) # Draw the stack top. self._highlight_productions() def _draw_stack_top(self, widget): # hack.. midx = widget.bbox()[2] + 50 self._canvas.coords(self._stacktop, midx, 0, midx, 5000) def _highlight_productions(self): # Highlight the productions that can be reduced. self._prodlist.selection_clear(0, 'end') for prod in self._parser.reducible_productions(): index = self._productions.index(prod) self._prodlist.selection_set(index) ######################################### ## Button Callbacks ######################################### def destroy(self, *e): if self._top is None: return self._top.destroy() self._top = None def reset(self, *e): self._parser.initialize(self._sent) self._lastoper1['text'] = 'Reset App' self._lastoper2['text'] = '' self._redraw() def step(self, *e): if self.reduce(): return True elif self.shift(): return True else: if list(self._parser.parses()): self._lastoper1['text'] = 'Finished:' self._lastoper2['text'] = 'Success' else: self._lastoper1['text'] = 'Finished:' self._lastoper2['text'] = 'Failure' def shift(self, *e): if self._animating_lock: return if self._parser.shift(): tok = self._parser.stack()[-1] self._lastoper1['text'] = 'Shift:' self._lastoper2['text'] = '%r' % tok if self._animate.get(): self._animate_shift() else: self._redraw() return True return False def reduce(self, *e): if self._animating_lock: return production = self._parser.reduce() if production: self._lastoper1['text'] = 'Reduce:' self._lastoper2['text'] = '%s' % production if self._animate.get(): self._animate_reduce() else: self._redraw() return production def undo(self, *e): if self._animating_lock: return if self._parser.undo(): self._redraw() def postscript(self, *e): self._cframe.print_to_file() def mainloop(self, *args, **kwargs): """ Enter the Tkinter mainloop. This function must be called if this demo is created from a non-interactive program (e.g. from a secript); otherwise, the demo will close as soon as the script completes. """ if in_idle(): return self._top.mainloop(*args, **kwargs) ######################################### ## Menubar callbacks ######################################### def resize(self, size=None): if size is not None: self._size.set(size) size = self._size.get() self._font.configure(size=-(abs(size))) self._boldfont.configure(size=-(abs(size))) self._sysfont.configure(size=-(abs(size))) #self._stacklabel['font'] = ('helvetica', -size-4, 'bold') #self._rtextlabel['font'] = ('helvetica', -size-4, 'bold') #self._lastoper_label['font'] = ('helvetica', -size) #self._lastoper1['font'] = ('helvetica', -size) #self._lastoper2['font'] = ('helvetica', -size) #self._prodlist['font'] = ('helvetica', -size) #self._prodlist_label['font'] = ('helvetica', -size-2, 'bold') self._redraw() def help(self, *e): # The default font's not very legible; try using 'fixed' instead. try: ShowText(self._top, 'Help: Shift-Reduce Parser Application', (__doc__ or '').strip(), width=75, font='fixed') except: ShowText(self._top, 'Help: Shift-Reduce Parser Application', (__doc__ or '').strip(), width=75) def about(self, *e): ABOUT = ("NLTK Shift-Reduce Parser Application\n" + "Written by Edward Loper") TITLE = 'About: Shift-Reduce Parser Application' try: from tkinter.messagebox import Message Message(message=ABOUT, title=TITLE).show() except: ShowText(self._top, TITLE, ABOUT) def edit_grammar(self, *e): CFGEditor(self._top, self._parser.grammar(), self.set_grammar) def set_grammar(self, grammar): self._parser.set_grammar(grammar) self._productions = list(grammar.productions()) self._prodlist.delete(0, 'end') for production in self._productions: self._prodlist.insert('end', (' %s' % production)) def edit_sentence(self, *e): sentence = " ".join(self._sent) title = 'Edit Text' instr = 'Enter a new sentence to parse.' EntryDialog(self._top, sentence, instr, self.set_sentence, title) def set_sentence(self, sent): self._sent = sent.split() #[XX] use tagged? self.reset() ######################################### ## Reduce Production Selection ######################################### def _toggle_grammar(self, *e): if self._show_grammar.get(): self._prodframe.pack(fill='both', side='left', padx=2, after=self._feedbackframe) self._lastoper1['text'] = 'Show Grammar' else: self._prodframe.pack_forget() self._lastoper1['text'] = 'Hide Grammar' self._lastoper2['text'] = '' def _prodlist_select(self, event): selection = self._prodlist.curselection() if len(selection) != 1: return index = int(selection[0]) production = self._parser.reduce(self._productions[index]) if production: self._lastoper1['text'] = 'Reduce:' self._lastoper2['text'] = '%s' % production if self._animate.get(): self._animate_reduce() else: self._redraw() else: # Reset the production selections. self._prodlist.selection_clear(0, 'end') for prod in self._parser.reducible_productions(): index = self._productions.index(prod) self._prodlist.selection_set(index) def _popup_reduce(self, widget): # Remove old commands. productions = self._parser.reducible_productions() if len(productions) == 0: return self._reduce_menu.delete(0, 'end') for production in productions: self._reduce_menu.add_command(label=str(production), command=self.reduce) self._reduce_menu.post(self._canvas.winfo_pointerx(), self._canvas.winfo_pointery()) ######################################### ## Animations ######################################### def _animate_shift(self): # What widget are we shifting? widget = self._rtextwidgets[0] # Where are we shifting from & to? right = widget.bbox()[0] if len(self._stackwidgets) == 0: left = 5 else: left = self._stackwidgets[-1].bbox()[2] + 10 # Start animating. dt = self._animate.get() dx = (left - right) * 1.0 / dt self._animate_shift_frame(dt, widget, dx) def _animate_shift_frame(self, frame, widget, dx): if frame > 0: self._animating_lock = 1 widget.move(dx, 0) self._top.after(10, self._animate_shift_frame, frame - 1, widget, dx) else: # but: stacktop?? # Shift the widget to the stack. del self._rtextwidgets[0] self._stackwidgets.append(widget) self._animating_lock = 0 # Display the available productions. self._draw_stack_top(widget) self._highlight_productions() def _animate_reduce(self): # What widgets are we shifting? numwidgets = len(self._parser.stack()[-1]) # number of children widgets = self._stackwidgets[-numwidgets:] # How far are we moving? if isinstance(widgets[0], TreeSegmentWidget): ydist = 15 + widgets[0].label().height() else: ydist = 15 + widgets[0].height() # Start animating. dt = self._animate.get() dy = ydist * 2.0 / dt self._animate_reduce_frame(dt / 2, widgets, dy) def _animate_reduce_frame(self, frame, widgets, dy): if frame > 0: self._animating_lock = 1 for widget in widgets: widget.move(0, dy) self._top.after(10, self._animate_reduce_frame, frame - 1, widgets, dy) else: del self._stackwidgets[-len(widgets):] for widget in widgets: self._cframe.remove_widget(widget) tok = self._parser.stack()[-1] if not isinstance(tok, Tree): raise ValueError() label = TextWidget(self._canvas, str(tok.label()), color='#006060', font=self._boldfont) widget = TreeSegmentWidget(self._canvas, label, widgets, width=2) (x1, y1, x2, y2) = self._stacklabel.bbox() y = y2 - y1 + 10 if not self._stackwidgets: x = 5 else: x = self._stackwidgets[-1].bbox()[2] + 10 self._cframe.add_widget(widget, x, y) self._stackwidgets.append(widget) # Display the available productions. self._draw_stack_top(widget) self._highlight_productions() # # Delete the old widgets.. # del self._stackwidgets[-len(widgets):] # for widget in widgets: # self._cframe.destroy_widget(widget) # # # Make a new one. # tok = self._parser.stack()[-1] # if isinstance(tok, Tree): # attribs = {'tree_color': '#4080a0', 'tree_width': 2, # 'node_font': bold, 'node_color': '#006060', # 'leaf_color': '#006060', 'leaf_font':self._font} # widget = tree_to_treesegment(self._canvas, tok.type(), # **attribs) # widget.node()['color'] = '#000000' # else: # widget = TextWidget(self._canvas, tok.type(), # color='#000000', font=self._font) # widget.bind_click(self._popup_reduce) # (x1, y1, x2, y2) = self._stacklabel.bbox() # y = y2-y1+10 # if not self._stackwidgets: x = 5 # else: x = self._stackwidgets[-1].bbox()[2] + 10 # self._cframe.add_widget(widget, x, y) # self._stackwidgets.append(widget) #self._redraw() self._animating_lock = 0 ######################################### ## Hovering. ######################################### def _highlight_hover(self, event): # What production are we hovering over? index = self._prodlist.nearest(event.y) if self._hover == index: return # Clear any previous hover highlighting. self._clear_hover() # If the production corresponds to an available reduction, # highlight the stack. selection = [int(s) for s in self._prodlist.curselection()] if index in selection: rhslen = len(self._productions[index].rhs()) for stackwidget in self._stackwidgets[-rhslen:]: if isinstance(stackwidget, TreeSegmentWidget): stackwidget.label()['color'] = '#00a000' else: stackwidget['color'] = '#00a000' # Remember what production we're hovering over. self._hover = index def _clear_hover(self, *event): # Clear any previous hover highlighting. if self._hover == -1: return self._hover = -1 for stackwidget in self._stackwidgets: if isinstance(stackwidget, TreeSegmentWidget): stackwidget.label()['color'] = 'black' else: stackwidget['color'] = 'black'
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