Exemple #1
0
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)
Exemple #2
0
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)
Exemple #3
0
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)
Exemple #4
0
 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)
Exemple #5
0
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")
Exemple #6
0
    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)
Exemple #7
0
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)
Exemple #8
0
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"
              ))
Exemple #9
0
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)
Exemple #10
0
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)
Exemple #12
0
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()
Exemple #13
0
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
Exemple #14
0
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")
Exemple #15
0
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())
Exemple #17
0
def _delete_menu_options(menu: Menu) -> None:
    """Deletes all items/commands from the Menu"""
    start, end = 0, "end"
    menu.delete(start, end)
Exemple #18
0
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)
Exemple #19
0
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)
Exemple #20
0
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'
Exemple #22
0
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
Exemple #24
0
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'
Exemple #26
0
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