Beispiel #1
0
def create_protocols_tree():
    """
    创建协议导航树
    :return: 协议导航树
    """
    protocols_tree.heading('#0', text='选择网络协议', anchor='w')
    # 参数:parent, index, iid=None, **kw (父节点,插入的位置,id,显示出的文本)
    # 应用层
    applicatoin_layer_tree_entry = protocols_tree.insert("", 0, "应用层", text="应用层")  # ""表示父节点是根
    http_packet_tree_entry = protocols_tree.insert(applicatoin_layer_tree_entry, 1, "HTTP包", text="HTTP包")
    dns_packet_tree_entry = protocols_tree.insert(applicatoin_layer_tree_entry, 1, "DNS包", text="DNS包")
    # 传输层
    transfer_layer_tree_entry = protocols_tree.insert("", 1, "传输层", text="传输层")
    tcp_packet_tree_entry = protocols_tree.insert(transfer_layer_tree_entry, 0, "TCP包", text="TCP包")
    upd_packet_tree_entry = protocols_tree.insert(transfer_layer_tree_entry, 1, "UDP包", text="UDP包")
    # 网络层
    ip_layer_tree_entry = protocols_tree.insert("", 2, "网络层", text="网络层")
    ip_packet_tree_entry = protocols_tree.insert(ip_layer_tree_entry, 0, "IP包", text="IP包")
    icmp_packet_tree_entry = protocols_tree.insert(ip_layer_tree_entry, 1, "ICMP包", text="ICMP包")
    arp_packet_tree_entry = protocols_tree.insert(ip_layer_tree_entry, 2, "ARP包", text="ARP包")
    # 网络接入层
    ether_layer_tree_entry = protocols_tree.insert("", 3, "网络接入层", text="网络接入层")
    mac_frame_tree_entry = protocols_tree.insert(ether_layer_tree_entry, 1, "MAC帧", text="MAC帧")
    protocols_tree.bind('<<TreeviewSelect>>', on_click_protocols_tree)
    style = Style(tk)
    # get disabled entry colors
    disabled_bg = style.lookup("TEntry", "fieldbackground", ("disabled",))
    style.map("Treeview",
              fieldbackground=[("disabled", disabled_bg)],
              foreground=[("disabled", "gray")],
              background=[("disabled", disabled_bg)])
    protocols_tree.pack()
    return protocols_tree
Beispiel #2
0
 def createprotocolsTree(self):
     """
     生成协议导航树
     :return:
     """
     self.protocolsTree.heading('#0', text='选择发送报文类型', anchor='w')
     # 参数:parent, index, iid=NOne, **kw(父节点,插入的位置,id,显示的文本)
     # 应用层
     applicatoin_layer_tree_entry = self.protocolsTree.insert('',
                                                              0,
                                                              "应用层",
                                                              text="应用层")
     dns_packet_tree_entry = self.protocolsTree.insert(
         applicatoin_layer_tree_entry, 1, "DNS数据包", text="DNS数据包")
     # 传输层
     transfer_layer_tree_entry = self.protocolsTree.insert('',
                                                           1,
                                                           "传输层",
                                                           text="传输层")
     tcp_packet_tree_entry = self.protocolsTree.insert(
         transfer_layer_tree_entry, 0, "TCP数据包", text="TCP数据包")
     udp_packet_tree_entry = self.protocolsTree.insert(
         transfer_layer_tree_entry, 1, "UDP数据包", text="UDP数据包")
     # 网络层协议
     ip_layer_tree_entry = self.protocolsTree.insert('',
                                                     2,
                                                     "网络层",
                                                     text="网络层")
     arp_packet_tree_entry = self.protocolsTree.insert(ip_layer_tree_entry,
                                                       0,
                                                       "ARP数据包",
                                                       text="ARP数据包")
     ip_packet_tree_entry = self.protocolsTree.insert(ip_layer_tree_entry,
                                                      1,
                                                      "IP数据包",
                                                      text="IP数据包")
     icmp_packet_tree_entry = self.protocolsTree.insert(ip_layer_tree_entry,
                                                        2,
                                                        "ICMP报文",
                                                        text="ICMP报文")
     # 网络接口层协议
     ether_layer_tree_entry = self.protocolsTree.insert('',
                                                        3,
                                                        "网络接口层",
                                                        text="网络接口层")
     mac_frame_tree_entry = self.protocolsTree.insert(
         ether_layer_tree_entry, 1, "以太网MAC协议", text="以太网MAC协议")
     self.protocolsTree.bind('<<TreeviewSelect>>',
                             self.on_click_protocols_tree)
     style = Style(self)
     # get disabled entry colors
     disabled_bg = style.lookup("TEntry", "fieldbackground", ("disabled", ))
     style.map("Treeview",
               fieldbackground=[("disabled", disabled_bg)],
               foreground=[("disabled", "gray")],
               background=[("disabled", disabled_bg)])
     self.protocolsTree.pack()
     return self.protocolsTree
Beispiel #3
0
    def __init__(self, master, font, symbols, **kwargs):
        Toplevel.__init__(self, master, **kwargs)
        self.title("Symbols")
        self.grab_set()
        self.resizable(False, False)
        self.columnconfigure(0, weight=1)

        style = Style(self)
        self.activebg = style.lookup("TEntry", "selectbackground", ("focus", ))

        self.text_to_insert = ""

        l = len(symbols)
        self.canvas = Canvas(self,
                             background="white",
                             width=240,
                             height=(l // 12 + 1) * 20)
        self.canvas.grid(row=0, column=0, columnspan=2, sticky="eswn")

        for i, s in enumerate(symbols):
            x = i % 12
            y = i // 12
            self.canvas.create_rectangle(x * 20,
                                         y * 20, (x + 1) * 20, (y + 1) * 20,
                                         activefill=self.activebg,
                                         fill="white",
                                         width=0,
                                         tags="square")
            self.canvas.create_text(x * 20 + 10,
                                    y * 20 + 10,
                                    text=s,
                                    activefill="white",
                                    font="%s 11" % font,
                                    tags="char")

        self.canvas.tag_bind("square", "<Button-1>", self.add_square)
        self.canvas.tag_bind("square", "<Enter>", self.enter_square)
        self.canvas.tag_bind("square", "<Leave>", self.leave_square)
        self.canvas.tag_bind("char", "<Button-1>", self.add_char)
        self.canvas.tag_bind("char", "<Enter>", self.enter_char)
        self.canvas.tag_bind("char", "<Leave>", self.leave_char)

        self.entry = Entry(self)
        self.entry.grid(row=1, column=0, sticky="ew")
        self.entry.focus_set()
        self.entry.bind("<Return>", self.ok)

        Button(self,
               text=_("Insert"),
               width=len(_("Insert")) + 1,
               command=self.ok).grid(row=1, column=1)
Beispiel #4
0
    def add_column_headers(self, lab_frame):
        '''Add the labels for the columns.  This needs to be in
        sync with the DaxModelParameter.render_ui() method.
        '''
        row = self.get_row()

        stt = Style()
        lfont = stt.lookup("TLabel", "font")
        basefont = font.nametofont(lfont)
        stt.configure("Hdr.TLabel",
                      font=(basefont.cget("family"), basefont.cget("size"),
                            "bold underline"))

        cols = ["Parameter", "Value", "Frozen?", "Min", "Max", "Units"]
        for col, txt in enumerate(cols):
            label = Label(lab_frame, text=txt, style="Hdr.TLabel")
            label.grid(row=row, column=col)
        self.next_row()
Beispiel #5
0
    def __init__(self, master):
        Toplevel.__init__(self, master, class_=APP_NAME)
        self.title(_("Settings"))
        self.grab_set()
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)
        self.rowconfigure(0, weight=1)
        self.resizable(True, True)
        self.minsize(470, 574)

        style = Style(self)
        self._bg = style.lookup('TFrame', 'background')

        self.notebook = Notebook(self)
        self._validate = self.register(self._validate_entry_nb)

        self.img_color = PhotoImage(master=self, file=IM_COLOR)

        self.lang = StringVar(self,
                              LANGUAGES[CONFIG.get("General", "language")])
        self.gui = StringVar(self,
                             CONFIG.get("General", "trayicon").capitalize())

        self._init_general()
        self._init_widget()

        self.notebook.grid(sticky='ewsn', row=0, column=0, columnspan=2)
        Button(self, text=_('Ok'), command=self.ok).grid(row=1,
                                                         column=0,
                                                         sticky='e',
                                                         padx=4,
                                                         pady=10)
        Button(self, text=_('Cancel'), command=self.destroy).grid(row=1,
                                                                  column=1,
                                                                  sticky='w',
                                                                  padx=4,
                                                                  pady=10)
Beispiel #6
0
    def __init__(self, master, app, **kwargs):
        """Create category manager."""
        Frame.__init__(self, master, padding=4, **kwargs)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)

        self.app = app

        self.style = Style(self)
        self.style.theme_use("clam")

        self.im_plus = PhotoImage(file=IM_PLUS)
        self.im_delete = PhotoImage(file=IM_DELETE)

        # --- Default category
        self.frame_def_cat = Frame(self)
        self.default_category = StringVar(
            self.frame_def_cat,
            CONFIG.get("General", "default_category").capitalize())
        Label(self.frame_def_cat,
              text=_("Default category ")).grid(row=0,
                                                column=0,
                                                sticky="e",
                                                padx=(4, 0))
        self.categories = CONFIG.options("Categories")
        self.categories.sort()
        categories = [cat.capitalize() for cat in self.categories]
        self.def_cat_menu = OptionMenu(
            self.frame_def_cat, self.default_category,
            CONFIG.get("General", "default_category").capitalize(),
            *categories)
        optionmenu_patch(self.def_cat_menu, self.default_category)
        self.def_cat_menu.grid(row=0, column=1, sticky="w", padx=4, pady=4)

        # --- Category colors, names ...
        style = Style(self)
        style.configure('txt.TFrame', relief='ridge', border=2)
        bg = style.lookup('TFrame', 'background')
        frame = Frame(self, style='txt.TFrame', padding=1)
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)
        txt = Text(frame,
                   width=1,
                   height=1,
                   bg=bg,
                   relief='flat',
                   highlightthickness=0,
                   padx=6,
                   pady=6,
                   cursor='arrow')
        scroll_x = AutoScrollbar(frame, orient='horizontal', command=txt.xview)
        scroll_y = AutoScrollbar(frame, orient='vertical', command=txt.yview)
        txt.configure(xscrollcommand=scroll_x.set, yscrollcommand=scroll_y.set)

        txt.grid(row=0, column=0, sticky='ewns')
        scroll_x.grid(row=1, column=0, sticky='ew')
        scroll_y.grid(row=0, column=1, sticky='ns')

        self.frame_cat = Frame(txt)
        txt.window_create('1.0', window=self.frame_cat)
        txt.configure(state='disabled')
        self.colors = list(COLORS.keys())
        self.colors.sort()
        self.images = []
        self.cat_colors = {}
        self.cat_labels = {}
        self.cat_menus = {}
        self.cat_buttons = {}
        for i, cat in enumerate(self.categories):
            self.cat_labels[cat] = Label(self.frame_cat,
                                         text="%s" % cat.capitalize(),
                                         anchor='e')
            self.cat_labels[cat].grid(row=i + 2, column=0, sticky="ew", padx=2)
            self.cat_labels[cat].bind('<Double-Button-1>', self.change_name)
            self.cat_colors[cat] = StringVar(self)
            color = CONFIG.get("Categories", cat)
            self.cat_menus[cat] = OptionMenu(self.frame_cat,
                                             self.cat_colors[cat],
                                             INV_COLORS[color],
                                             *self.colors,
                                             command=lambda color, c=cat: self.
                                             change_menubutton_color(color, c),
                                             style="%s.TMenubutton" % cat)
            optionmenu_patch(self.cat_menus[cat], self.cat_colors[cat])
            self.style.configure("%s.TMenubutton" % cat, background=color)
            self.cat_menus[cat].grid(row=i + 2,
                                     column=1,
                                     sticky="w",
                                     padx=4,
                                     pady=4)
            self.cat_buttons[cat] = Button(
                self.frame_cat,
                image=self.im_delete,
                padding=0,
                command=lambda c=cat: self.del_cat(c))
            self.cat_buttons[cat].grid(row=i + 2,
                                       column=2,
                                       padx=4,
                                       pady=4,
                                       sticky='ns')

        if len(self.categories) == 1:
            self.cat_buttons[self.categories[0]].configure(state="disabled")

        # --- placement
        self.frame_def_cat.grid(row=0, column=0, sticky="eswn", pady=4)
        frame.grid(row=1, column=0, sticky="eswn")
        Button(self, image=self.im_plus, command=self.add_cat).grid(row=2,
                                                                    column=0,
                                                                    sticky='w',
                                                                    pady=8)
Beispiel #7
0
    def __init__(self, MainWin):
        """Inits Tkinter window of GridGUI."""
        
        self.root = MainWin
        #MainWin.geometry("800x600") #You want the size of the app to be 500x500
        MainWin.geometry( '+10+30' )
        
        try:
            style = Style(self.root)
            if "win" == platform[:3]:
                style.theme_use('vista')
            elif "darwin" in platform:
                style.theme_use('clam')
            else:
                style.theme_use('clam')
            bg = style.lookup("TLabel", "background")
            self.root.configure(bg=bg)
        except:
            print("OOPS... failed to set style.theme_use... Let's press on.")
        
        self.MainWin = MainWin
        
        MainWin.protocol('WM_DELETE_WINDOW', self.cleanupOnQuit)
        MainWin.allow_subWindows_to_close = 0
        
        self.add_menu_to_MainWin()
        
        topFrame = Frame( MainWin ) # frame for controls
        #topFrame = tx.ScrolledWindow( MainWin )
        
        frame1 = LabelFrame(topFrame, text="Widgets")
        self.place_widget_selection_listbox( frame1 )
        frame1.pack(anchor=NW, side=LEFT)
        
        frame2 = Frame( topFrame ) # frame for radio buttons
        self.place_gui_definition_controls( frame2, MainWin )
        frame2.pack(anchor=N, side=LEFT)

        self.grid_frame = Frame(topFrame) 
        self.grid_notebook = NotebookGridDes(self, self.grid_frame, MainWin, num_cols=5, num_rows=8)
        self.grid_frame.pack(anchor=N, side=LEFT)
                
        topFrame.pack(fill=BOTH, expand=Y)
        
        # make a Status Bar
        statframe = Frame(MainWin)
        MainWin.statusMessage = StringVar()
        MainWin.statusMessage.set('Welcome to TkGridGUI')
        self.statusbar = Label(statframe, textvariable=MainWin.statusMessage, 
            relief=SUNKEN, anchor=W)
        self.statusbar.pack(anchor=SW, fill=X, side=BOTTOM)
        statframe.pack(anchor=SW, fill=X, side=BOTTOM)
        
        # Initialize some GridGUI parameters
        
        self.current_fileFullName = '' # no file for now
        self.current_filePath = '' # no file for now
        self.current_fileName = '' # no file for now

        self.target_app = TargetTkAppDef( name='myApp')
        self.PreviewWin = None # need to initialize later
        
        self.Listbox_1_Click( 'FromInit' ) # execute selection logic

        
        self.in_reading_mode = False # when True, suppresses some automatic trace actions.
        if len( sys.argv ) == 2:
            fName = sys.argv[1]
            if fName.find('.')<0:
                fName += '.def'
                
            fullpath = os.path.abspath(fName)
            
            if os.path.isfile( fullpath ): # if file exists, read it as a definition file
                self.openFile( fName=fullpath )
            else:
                self.MainWin.statusMessage.set('file "%s" does not exist'%fName)
                
        self.grid_notebook.notebook.bind("<<NotebookTabChanged>>", self.tab_of_notebook_changed)

        self.mouse_location = ''
        self.MainWin.bind("<Enter>", self.onMainWindowEnter)
Beispiel #8
0
class Tooltip(Toplevel):
    """ Tooltip class """
    def __init__(self, parent, **kwargs):
        """
            Create a tooltip with given parent.

            KEYWORD OPTIONS

                title, alpha, padx, pady, font, background, foreground, image, text

        """
        Toplevel.__init__(self, parent)
        if 'title' in kwargs:
            self.title(kwargs['title'])
        self.transient(parent)
        self.attributes('-type', 'tooltip')
        self.attributes('-alpha', kwargs.get('alpha', 0.75))
        self.overrideredirect(True)
        self.configure(padx=kwargs.get('padx', 4))
        self.configure(pady=kwargs.get('pady', 4))

        self.font = Font(self, kwargs.get('font', ''))

        self.style = Style(self)
        if 'background' in kwargs:
            bg = kwargs['background']
            self.configure(background=bg)
            self.style.configure('tooltip.TLabel', background=bg)
        if 'foreground' in kwargs:
            self.style.configure('tooltip.TLabel',
                                 foreground=kwargs['foreground'])

        self.im = kwargs.get('image', None)
        self.label = Label(self,
                           text=kwargs.get('text', ''),
                           image=self.im,
                           style='tooltip.TLabel',
                           font=self.font,
                           wraplength='6c',
                           compound=kwargs.get('compound', 'left'))
        self.label.pack()

    def configure(self, **kwargs):
        if 'text' in kwargs:
            self.label.configure(text=kwargs.pop('text'))
        if 'image' in kwargs:
            self.label.configure(image=kwargs.pop('image'))
        if 'background' in kwargs:
            self.style.configure('tooltip.TLabel',
                                 background=kwargs['background'])
        if 'foreground' in kwargs:
            fg = kwargs.pop('foreground')
            self.style.configure('tooltip.TLabel', foreground=fg)
        if 'alpha' in kwargs:
            self.attributes('-alpha', kwargs.pop('alpha'))
        if 'font' in kwargs:
            font = Font(self, kwargs.pop('font'))
            self.font.configure(**font.actual())
        Toplevel.configure(self, **kwargs)

    def config(self, **kw):
        self.configurere(**kw)

    def cget(self, key):
        if key in ['text', 'image']:
            return self.label.cget(key)
        elif key == 'font':
            return self.font
        elif key == 'alpha':
            return self.attributes('-alpha')
        elif key in ['foreground', 'background']:
            return self.style.lookup('tooltip.TLabel', key)
        else:
            return Toplevel.cget(self, key)
    def __init__(self,
                 parent,
                 length=0,
                 from_=0,
                 to=255,
                 orient='vertical',
                 variable=0,
                 digits=None,
                 tickinterval=None,
                 command=None,
                 style=None,
                 showvalue=True,
                 resolution=1):

        self.from_ = from_
        self.to = to
        self.variable = variable
        self.length = length
        self.command = command
        self.parent = parent

        super().__init__(parent,
                         length=length,
                         from_=from_,
                         to=to,
                         orient=orient,
                         variable=variable,
                         command=command,
                         style=style)

        self.digits = digits
        self.tickinterval = tickinterval
        self.showvalue = showvalue
        self.resolution = resolution

        # set sliderlength
        st = Style(self)
        self.bw_val = bw_val = st.lookup('Vertical.Scale.trough',
                                         'borderwidth')
        self.sliderlength = sliderlength = 32

        if showvalue:
            self.configure(command=self.display_value)

        def_font = font.nametofont('TkDefaultFont')
        # if from_ more than to swap values
        if from_ < to:
            pass
        else:
            from_, to = to, from_

        data = np.arange(from_,
                         (to + 1 if tickinterval >= 1 else to + tickinterval),
                         tickinterval)
        self.data = data = np.round(data, 1)
        range_vals = tuple(data)
        len_rvs = len(range_vals)
        lspace = def_font.metrics('linespace')
        len_rvs = len(range_vals)
        data_size = len_rvs * lspace

        space_size = len_rvs * 3
        sizes = data_size + space_size
        min_len = (sizes if sizes % 50 == 0 else sizes + 50 - sizes % 50)
        self.len_val = len_val = min_len if length < min_len else length
        self.configure(length=len_val)

        self.rel_min = rel_min = (sliderlength / 2 + bw_val) / len_val
        self.rel_max = rel_max = 1 - (sliderlength / 2 - bw_val) / len_val
        if range_vals[-1] == to:
            pass
        else:
            max_rv = range_vals[-1]
            self.mult_y = mult_y = ((max_rv - from_) * rel_max / (to - from_))

        self.bind("<Button-1>", self.resolve)

        self.build(from_, to, rel_min, rel_max, range_vals, len_rvs)
Beispiel #10
0
except ImportError:
    from Tkinter import Tk
    from ttk import Style, Button, Label
from sys import platform
from tkfontchooser import askfont  # pip install tkfontchooser

# create main window
root = Tk()
style = Style(root)
if "win" == platform[:3]:
    style.theme_use('vista')
elif "darwin" in platform:
    style.theme_use('clam')
else:
    style.theme_use('clam')
bg = style.lookup("TLabel", "background")
root.configure(bg=bg)
label = Label(root, text='Chosen font: ')
label.pack(padx=10, pady=(10,4))

def callback():
    # open the font chooser and get the font selected by the user
    font = askfont(root)
    # font is "" if the user has cancelled
    if font:
        # spaces in the family name need to be escaped
        font['family'] = font['family'].replace(' ', '\ ')
        font_str = "%(family)s %(size)i %(weight)s %(slant)s" % font
        if font['underline']:
            font_str += ' underline'
        if font['overstrike']:
Beispiel #11
0
class PomodoroParams(Frame):
    def __init__(self, parent, **options):
        """ créer le Toplevel permettant de modifier les paramètres """
        Frame.__init__(self, parent, **options)

        self.onglets = Notebook(self)
        self.onglets.pack(fill='both', expand=True)
        self.im_color = PhotoImage(master=self, file=IM_COLOR)
        self.im_plus = PhotoImage(master=self, file=IM_ADD)
        self.im_moins = PhotoImage(master=self, file=IM_DEL)

        self.okfct = self.register(only_nb)

        self.style = Style(self)

        self.nb_task = len(CONFIG.options("PomodoroTasks"))

        # --- Général (temps, police et langue)
        self.general = Frame(self.onglets, padding=10)
        self.general.columnconfigure(1, weight=1)
        self.onglets.add(self.general, text=_("General"))

        # --- --- Temps
        Label(self.general, text=_("Times (min)"),
              style='title.TLabel').grid(row=0,
                                         pady=4,
                                         padx=(2, 10),
                                         sticky="w")
        self.time_frame = Frame(self.general)
        self.time_frame.grid(row=0, column=1, sticky="w", padx=4)
        Label(self.time_frame, text=_("Work")).grid(row=0, padx=4, column=0)
        self.travail = Entry(self.time_frame,
                             width=4,
                             justify='center',
                             validatecommand=(self.okfct, '%P'),
                             validate='key')
        self.travail.insert(0, CONFIG.get("Pomodoro", "work_time"))
        self.travail.grid(row=0, column=1, padx=(0, 10))
        Label(self.time_frame, text=_("Break")).grid(row=0, column=2, padx=4)
        self.pause = Entry(self.time_frame,
                           width=4,
                           justify='center',
                           validatecommand=(self.okfct, '%P'),
                           validate='key')
        self.pause.insert(0, CONFIG.get("Pomodoro", "break_time"))
        self.pause.grid(row=0, column=3, padx=(0, 10))
        Label(self.time_frame, text=_("Rest")).grid(row=0, column=4, padx=4)
        self.rest = Entry(self.time_frame,
                          width=4,
                          justify='center',
                          validatecommand=(self.okfct, '%P'),
                          validate='key')
        self.rest.insert(0, CONFIG.get("Pomodoro", "rest_time"))
        self.rest.grid(row=0, column=5)

        Separator(self.general, orient='horizontal').grid(row=1,
                                                          columnspan=2,
                                                          sticky="ew",
                                                          pady=10)

        # --- --- Police
        Label(self.general, text=_("Font"),
              style='title.TLabel').grid(row=2, sticky='nw', padx=(2, 10))
        self.font = FontFrame(self.general,
                              font=CONFIG.get('Pomodoro', 'font'),
                              sample_text="02:17")
        self.font.grid(row=2, column=1, padx=4, sticky='w')

        Separator(self.general, orient='horizontal').grid(row=3,
                                                          columnspan=2,
                                                          sticky="ew",
                                                          pady=10)

        # --- --- Opacity
        self.opacity = OpacityFrame(self.general)
        self.opacity.grid(row=5, columnspan=2, sticky='w', padx=(2, 4), pady=4)

        Separator(self.general, orient='horizontal').grid(row=6,
                                                          columnspan=2,
                                                          sticky="ew",
                                                          pady=10)

        # --- --- Son
        self.sound = SoundFrame(self.general,
                                CONFIG.get("Pomodoro", "beep"),
                                mute=CONFIG.getboolean("Pomodoro", "mute"),
                                label=_("Sound"),
                                style='title.TLabel')
        self.sound.grid(row=7, columnspan=2, sticky='ew', pady=4)

        # --- Couleurs
        self.couleurs = Frame(self.onglets, padding=10)
        self.couleurs.columnconfigure(3, weight=1)
        self.onglets.add(self.couleurs, text=_("Colors"))

        self.bg = ColorFrame(self.couleurs, CONFIG.get("Pomodoro",
                                                       "background"),
                             _("Background"))
        self.work_bg = ColorFrame(self.couleurs,
                                  CONFIG.get("Pomodoro", "work_bg"),
                                  _("Background"))
        self.break_bg = ColorFrame(self.couleurs,
                                   CONFIG.get("Pomodoro", "break_bg"),
                                   _("Background"))
        self.rest_bg = ColorFrame(self.couleurs,
                                  CONFIG.get("Pomodoro", "rest_bg"),
                                  _("Background"))
        self.fg = ColorFrame(self.couleurs, CONFIG.get("Pomodoro",
                                                       "foreground"),
                             _("Foreground"))
        self.work_fg = ColorFrame(self.couleurs,
                                  CONFIG.get("Pomodoro", "work_fg"),
                                  _("Foreground"))
        self.break_fg = ColorFrame(self.couleurs,
                                   CONFIG.get("Pomodoro", "break_fg"),
                                   _("Foreground"))
        self.rest_fg = ColorFrame(self.couleurs,
                                  CONFIG.get("Pomodoro", "rest_fg"),
                                  _("Foreground"))

        Label(self.couleurs, text=_("General"),
              style='title.TLabel').grid(row=0,
                                         column=0,
                                         pady=4,
                                         padx=(2, 10),
                                         sticky="w")
        self.bg.grid(row=0, column=1, sticky='e', padx=8, pady=4)
        self.fg.grid(row=0, column=2, sticky='e', padx=8, pady=4)
        Separator(self.couleurs, orient='horizontal').grid(row=1,
                                                           sticky="ew",
                                                           pady=10,
                                                           columnspan=4)
        Label(self.couleurs, text=_("Work"),
              style='title.TLabel').grid(row=2,
                                         column=0,
                                         pady=4,
                                         padx=(2, 10),
                                         sticky="w")
        self.work_bg.grid(row=2, column=1, sticky='e', padx=8, pady=4)
        self.work_fg.grid(row=2, column=2, sticky='e', padx=8, pady=4)
        Separator(self.couleurs, orient='horizontal').grid(row=3,
                                                           sticky="ew",
                                                           pady=10,
                                                           columnspan=4)
        Label(self.couleurs, text=_("Break"),
              style='title.TLabel').grid(row=4,
                                         column=0,
                                         pady=4,
                                         padx=(2, 10),
                                         sticky="w")
        self.break_bg.grid(row=4, column=1, sticky='e', padx=8, pady=4)
        self.break_fg.grid(row=4, column=2, sticky='e', padx=8, pady=4)
        Separator(self.couleurs, orient='horizontal').grid(row=5,
                                                           sticky="ew",
                                                           pady=10,
                                                           columnspan=4)
        Label(self.couleurs, text=_("Rest"),
              style='title.TLabel').grid(row=6,
                                         column=0,
                                         pady=4,
                                         padx=(2, 10),
                                         sticky="w")
        self.rest_bg.grid(row=6, column=1, sticky='e', padx=8, pady=4)
        self.rest_fg.grid(row=6, column=2, sticky='e', padx=8, pady=4)

        # --- Tasks
        self.stats = Frame(self.onglets, padding=10)
        self.stats.columnconfigure(0, weight=1)
        self.stats.rowconfigure(2, weight=1)
        self.onglets.add(self.stats, text=_("Tasks"))
        # graph legend
        legend_frame = Frame(self.stats)
        Label(legend_frame,
              style='title.TLabel',
              text=_('Maximum number of rows in the legend')).pack(side='left')
        self.legend_row_nb = Entry(legend_frame,
                                   width=4,
                                   justify='center',
                                   validatecommand=(self.okfct, '%P'),
                                   validate='key')
        self.legend_row_nb.insert(
            0, CONFIG.get('Pomodoro', 'legend_max_height', fallback='6'))
        self.legend_row_nb.pack(side='left', padx=4)

        # task colors
        can = Canvas(self.stats,
                     bg=self.style.lookup('TFrame', 'background'),
                     highlightthickness=0,
                     width=1,
                     relief='flat')
        scroll = AutoScrollbar(self.stats,
                               orient='vertical',
                               command=can.yview)
        can.configure(yscrollcommand=scroll.set)
        self.task_frame = Frame(can)
        can.create_window(0, 0, anchor='nw', window=self.task_frame)

        tasks = CONFIG.options("PomodoroTasks")
        tasks.sort()
        cmap = [CONFIG.get("PomodoroTasks", task) for task in tasks]
        self.tasks = {}
        self._tasks_btns = {}
        for i, (coul, task) in enumerate(zip(cmap, tasks)):
            self.tasks[task] = ColorFrame(self.task_frame, coul,
                                          task.capitalize())
            self.tasks[task].grid(row=i, column=0, sticky='e', padx=4, pady=4)
            b = Button(self.task_frame,
                       image=self.im_moins,
                       padding=2,
                       command=lambda t=task: self.del_task(t))
            b.grid(row=i, column=1, sticky='w', padx=4, pady=4)
            self._tasks_btns[task] = b
        if len(tasks) == 1:
            self._tasks_btns[tasks[0]].state(['disabled'])

        legend_frame.grid(row=0, columnspan=2, sticky='w', pady=4)
        Label(self.stats,
              text=_('Colors in the statistic graph'),
              style='title.TLabel').grid(row=1, column=0, sticky='w', pady=4)
        can.grid(row=2, column=0, sticky='ewns')
        scroll.grid(row=2, column=1, sticky='ns')
        Button(self.stats, image=self.im_plus,
               command=self.add_task).grid(row=3, column=0, sticky='w')

        self.update_idletasks()
        can.configure(width=self.task_frame.winfo_reqwidth())
        can.configure(scrollregion=can.bbox('all'))
        can.bind('<4>', lambda e: self._scroll(e, -1))
        can.bind('<5>', lambda e: self._scroll(e, 1))
        self.task_frame.bind(
            '<Configure>',
            lambda e: can.configure(scrollregion=can.bbox('all')))

    def _scroll(self, event, delta):
        if event.widget.yview() != (0, 1):
            event.widget.yview_scroll(delta, 'units')

    def choix_couleur(self, type_mode):
        """ sélection de la couleur du fond/texte pour chaque mode (travail/pause/repos) """
        coul = askcolor(self.style.lookup(type_mode + ".TButton",
                                          'background'),
                        parent=self)
        if coul:
            self.style.configure(type_mode + ".TButton", background=coul)

    def coul_stat(self, i):
        """ choix des couleurs pour l'affichage des stats """
        coul = askcolor(self.style.lookup("t%i.TButton" % i, "background"),
                        parent=self)
        if coul:
            self.style.configure("t%i.TButton" % i, background=coul)

    def valide(self):
        """Update config and return whether the pomodor timer should be stopped."""
        old_tpsw = CONFIG.getint("Pomodoro", "work_time")
        old_tpsp = CONFIG.getint("Pomodoro", "break_time")
        old_tpsr = CONFIG.getint("Pomodoro", "rest_time")
        try:
            tpsw = int(self.travail.get())
        except ValueError:  # empty entry
            tpsw = 0
        if tpsw == 0:
            tpsw = old_tpsw
        try:
            tpsp = int(self.pause.get())
        except ValueError:
            tpsp = 0
        if tpsp == 0:
            tpsp = old_tpsp
        try:
            tpsr = int(self.rest.get())
        except ValueError:
            tpsr = 0
        if tpsr == 0:
            tpsr = old_tpsr

        sound, mute = self.sound.get()
        font_prop = self.font.get_font()
        font = "{} {}".format(font_prop['family'].replace(' ', '\ '),
                              font_prop['size'])
        try:
            legend_rows = int(self.legend_row_nb.get())
        except ValueError:
            legend_rows = 0
        if legend_rows == 0:
            legend_rows = CONFIG.getint("Pomodoro", "legend_max_height")

        if not os.path.exists(sound):
            showerror(
                _("Error"),
                _("The file {filepath} does not exists, the old file will be used."
                  ).format(filepath=sound))
            sound = CONFIG.get("Pomodoro", "beep")

        CONFIG.set("Pomodoro", "alpha", str(self.opacity.get_opacity()))
        CONFIG.set("Pomodoro", "font", font)
        CONFIG.set("Pomodoro", "background", self.bg.get_color())
        CONFIG.set("Pomodoro", "foreground", self.fg.get_color())
        CONFIG.set("Pomodoro", "work_time", str(tpsw))
        CONFIG.set("Pomodoro", "work_bg", self.work_bg.get_color())
        CONFIG.set("Pomodoro", "work_fg", self.work_fg.get_color())
        CONFIG.set("Pomodoro", "break_time", str(tpsp))
        CONFIG.set("Pomodoro", "break_bg", self.break_bg.get_color())
        CONFIG.set("Pomodoro", "break_fg", self.break_fg.get_color())
        CONFIG.set("Pomodoro", "rest_time", str(tpsr))
        CONFIG.set("Pomodoro", "rest_bg", self.rest_bg.get_color())
        CONFIG.set("Pomodoro", "rest_fg", self.rest_fg.get_color())
        CONFIG.set("Pomodoro", "beep", sound)
        CONFIG.set("Pomodoro", "mute", str(mute))
        CONFIG.set("Pomodoro", "legend_max_height", str(legend_rows))
        for task, widget in self.tasks.items():
            CONFIG.set("PomodoroTasks", task, widget.get_color())

        return old_tpsw != tpsw or old_tpsp != tpsp or old_tpsr != old_tpsr

    def del_task(self, task):
        """ Suppression de tâches """
        rep = askyesno(
            _("Confirmation"),
            _("Are you sure you want to delete the task {task}? This action cannot be undone."
              ).format(task=task.capitalize()))
        if rep:
            CONFIG.remove_option("PomodoroTasks", task)
            # remove stats
            db = sqlite3.connect(PATH_STATS)
            cursor = db.cursor()
            try:
                cursor.execute('DROP TABLE {}'.format(
                    scrub(task.lower().replace(' ', '_'))))
                db.commit()
            except sqlite3.OperationalError:
                pass  # no stats yet
            db.close()
            self.tasks[task].destroy()
            self._tasks_btns[task].destroy()
            del self.tasks[task]
            del self._tasks_btns[task]

            if len(CONFIG.options("PomodoroTasks")) == 1:
                CONFIG.set("PomodoroTasks", _("Work"), CMAP[0])
                self._tasks_btns[CONFIG.options("PomodoroTasks")[0]].state(
                    ['disabled'])
            save_config()

    def add_task(self):
        def ajoute(event=None):
            task = nom.get().lower().strip()
            if task in self.tasks:
                showerror(
                    _("Error"),
                    _("The task {task} already exists.").format(task=task),
                    parent=self)

            elif task:
                coul = CMAP[(len(self.tasks) + 1) % len(CMAP)]
                i = self.task_frame.grid_size()[1] + 1
                self.tasks[task] = ColorFrame(self.task_frame, coul,
                                              task.capitalize())
                self.tasks[task].grid(row=i,
                                      column=0,
                                      sticky='e',
                                      padx=4,
                                      pady=4)
                b = Button(self.task_frame,
                           image=self.im_moins,
                           padding=2,
                           command=lambda t=task: self.del_task(t))
                b.grid(row=i, column=1, sticky='w', padx=4, pady=4)
                self._tasks_btns[task] = b
                self._tasks_btns[CONFIG.options("PomodoroTasks")[0]].state(
                    ['!disabled'])
            top.destroy()

        top = Toplevel(self)
        top.title(_("New task"))
        top.transient(self)
        top.grab_set()
        nom = Entry(top, width=20, justify='center')
        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)
Beispiel #12
0
    def __init__(self,
                 master,
                 columns,
                 data=None,
                 command=None,
                 sort=True,
                 select_mode=None,
                 heading_anchor=CENTER,
                 cell_anchor=W,
                 style=None,
                 height=None,
                 padding=None,
                 adjust_heading_to_content=False,
                 stripped_rows=None,
                 selection_background=None,
                 selection_foreground=None,
                 field_background=None,
                 heading_font=None,
                 heading_background=None,
                 heading_foreground=None,
                 cell_pady=2,
                 cell_background=None,
                 cell_foreground=None,
                 cell_font=None,
                 headers=True):

        self._stripped_rows = stripped_rows

        self._columns = columns

        self._number_of_rows = 0
        self._number_of_columns = len(columns)

        self.row = self.List_Of_Rows(self)
        self.column = self.List_Of_Columns(self)

        s = Style()

        if style is None:
            style_name = "Multicolumn_Listbox%s.Treeview" % self._style_index
            self._style_index += 1
        else:
            style_name = style

        style_map = {}
        if selection_background is not None:
            style_map["background"] = [('selected', selection_background)]

        if selection_foreground is not None:
            style_map["foeground"] = [('selected', selection_foreground)]

        if style_map:
            s.map(style_name, **style_map)

        style_config = {}
        if cell_background is not None:
            style_config["background"] = cell_background

        if cell_foreground is not None:
            style_config["foreground"] = cell_foreground

        if cell_font is None:
            font_name = s.lookup(style_name, "font")
            cell_font = nametofont(font_name)
        else:
            if not isinstance(cell_font, Font):
                if isinstance(cell_font, basestring):
                    cell_font = nametofont(cell_font)
                else:
                    if len(Font) == 1:
                        cell_font = Font(family=cell_font[0])
                    elif len(Font) == 2:
                        cell_font = Font(family=cell_font[0],
                                         size=cell_font[1])

                    elif len(Font) == 3:
                        cell_font = Font(family=cell_font[0],
                                         size=cell_font[1],
                                         weight=cell_font[2])
                    else:
                        raise ValueError(
                            "Not possible more than 3 values for font")

            style_config["font"] = cell_font

        self._cell_font = cell_font

        self._rowheight = cell_font.metrics("linespace") + cell_pady
        style_config["rowheight"] = self._rowheight

        if field_background is not None:
            style_config["fieldbackground"] = field_background

        s.configure(style_name, **style_config)

        heading_style_config = {}
        if heading_font is not None:
            heading_style_config["font"] = heading_font
        if heading_background is not None:
            heading_style_config["background"] = heading_background
        if heading_foreground is not None:
            heading_style_config["foreground"] = heading_foreground

        heading_style_name = style_name + ".Heading"
        s.configure(heading_style_name, **heading_style_config)

        treeview_kwargs = {"style": style_name}

        if height is not None:
            treeview_kwargs["height"] = height

        if padding is not None:
            treeview_kwargs["padding"] = padding

        if headers:
            treeview_kwargs["show"] = "headings"
        else:
            treeview_kwargs["show"] = ""

        if select_mode is not None:
            treeview_kwargs["selectmode"] = select_mode

        self.interior = Treeview(master, columns=columns, **treeview_kwargs)

        if command is not None:
            self._command = command
            self.interior.bind("<<TreeviewSelect>>", self._on_select)

        for i in range(0, self._number_of_columns):

            if sort:
                self.interior.heading(
                    i,
                    text=columns[i],
                    anchor=heading_anchor,
                    command=lambda col=i: self.sort_by(col, descending=False))
            else:
                self.interior.heading(i,
                                      text=columns[i],
                                      anchor=heading_anchor)

            if adjust_heading_to_content:
                self.interior.column(i, width=Font().measure(columns[i]))

            self.interior.column(i, anchor=cell_anchor)

        if data is not None:
            for row in data:
                self.insert_row(row)
Beispiel #13
0
class DefaultThemingEngine(AOSLibrary):
    def __init__(self, manager):
        super().__init__(manager)
        self.manager.registerConfig('theme')
        self.style = Style()
        self.style.configure("Tk", background="#000000", foreground="white")
        self.delayEnable = True

    def enable(self):
        self.manager.addTool('Edit Theme', self.themeList)
        self.loadTheme()

    def disable(self):
        self.manager.removeTool('Edit Theme')

    def themeList(self):
        self.themelistwin = Toplevel()

        Label(self.themelistwin, text="Log Window background:").grid(row=0,
                                                                     column=0)
        self.textbox_bg = Entry(self.themelistwin)
        self.textbox_bg.grid(row=0, column=1)
        self.textbox_bg.insert(0,
                               self.manager.process.log.textbox['background'])

        Label(self.themelistwin, text="Log Window Font Colour:").grid(row=0,
                                                                      column=2)
        self.textbox_fg = Entry(self.themelistwin)
        self.textbox_fg.grid(row=0, column=3)
        self.textbox_fg.insert(0,
                               self.manager.process.log.textbox['foreground'])

        Label(self.themelistwin, text="Buttons background:").grid(row=1,
                                                                  column=0)
        self.button_bg = Entry(self.themelistwin)
        self.button_bg.grid(row=1, column=1)
        self.button_bg.insert(0, self.style.lookup("TButton", "background"))

        Label(self.themelistwin, text="Buttons padding:").grid(row=2, column=0)
        self.button_fbg = Entry(self.themelistwin)
        self.button_fbg.grid(row=2, column=1)
        self.button_fbg.insert(0, self.style.lookup("TButton", "padding"))

        Label(self.themelistwin, text="Buttons foreground:").grid(row=3,
                                                                  column=0)
        self.button_fg = Entry(self.themelistwin)
        self.button_fg.grid(row=3, column=1)
        self.button_fg.insert(0, self.style.lookup("TButton", "foreground"))

        Label(self.themelistwin, text="Buttons relief:").grid(row=4, column=0)
        self.button_rf = Entry(self.themelistwin)
        self.button_rf.grid(row=4, column=1)
        self.button_rf.insert(0, self.style.lookup("TButton", "relief"))

        Button(self.themelistwin, text="Save",
               command=self.saveTheme).grid(row=10, column=0)
        self.themelistwin.bind('<Return>', lambda evt: self.saveTheme())

    def saveTheme(self):
        self.manager.getConfig('theme')._set('theme_outputwin',
                                             self.textbox_bg.get())
        self.manager.getConfig('theme')._set('theme_outputwin_font',
                                             self.textbox_fg.get())
        self.manager.process.log.textbox.configure(bg=self.textbox_bg.get())
        self.manager.process.log.textbox.configure(fg=self.textbox_fg.get())
        self.style.configure("TButton",
                             padding=self.button_fbg.get(),
                             relief=self.button_rf.get(),
                             background=self.button_bg.get(),
                             foreground=self.button_fg.get())

        data = {
            'button': {
                'padding': self.button_fbg.get(),
                'relief': self.button_rf.get(),
                'background': self.button_bg.get(),
                'foreground': self.button_fg.get()
            }
        }
        self.manager.getConfig('theme')._set('theme', data)

    def loadTheme(self):
        tbb = self.manager.getConfig('theme').get(
            'theme_outputwin', self.manager.process.log.textbox['background'])
        tbf = self.manager.getConfig('theme').get(
            'theme_outputwin_font',
            self.manager.process.log.textbox['foreground'])

        self.manager.process.log.textbox.configure(bg=tbb)
        self.manager.process.log.textbox.configure(fg=tbf)

        data = self.manager.getConfig('theme').get('theme', {})
        if data:
            theme_b = data.get('button', None)
            #print(theme_b)
            if theme_b:
                #print(theme_b.get('background'))
                self.style.configure(
                    "TButton",
                    padding=theme_b.get(
                        'padding', self.style.lookup("TButton", "padding")),
                    relief=theme_b.get('relief',
                                       self.style.lookup("TButton", "relief")),
                    background=theme_b.get(
                        'background',
                        self.style.lookup("TButton", "background")),
                    foreground=theme_b.get(
                        'foreground',
                        self.style.lookup("TButton", "foreground")))
Beispiel #14
0
        subprocess.Popen([save_to], shell=True)
    except Exception:
        print("Fichier github_id absent")


if __name__ == '__main__':
    MIN_WIDTH: int = 400
    MIN_HEIGHT: int = 150

    window = ThemedTk(theme='plastik')  # arc, breeze, plastik, clearlooks, black, equilux
    window.title('Utilitaires TMA')
    window.resizable(False, False)

    # Personnalisation Style
    style = Style(window)
    original_font = font.nametofont(style.lookup("TLabel", "font"))
    f = font.Font(**original_font.configure())
    f.configure(weight='bold', underline=0, size=9)
    style.configure('H1.TLabel', font=f)
    original_font = font.nametofont(style.lookup("TButton", "font"))

    f2 = font.Font(**original_font.configure())
    f2.configure(weight='bold', size=9)
    style.configure('H1.TButton', font=f2)

    window.grid_columnconfigure(0, weight=1)
    window.grid_rowconfigure(0, weight=1)
    window.minsize(MIN_WIDTH, MIN_HEIGHT)

    # FRAME Principale
    frame_principale = Frame(window)
Beispiel #15
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
Beispiel #16
0
    def __init__(self, master, event, new=False):
        Toplevel.__init__(self, master)
        self.minsize(410, 402)
        if master.winfo_ismapped():
            self.transient(master)
        self.protocol('WM_DELETE_WINDOW', self.cancel)

        self._only_nb = self.register(only_nb)

        self.event = event
        if new:
            self.title(_('New Event'))
        else:
            self.title(_('Edit Event'))
        self._new = new
        self._task = BooleanVar(self, bool(event['Task']))
        self._whole_day = BooleanVar(self, event['WholeDay'])

        # --- style
        style = Style(self)
        active_bg = style.lookup('TEntry', 'selectbackground', ('focus', ))

        self.alarms = []

        notebook = Notebook(self)
        notebook.pack(fill='both', expand=True)
        Button(self, text=_('Ok'), command=self.ok).pack(pady=(10, 6), padx=4)

        # --- event settings
        frame_event = Frame(notebook)
        notebook.add(frame_event, text=_('Event'), sticky='eswn', padding=4)
        frame_event.columnconfigure(1, weight=1)
        frame_event.rowconfigure(5, weight=1)

        self.img_moins = PhotoImage(master=self, file=IM_DEL)
        self.img_bell = PhotoImage(master=self, file=IM_BELL)
        Label(frame_event, text=_('Summary')).grid(row=0,
                                                   column=0,
                                                   padx=4,
                                                   pady=6,
                                                   sticky='e')
        Label(frame_event, text=_('Place')).grid(row=1,
                                                 column=0,
                                                 padx=4,
                                                 pady=6,
                                                 sticky='e')
        Label(frame_event, text=_('Start')).grid(row=2,
                                                 column=0,
                                                 padx=4,
                                                 pady=6,
                                                 sticky='e')
        self._end_label = Label(frame_event, text=_('End'))
        self._end_label.grid(row=3, column=0, padx=4, pady=6, sticky='e')
        frame_task = Frame(frame_event)
        frame_task.grid(row=4, column=1, padx=4, pady=6, sticky='w')
        Label(frame_event, text=_('Description')).grid(row=5,
                                                       column=0,
                                                       padx=4,
                                                       pady=6,
                                                       sticky='e')
        Label(frame_event, text=_('Category')).grid(row=6,
                                                    column=0,
                                                    padx=4,
                                                    pady=6,
                                                    sticky='e')
        Button(frame_event,
               image=self.img_bell,
               command=self.add_reminder,
               padding=0).grid(row=7, column=0, padx=4, pady=6, sticky='en')

        self.summary = Entry(frame_event, width=35)
        self.summary.insert(0, self.event['Summary'])
        self.summary.grid(row=0, column=1, padx=4, pady=6, sticky='ew')
        self.place = Entry(frame_event, width=35)
        self.place.insert(0, self.event['Place'])
        self.place.grid(row=1, column=1, padx=4, pady=6, sticky='ew')
        frame_start = Frame(frame_event)
        frame_start.grid(row=2, column=1, padx=4, pady=6, sticky='w')
        frame_end = Frame(frame_event)
        frame_end.grid(row=3, column=1, padx=4, pady=6, sticky='w')
        txt_frame = Frame(frame_event,
                          style='txt.TFrame',
                          border=1,
                          relief='sunken')
        self.desc = Text(txt_frame,
                         width=35,
                         height=4,
                         highlightthickness=0,
                         relief='flat',
                         selectbackground=active_bg)
        self.desc.insert('1.0', self.event['Description'])
        self.desc.pack(fill='both', expand=True)
        txt_frame.grid(row=5, column=1, padx=4, pady=6, sticky='ewsn')
        cats = list(CONFIG.options('Categories'))
        width = max([len(cat) for cat in cats])
        self.category = Combobox(frame_event,
                                 width=width + 2,
                                 values=cats,
                                 state='readonly')
        self.category.set(event['Category'])
        self.category.grid(row=6, column=1, padx=4, pady=6, sticky='w')
        self.frame_alarms = Frame(frame_event)
        self.frame_alarms.grid(row=7, column=1, sticky='w')

        # --- *--- task
        Checkbutton(frame_task,
                    text=_('Task'),
                    command=self._change_label,
                    variable=self._task).pack(side='left')

        self.task_progress = Combobox(frame_task,
                                      state='readonly',
                                      width=9,
                                      values=(_('Pending'), _('In Progress'),
                                              _('Completed'), _('Cancelled')))
        self.task_progress.pack(side='left', padx=(8, 4))
        self.in_progress = Combobox(
            frame_task,
            state='readonly',
            width=5,
            values=['{}%'.format(i) for i in range(0, 110, 10)])
        self.in_progress.pack(side='left', padx=4)
        if not event['Task']:
            self.task_progress.set(_('Pending'))
            self.in_progress.set('0%')
        elif '%' in event['Task']:
            self.task_progress.set(_('In Progress'))
            self.in_progress.set(event['Task'])
        else:
            self.task_progress.set(_(event['Task']))
            self.in_progress.set('0%')

        # calendar settings
        prop = {
            op: CONFIG.get('Calendar', op)
            for op in CONFIG.options('Calendar')
        }
        prop['font'] = "Liberation\ Sans 9"
        prop.update(selectforeground='white', selectbackground=active_bg)
        locale = CONFIG.get('General', 'locale')

        # --- *--- start date
        self.start_date = self.event['Start']
        self.start_entry = DateEntry(frame_start,
                                     locale=locale,
                                     width=10,
                                     justify='center',
                                     year=self.start_date.year,
                                     month=self.start_date.month,
                                     day=self.start_date.day,
                                     **prop)

        self.start_hour = Combobox(frame_start,
                                   width=3,
                                   justify='center',
                                   state='readonly',
                                   exportselection=False,
                                   values=['%02d' % i for i in range(24)])
        self.start_hour.set('%02d' % self.start_date.hour)
        self.start_min = Combobox(frame_start,
                                  width=3,
                                  justify='center',
                                  state='readonly',
                                  exportselection=False,
                                  values=['%02d' % i for i in range(0, 60, 5)])
        self.start_min.set('%02d' % self.start_date.minute)
        self.start_entry.pack(side='left', padx=(0, 18))
        self.start_hour.pack(side='left', padx=(4, 0))
        self.start_date = self.start_date.date()
        Label(frame_start, text=':').pack(side='left')
        self.start_min.pack(side='left', padx=(0, 4))
        Checkbutton(frame_start,
                    text=_("whole day"),
                    variable=self._whole_day,
                    command=self._toggle_whole_day).pack(side='left', padx=4)

        # --- *--- end date
        self.end_date = self.event['End']
        self.end_entry = DateEntry(frame_end,
                                   justify='center',
                                   locale=locale,
                                   width=10,
                                   year=self.end_date.year,
                                   month=self.end_date.month,
                                   day=self.end_date.day,
                                   **prop)

        self.end_hour = Combobox(frame_end,
                                 width=3,
                                 justify='center',
                                 state='readonly',
                                 exportselection=False,
                                 values=['%02d' % i for i in range(24)])
        self.end_hour.set('%02d' % self.end_date.hour)
        self.end_min = Combobox(frame_end,
                                width=3,
                                justify='center',
                                state='readonly',
                                exportselection=False,
                                values=['%02d' % i for i in range(0, 60, 5)])
        self.end_min.set('%02d' % self.end_date.minute)
        self.end_entry.pack(side='left', padx=(0, 18))

        self.end_hour.pack(side='left', padx=(4, 0))
        Label(frame_end, text=':').pack(side='left')
        self.end_min.pack(side='left', padx=(0, 4))
        self.end_date = self.end_date.date()

        for date in self.event['Reminders'].values():
            self.add_reminder(date)

        self._toggle_whole_day()

        # --- repetition settings
        frame_rep = Frame(notebook)
        notebook.add(frame_rep, text=_('Repetition'), padding=4, sticky='eswn')
        frame_rep.columnconfigure(0, weight=1)
        frame_rep.columnconfigure(1, weight=1)
        frame_rep.rowconfigure(1, weight=1)
        self._repeat = BooleanVar(self, bool(self.event['Repeat']))
        repeat = {
            'Frequency': 'year',
            'Limit': 'always',
            'NbTimes': 1,
            'EndDate': (datetime.now() + timedelta(days=1)).date(),
            'WeekDays': [self.start_date.isocalendar()[2] - 1]
        }
        repeat.update(self.event['Repeat'])

        self._repeat_freq = StringVar(self, repeat['Frequency'])
        Checkbutton(frame_rep,
                    text=_('Repeat event'),
                    variable=self._repeat,
                    command=self._toggle_rep).grid(row=0,
                                                   column=0,
                                                   columnspan=2,
                                                   padx=4,
                                                   pady=6,
                                                   sticky='w')
        # --- *--- Frequency
        frame_freq = LabelFrame(frame_rep, text=_('Frequency'))
        frame_freq.grid(row=1, column=0, sticky='eswn', padx=(0, 3))
        self._lfreq = Label(frame_freq, text=_('Every:'))
        self._lfreq.grid(row=0, column=0, padx=4, pady=2, sticky='e')

        self._freqs = []
        for i, val in enumerate(['Year', 'Month', 'Week']):
            r = Radiobutton(frame_freq,
                            text=_(val),
                            variable=self._repeat_freq,
                            value=val.lower(),
                            command=self._toggle_wd)
            r.grid(row=i, column=1, padx=4, pady=2, sticky='nw')
            self._freqs.append(r)

        frame_days = Frame(frame_freq)
        frame_days.grid(row=2, column=2, padx=4, pady=2, sticky='nw')
        self._week_days = []
        days = get_day_names("wide", locale=locale)
        days = [days[i] for i in range(7)]
        for day in days:
            ch = Checkbutton(frame_days, text=day)
            ch.pack(anchor='w')
            self._week_days.append(ch)

        for d in repeat['WeekDays']:
            self._week_days[int(d)].state(('selected', ))

        # --- *--- Limit
        frame_lim = LabelFrame(frame_rep, text=_('Limit'))
        frame_lim.grid(row=1, column=1, sticky='eswn', padx=(3, 0))
        frame_lim.grid(row=1, column=1, sticky='eswn', padx=(3, 0))
        self._repeat_lim = StringVar(self, repeat['Limit'])

        # always
        r1 = Radiobutton(frame_lim,
                         text=_('Always'),
                         value='always',
                         variable=self._repeat_lim,
                         command=self._toggle_lim)
        r1.grid(row=0, column=0, sticky='w')
        # until
        r2 = Radiobutton(frame_lim,
                         text=_('Until'),
                         value='until',
                         variable=self._repeat_lim,
                         command=self._toggle_lim)
        r2.grid(row=1, column=0, sticky='w')
        until_date = repeat['EndDate']
        self.until_entry = DateEntry(frame_lim,
                                     width=10,
                                     justify='center',
                                     locale=locale,
                                     year=until_date.year,
                                     month=until_date.month,
                                     day=until_date.day,
                                     **prop)

        self.until_entry.grid(row=1,
                              column=1,
                              columnspan=2,
                              sticky='w',
                              padx=(4, 10),
                              pady=2)

        # after
        r3 = Radiobutton(frame_lim,
                         text=_('After'),
                         value='after',
                         variable=self._repeat_lim,
                         command=self._toggle_lim)
        r3.grid(row=2, column=0, sticky='w')
        frame_after = Frame(frame_lim,
                            style='txt.TFrame',
                            relief='sunken',
                            border=1)
        self.s_after = Spinbox(frame_after,
                               from_=0,
                               to=100,
                               width=3,
                               justify='center',
                               relief='flat',
                               highlightthickness=0,
                               validate='key',
                               validatecommand=(self._only_nb, '%P'),
                               disabledbackground='white')
        self.s_after.pack()
        self.s_after.delete(0, 'end')
        self.s_after.insert(0, str(repeat['NbTimes']))
        frame_after.grid(row=2, column=1, padx=4, pady=2, sticky='w')
        self._llim = Label(frame_lim, text=_('times'))
        self._llim.grid(row=2, column=2, padx=0, pady=2, sticky='w')

        self._rb_lim = [r1, r2, r3]

        self._toggle_rep()
        self._change_label()

        # --- bindings
        self.bind('<Configure>')
        self.task_progress.bind('<<ComboboxSelected>>',
                                self._toggle_in_progress)
        self.start_entry.bind('<<DateEntrySelected>>', self._select_start)
        self.end_entry.bind('<<DateEntrySelected>>', self._select_end)
        self.start_hour.bind("<<ComboboxSelected>>", self._select_start_hour)
        self.start_min.bind("<<ComboboxSelected>>", self._select_start_min)
        self.end_min.bind("<<ComboboxSelected>>", self._select_end_time)
        self.end_hour.bind("<<ComboboxSelected>>", self._select_end_time)
        self.bind_class("TCombobox",
                        "<<ComboboxSelected>>",
                        self.__clear_selection,
                        add=True)

        # self.wait_visibility(self)
        # self.grab_set()
        self.summary.focus_set()
Beispiel #17
0
class Params(Toplevel):

    def __init__(self, parent, **options):
        """ créer le Toplevel permettant de modifier les paramètres """
        Toplevel.__init__(self, parent, **options)
        self.grab_set()
        self.transient(parent)
        self.title(_("Settings"))
        self.resizable(0, 0)

        self.onglets = Notebook(self)
        self.onglets.grid(row=0,column=0,columnspan=2)
        self.im_color = PhotoImage(master=self, file=COLOR)

        self.style = Style(self)
        self.style.theme_use(STYLE)
        self.style.configure('title.TLabel', font='CMU\ Sans\ Serif\ Demi\ Condensed 12')

        self.okfct = self.register(valide_entree_nb)

        self.nb_task = len(CONFIG.options("Tasks"))

        # Général (temps, police et langue)
        self.general = Frame(self.onglets, padding=10)
        self.general.pack(fill="both", expand=True, padx=10, pady=10)
        self.onglets.add(self.general, text=_("General"))

        # Temps
        Label(self.general, text=_("Times:"),
              style='title.TLabel').grid(row=0, pady=4, sticky="w")
        self.time_frame = Frame(self.general)
        self.time_frame.grid(row=1, sticky="ew")
        Label(self.time_frame, text=_("Work: ")).grid(row=0, column=0)
        self.travail = Entry(self.time_frame, width=4, justify='center',
                             validatecommand=(self.okfct, '%d', '%S'),
                             validate='key')
        self.travail.insert(0, CONFIG.get("Work", "time"))
        self.travail.grid(row=0, column=1, padx=(0,10))
        Label(self.time_frame, text=_("Break: ")).grid(row=0, column=2)
        self.pause = Entry(self.time_frame, width=4, justify='center',
                           validatecommand=(self.okfct, '%d', '%S'),
                           validate='key')
        self.pause.insert(0, CONFIG.get("Break", "time"))
        self.pause.grid(row=0, column=3, padx=(0,10))
        Label(self.time_frame, text=_("Rest: ")).grid(row=0, column=4)
        self.rest = Entry(self.time_frame, width=4, justify='center',
                          validatecommand=(self.okfct, '%d', '%S'),
                          validate='key')
        self.rest.insert(0, CONFIG.get("Rest", "time"))
        self.rest.grid(row=0, column=5)

        Separator(self.general,
                  orient='horizontal').grid(row=2, sticky="ew", pady=10)

              # Police
        self.font_frame = Frame(self.general)
        self.font_frame.grid(row=3, pady=4, sticky="ew")
        Label(self.font_frame, text=_("Font:"),
              style='title.TLabel').pack(anchor="n", side="left")
        self.exemple = Label(self.font_frame, text="02:17", anchor="center",
                             font="%s %i" % (CONFIG.get("General", "font"), CONFIG.getint("General", "fontsize")),
                             relief="groove")
        self.exemple.pack(side="right")
        self.font_frame2 = Frame(self.general)
        self.font_frame2.grid(row=4)
        Label(self.font_frame2, text=_("Family: ")).grid(row=0, column=0, sticky="e")
        self.font = Entry(self.font_frame2, justify='center')
        self.font.insert(0, CONFIG.get("General", "font"))
        self.font.grid(row=0, column=1, padx=(0,10), sticky="ew")
        self.font.bind('<FocusOut>', self.actualise_police)
        self.font.bind('<Key-Return>', self.actualise_police, True)
        Label(self.font_frame2, text=_("Size: ")).grid(row=0, column=2, sticky="e")
        self.size = Entry(self.font_frame2, width=4, justify='center',
                          validatecommand=(self.okfct, '%d', '%S'),
                          validate='key')
        self.size.insert(0, CONFIG.getint("General", "fontsize"))
        self.size.grid(row=0, column=3, pady=2, sticky="w")
        self.size.bind('<FocusOut>', self.actualise_police)
        self.size.bind('<Key-Return>', self.actualise_police, True)

        Separator(self.general,
                  orient='horizontal').grid(row=5, sticky="ew", pady=10)

            # Langues
        self.lang_frame = Frame(self.general)
        self.lang_frame.grid(row=6, pady=4, sticky="ew")
        Label(self.lang_frame, text=_("Language:"),
              style='title.TLabel').pack(side="left")
        self.lang = StringVar(self.lang_frame, LANGUES[CONFIG.get("General", "language")])
        b_lang = Menubutton(self.lang_frame, textvariable=self.lang)
        menu = Menu(b_lang, tearoff=False)
        menu.add_radiobutton(label="English", variable=self.lang,
                             value="English", command=self.change_langue)
        menu.add_radiobutton(label="Français", variable=self.lang,
                             value="Français", command=self.change_langue)
        b_lang.configure(menu=menu)
        b_lang.pack(side="right")

        # Son
        self.im_son = PhotoImage(master=self, file=SON)
        self.im_mute = PhotoImage(master=self, file=MUTE)

        self.son = Frame(self.onglets, padding=10)
        self.son.pack(fill="both", expand=True, padx=10, pady=10)
        self.son.columnconfigure(1, weight=1)
        self.onglets.add(self.son, text=_("Sound"))

        Label(self.son, text=_("Sound:"),
              style='title.TLabel').grid(row=0, pady=4, sticky="w")
        self.mute = BooleanVar(self)
        self.mute.set(not CONFIG.get("Sound", "mute"))

        def mute_unmute():
            if self.mute.get():
                self.mute.set(False)
                b_son.configure(image=self.im_son)
            else:
                self.mute.set(True)
                b_son.configure(image=self.im_mute)

        b_son = Button(self.son, command=mute_unmute)
        mute_unmute()
        b_son.grid(row=0, column=1, sticky="e", pady=4)
        self.son_frame = Frame(self.son)
        self.son_frame.grid(row=1, sticky="ew", columnspan=2)
        self.bip = Entry(self.son_frame, justify='center')
        self.bip.insert(0, CONFIG.get("Sound", "beep"))
        self.bip.pack(side="left", fill="both", expand=True)
        Button(self.son_frame, text="...", width=2,
               command=self.choix_son).pack(side="right", padx=(2,0))

        if PL[0] != "w":
            Separator(self.son, orient='horizontal').grid(row=2, columnspan=2,
                                                          sticky="ew", pady=10)
            son_frame2 = Frame(self.son)
            son_frame2.grid(row=3, sticky="ew", columnspan=2)
            Label(son_frame2, text=_("Audio player: "),
                  style='title.TLabel').pack(side="left")
            self.player = Entry(son_frame2, justify='center')
            self.player.insert(0, CONFIG.get("Sound", "player"))
            self.player.pack(side="right", fill="both", expand=True)


        # Couleurs
        self.couleurs = Frame(self.onglets, padding=10)
        self.couleurs.pack(fill="both", expand=True, padx=10, pady=10)
        self.onglets.add(self.couleurs, text=_("Colors"))

        # style des boutons de choix des couleurs
        self.style.configure("fond_w.TButton", background=CONFIG.get("Work", "bg"))
        self.style.configure("fond_p.TButton", background=CONFIG.get("Break", "bg"))
        self.style.configure("fond_r.TButton", background=CONFIG.get("Rest", "bg"))
        self.style.configure("texte_w.TButton", background=CONFIG.get("Work", "fg"))
        self.style.configure("texte_p.TButton", background=CONFIG.get("Break", "fg"))
        self.style.configure("texte_r.TButton", background=CONFIG.get("Rest", "fg"))
        self.couleurs.grid_columnconfigure(3, weight=3)
        self.couleurs.grid_rowconfigure(0, weight=1)
        Label(self.couleurs, text=_("Work: "),
              style='title.TLabel').grid(row=0, column=0, pady=4,
                                         padx=(2,10), sticky="w")
        Label(self.couleurs, text=_("Background: ")).grid(row=0, column=1,
                                                          sticky="e", pady=(6,4))
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("fond_w"),
               style='fond_w.TButton').grid(row=0, column=2, pady=4)
        Label(self.couleurs, text=_("Text: ")).grid(row=1, column=1, sticky="e")
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("texte_w"),
               style='texte_w.TButton').grid(row=1, column=2, pady=4)

        Separator(self.couleurs, orient='horizontal').grid(row=2, sticky="ew",
                                                           pady=10, columnspan=4)

        Label(self.couleurs, text=_("Break: "),
              style='title.TLabel').grid(row=3, column=0, pady=4,
                                         padx=(2,10), sticky="w")
        Label(self.couleurs, text=_("Background: ")).grid(row=3, column=1,
                                                          sticky="e", pady=(6,4))
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("fond_p"),
               style='fond_p.TButton').grid(row=3, column=2, pady=4)
        Label(self.couleurs, text=_("Text: ")).grid(row=4, column=1, sticky="e")
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("texte_p"),
               style='texte_p.TButton').grid(row=4, column=2, pady=4)

        Separator(self.couleurs, orient='horizontal').grid(row=5, sticky="ew",
                                                           pady=10, columnspan=4)

        Label(self.couleurs, text=_("Rest: "),
              style='title.TLabel').grid(row=6, column=0, pady=4,
                                         sticky="w", padx=(2,10))
        Label(self.couleurs, text=_("Background: ")).grid(row=6, column=1,
                                                          sticky="e", pady=(6,4))
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("fond_r"),
               style='fond_r.TButton').grid(row=6, column=2, pady=4)
        Label(self.couleurs, text=_("Text: ")).grid(row=7, column=1, sticky="e")
        Button(self.couleurs, width=2, command=lambda: self.choix_couleur("texte_r"),
               style='texte_r.TButton').grid(row=7, column=2, pady=4)

        # Stats
        self.stats = Frame(self.onglets, padding=10)
        self.stats.pack(fill="both", expand=True, padx=10, pady=10)
        self.stats.grid_columnconfigure(2, weight=1)
        self.onglets.add(self.stats, text=_("Statistics"))

        Label(self.stats, text=_("Statistics:"),
              style='title.TLabel').grid(row=0, column=0, pady=4, sticky="w")

        tasks = [t.capitalize() for t in CONFIG.options("Tasks")]
        cmap = [CONFIG.get("Tasks", task) for task in tasks]
        for i, coul, task in zip(range(self.nb_task), cmap , tasks):
            Label(self.stats, text=task).grid(row=i + 1, column=0, sticky="e",
                                              padx=4, pady=4)
            self.style.configure("t%i.TButton" % i, background=coul)
            Button(self.stats, style="t%i.TButton" % i, width=2,
                   command=lambda j=i: self.coul_stat(j)).grid(row=i + 1,
                                                               column=1, pady=4)

        # Validation
        Button(self, text="Ok", command=self.valide).grid(row=1,column=1, sticky="we")
        Button(self, text=_("Cancel"), command=self.destroy).grid(row=1,column=0, sticky="we")



    def actualise_police(self, event):
        """ actualise le texte d'exemple de la police choisie """
        family = self.font.get()
        family = "\ ".join(family.split(" "))
        self.exemple.configure(font="%s %s" % (family, self.size.get()))

    def choix_couleur(self, type_mode):
        """ sélection de la couleur du fond/texte pour chaque mode (travail/pause/repos) """
        coul = askcolor(self.style.lookup(type_mode+".TButton", 'background'))
        if coul:
            self.style.configure(type_mode+".TButton", background=coul[1])

    def coul_stat(self, i):
        """ choix des couleurs pour l'affichage des stats """
        coul = askcolor(self.style.lookup("t%i.TButton" % i, "background"))
        if coul:
            self.style.configure("t%i.TButton" % i, background=coul[1])

    def valide(self):
        old_tpsw = CONFIG.getint("Work", "time")
        old_tpsp = CONFIG.getint("Break", "time")
        old_tpsr = CONFIG.getint("Rest", "time")
        tpsw = int(self.travail.get())
        tpsp = int(self.pause.get())
        tpsr = int(self.rest.get())
        pausefg = self.style.lookup("texte_p.TButton", "background")
        pausebg = self.style.lookup("fond_p.TButton", "background")
        workfg = self.style.lookup("texte_w.TButton", "background")
        workbg = self.style.lookup("fond_w.TButton", "background")
        restfg = self.style.lookup("texte_r.TButton", "background")
        restbg =self.style.lookup("fond_r.TButton", "background")
        son = self.bip.get()
        if PL[0] != "w":
            player = self.player.get()
        else:
            player = CONFIG.get("Sound", "player")
        mute = self.mute.get()
        family = self.font.get()
        family = "\ ".join(family.split(" "))
        size = self.size.get()
        cmap = []
        for i in range(self.nb_task):
            cmap.append(self.style.lookup("t%i.TButton" % i, "background"))

        if PL[0] == "w":
            filetypes = ['wav']
        else:
            filetypes = ["ogg", "wav", "mp3"]
        if (tpsw > 0 and tpsp > 0 and tpsr > 0 and
            os.path.exists(son) and (son.split('.')[-1] in filetypes)):
            CONFIG.set("General", "language", self.lang.get()[:2].lower())
            CONFIG.set("General", "font", family)
            CONFIG.set("General", "fontsize", size)
            CONFIG.set("Work", "time", str(tpsw))
            CONFIG.set("Work", "bg", workbg)
            CONFIG.set("Work", "fg", workfg)
            CONFIG.set("Break", "time", str(tpsp))
            CONFIG.set("Break", "bg", pausebg)
            CONFIG.set("Break", "fg", pausefg)
            CONFIG.set("Rest", "time", str(tpsr))
            CONFIG.set("Rest", "bg", restbg)
            CONFIG.set("Rest", "fg", restfg)
            CONFIG.set("Sound", "beep", son)
            CONFIG.set("Sound", "player", player)
            CONFIG.set("Sound", "mute", str(mute))
            for task, col in zip(CONFIG.options("Tasks"), cmap):
                CONFIG.set("Tasks", task, col)
            self.master.set_config()

            with open(PATH_CONFIG, "w") as file:
                CONFIG.write(file)
            if (old_tpsw != CONFIG.getint("Work", "time") or
                old_tpsp != CONFIG.getint("Break", "time") or
                old_tpsr != CONFIG.getint("Rest", "time")):
                self.master.stop(False)

            self.destroy()
        else:
            showerror(_("Error"),_("There is at least one invalid setting!"))

    def change_langue(self):
        showinfo(_("Information"),
                 _("The language setting will take effect after restarting the application."))

    def choix_son(self):
        if PL[0] == "w":
            filetypes = [('WAV','*.wav')]
        else:
            filetypes = [(_("sound file"), '*.mp3 *.ogg *.wav'),
                         ('OGG', '*.ogg'),
                         ('MP3', '*.mp3'),
                         ('WAV','*.wav')]
        init = self.bip.get()
        if not os.path.exists(init):
            init=CONFIG.get("Sound", "beep")
        fich = askopenfilename(filetypes=filetypes, initialfile=os.path.split(init)[1], initialdir=os.path.dirname(init))
        if fich:
            self.bip.delete(0,"end")
            self.bip.insert(0, fich)
Beispiel #18
0
    def __init__(self, master, font_dict={}, text="Abcd", title="Font Chooser",
                 **kwargs):
        """
        Create a new FontChooser instance.
        Arguments:
            master : Tk or Toplevel instance
                master window
            font_dict : dict
                dictionnary, like the one returned by the ``actual`` method of a ``Font`` object:
                ::
                    {'family': str,
                     'size': int,
                     'weight': 'bold'/'normal',
                     'slant': 'italic'/'roman',
                     'underline': bool,
                     'overstrike': bool}
            text : str
                text to be displayed in the preview label
            title : str
                window title
            kwargs : dict
                additional keyword arguments to be passed to ``Toplevel.__init__``
        """
        Toplevel.__init__(self, master, **kwargs)
        self.title(title)
        self.resizable(False, False)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

        # --- variable storing the chosen font
        self.res = ""

        style = Style(self)
        style.configure("prev.TLabel", background="white")
        bg = style.lookup("TLabel", "background")
        self.configure(bg=bg)

        # --- family list
        self.fonts = list(set(families()))
        self.fonts.append("TkDefaultFont")
        self.fonts.sort()
        for i in range(len(self.fonts)):
            self.fonts[i] = self.fonts[i].replace(" ", "\ ")
        max_length = int(2.5 * max([len(font) for font in self.fonts])) // 3
        self.sizes = ["%i" % i for i in (list(range(6, 17)) + list(range(18, 32, 2)))]
        # --- font default
        font_dict["weight"] = font_dict.get("weight", "normal")
        font_dict["slant"] = font_dict.get("slant", "roman")
        font_dict["underline"] = font_dict.get("underline", False)
        font_dict["overstrike"] = font_dict.get("overstrike", False)
        font_dict["family"] = font_dict.get("family",
                                            self.fonts[0].replace('\ ', ' '))
        font_dict["size"] = font_dict.get("size", 10)

        # --- creation of the widgets
        # ------ style parameters (bold, italic ...)
        options_frame = Frame(self, relief='groove', borderwidth=2)
        self.font_family = StringVar(self, " ".join(self.fonts))
        self.font_size = StringVar(self, " ".join(self.sizes))
        self.var_bold = BooleanVar(self, font_dict["weight"] == "bold")
        b_bold = Checkbutton(options_frame, text=TR["Bold"],
                             command=self.toggle_bold,
                             variable=self.var_bold)
        b_bold.grid(row=0, sticky="w", padx=4, pady=(4, 2))
        self.var_italic = BooleanVar(self, font_dict["slant"] == "italic")
        b_italic = Checkbutton(options_frame, text=TR["Italic"],
                               command=self.toggle_italic,
                               variable=self.var_italic)
        b_italic.grid(row=1, sticky="w", padx=4, pady=2)
        self.var_underline = BooleanVar(self, font_dict["underline"])
        b_underline = Checkbutton(options_frame, text=TR["Underline"],
                                  command=self.toggle_underline,
                                  variable=self.var_underline)
        b_underline.grid(row=2, sticky="w", padx=4, pady=2)
        self.var_overstrike = BooleanVar(self, font_dict["overstrike"])
        b_overstrike = Checkbutton(options_frame, text=TR["Overstrike"],
                                   variable=self.var_overstrike,
                                   command=self.toggle_overstrike)
        b_overstrike.grid(row=3, sticky="w", padx=4, pady=(2, 4))
        # ------ Size and family
        self.var_size = StringVar(self)
        self.entry_family = Entry(self, width=max_length, validate="key",
                                  validatecommand=(self._validate_family, "%d", "%S",
                                                   "%i", "%s", "%V"))
        self.entry_size = Entry(self, width=4, validate="key",
                                textvariable=self.var_size,
                                validatecommand=(self._validate_size, "%d", "%P", "%V"))
        self.list_family = Listbox(self, selectmode="browse",
                                   listvariable=self.font_family,
                                   highlightthickness=0,
                                   exportselection=False,
                                   width=max_length)
        self.list_size = Listbox(self, selectmode="browse",
                                 listvariable=self.font_size,
                                 highlightthickness=0,
                                 exportselection=False,
                                 width=4)
        scroll_family = Scrollbar(self, orient='vertical',
                                  command=self.list_family.yview)
        scroll_size = Scrollbar(self, orient='vertical',
                                command=self.list_size.yview)
        self.preview_font = Font(self, **font_dict)
        if len(text) > 30:
            text = text[:30]
        self.preview = Label(self, relief="groove", style="prev.TLabel",
                             text=text, font=self.preview_font,
                             anchor="center")

        # --- widget configuration
        self.list_family.configure(yscrollcommand=scroll_family.set)
        self.list_size.configure(yscrollcommand=scroll_size.set)

        self.entry_family.insert(0, font_dict["family"])
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.entry_size.insert(0, font_dict["size"])

        try:
            i = self.fonts.index(self.entry_family.get().replace(" ", "\ "))
        except ValueError:
            # unknown font
            i = 0
        self.list_family.selection_clear(0, "end")
        self.list_family.selection_set(i)
        self.list_family.see(i)
        try:
            i = self.sizes.index(self.entry_size.get())
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            self.list_size.see(i)
        except ValueError:
            # size not in list
            pass

        self.entry_family.grid(row=0, column=0, sticky="ew",
                               pady=(10, 1), padx=(10, 0))
        self.entry_size.grid(row=0, column=2, sticky="ew",
                             pady=(10, 1), padx=(10, 0))
        self.list_family.grid(row=1, column=0, sticky="nsew",
                              pady=(1, 10), padx=(10, 0))
        self.list_size.grid(row=1, column=2, sticky="nsew",
                            pady=(1, 10), padx=(10, 0))
        scroll_family.grid(row=1, column=1, sticky='ns', pady=(1, 10))
        scroll_size.grid(row=1, column=3, sticky='ns', pady=(1, 10))
        options_frame.grid(row=0, column=4, rowspan=2,
                           padx=10, pady=10, ipadx=10)

        self.preview.grid(row=2, column=0, columnspan=5, sticky="eswn",
                          padx=10, pady=(0, 10), ipadx=4, ipady=4)

        button_frame = Frame(self)
        button_frame.grid(row=3, column=0, columnspan=5, pady=(0, 10), padx=10)

        Button(button_frame, text="Ok",
               command=self.ok).grid(row=0, column=0, padx=4, sticky='ew')
        Button(button_frame, text=TR["Cancel"],
               command=self.quit).grid(row=0, column=1, padx=4, sticky='ew')
        self.list_family.bind('<<ListboxSelect>>', self.update_entry_family)
        self.list_size.bind('<<ListboxSelect>>', self.update_entry_size,
                            add=True)
        self.list_family.bind("<KeyPress>", self.keypress)
        self.entry_family.bind("<Return>", self.change_font_family)
        self.entry_family.bind("<Tab>", self.tab)
        self.entry_size.bind("<Return>", self.change_font_size)

        self.entry_family.bind("<Down>", self.down_family)
        self.entry_size.bind("<Down>", self.down_size)

        self.entry_family.bind("<Up>", self.up_family)
        self.entry_size.bind("<Up>", self.up_size)

        # bind Ctrl+A to select all instead of go to beginning
        self.bind_class("TEntry", "<Control-a>", self.select_all)

        self.wait_visibility(self)
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()
Beispiel #19
0
    def __init__(self, master, font_dict=None, text='AaBbYyZz', title='Font', **kwargs):

        Toplevel.__init__(self, master, **kwargs)
        self.title(title)

        try:
            self.wm_iconbitmap('transparent.ico')
        except TclError:
            pass

        self.resizable(False, False)
        self.protocol('WM_DELETE_WINDOW', self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

        # --- variable storing the chosen font
        self.res = ''

        style = Style(self)
        style.configure('prev.TLabel')
        bg = style.lookup('TLabel', 'background')
        self.configure(bg=bg)

        # --- family list
        self.fonts = list(set(families()))
        self.fonts.append('TkDefaultFont')
        self.fonts.sort()
        for i in range(len(self.fonts)):
            self.fonts[i] = self.fonts[i].replace(' ', '\\ ')
        max_length = int(2.5 * max([len(font) for font in self.fonts])) // 3 - 2
        self.sizes = ['%i' % i for i in (list(range(6, 17)) + list(range(18, 32, 2)) + list(range(36, 48, 4)))]
        # --- font default
        font_dict['weight'] = font_dict.get('weight', 'normal')
        font_dict['slant'] = font_dict.get('slant', 'roman')
        font_dict['underline'] = font_dict.get('underline', False)
        font_dict['overstrike'] = font_dict.get('overstrike', False)
        font_dict['family'] = font_dict.get('family', self.fonts[0].replace('\\ ', ' '))
        font_dict['size'] = font_dict.get('size', 10)

        # --- format list
        self.formats = ['Regular', 'Italic', 'Bold', 'Bold Italic']

        # --- creation of the widgets
        self.font_family = StringVar(self, ' '.join(self.fonts))
        self.font_size = StringVar(self, ' '.join(self.sizes))
        self.format_type = StringVar(self, self.formats)
        self.var_bold = BooleanVar(self, font_dict['weight'] == 'bold')
        self.var_italic = BooleanVar(self, font_dict['slant'] == 'italic')

        # ------ Size and family
        self.var_size = StringVar(self)
        self.entry_family = Entry(self, width=max_length, validate='key',
                                  validatecommand=(self._validate_family, '%d', '%S',
                                                   '%i', '%s', '%V'))
        self.entry_size = Entry(self, width=8, validate='key',
                                textvariable=self.var_size,
                                validatecommand=(self._validate_size, '%d', '%P', '%V'))

        self.entry_format = Entry(self)

        self.list_family = Listbox(self, selectmode='browse',
                                   listvariable=self.font_family,
                                   highlightthickness=0,
                                   exportselection=False,
                                   width=max_length,
                                   height=6)
        self.list_size = Listbox(self, selectmode='browse',
                                 listvariable=self.font_size,
                                 highlightthickness=0,
                                 exportselection=False,
                                 width=6,
                                 height=6)
        self.list_format = Listbox(self, selectmode='browse',
                                   listvariable=self.format_type,
                                   highlightthickness=0,
                                   exportselection=False,
                                   width=12,
                                   height=6)
        self.scroll_family = Scrollbar(self, orient='vertical',
                                       command=self.list_family.yview)
        self.scroll_size = Scrollbar(self, orient='vertical',
                                     command=self.list_size.yview)
        self.scroll_format = Scrollbar(self, orient='vertical',
                                       command=self.list_format.yview)
        self.family_label = Label(self, text='Font:')
        self.style_label = Label(self, text='Font style:')
        self.size_label = Label(self, text='Size:')

        self.script_label = Label(self, text='Script:')
        self.script_box = Combobox(self, values=['Western'])

        self.more_fonts_label = Lbl(self, text='Show more fonts', underline=1, fg='blue', cursor='hand2')
        f = Font(self.more_fonts_label, self.more_fonts_label.cget("font"))
        f.configure(underline=True)
        self.more_fonts_label.configure(font=f)

        self.preview_font = Font(self, **font_dict)
        if len(text) > 30:
            text = text[:30]
        self.preview_window = LabelFrame(self, relief='groove', text='Sample', bd=1)

        # --- widget configuration
        self.list_family.configure(yscrollcommand=self.scroll_family.set)
        self.list_size.configure(yscrollcommand=self.scroll_size.set)
        self.list_format.configure(yscrollcommand=self.scroll_format.set)

        self.entry_family.insert(0, font_dict['family'])
        self.entry_family.selection_clear()
        self.entry_family.icursor('end')
        self.entry_format.insert(0, self.formats[1])
        self.entry_format.selection_clear()
        self.entry_format.icursor('end')
        self.entry_size.insert(0, font_dict['size'])

        try:
            i = self.fonts.index(self.entry_family.get().replace(' ', '\\ '))
        except ValueError:
            # unknown font
            i = 0
        self.list_family.selection_clear(0, 'end')
        self.list_family.selection_set(i)
        self.list_family.see(i)
        try:
            i = self.sizes.index(self.entry_size.get())
            self.list_size.selection_clear(0, 'end')
            self.list_size.selection_set(i)
            self.list_size.see(i)
        except ValueError:
            # size not in listtsg
            pass

        # font family location config
        self.family_label.grid(row=0, column=0, sticky='nsew', pady=(10, 1), padx=(10, 1))
        self.entry_family.grid(row=1, column=0, sticky='nsew', pady=(1, 1), padx=(10, 0), columnspan=2)
        self.list_family.grid(row=2, column=0, sticky='nsew', pady=(1, 10), padx=(10, 0))
        self.scroll_family.grid(row=2, column=1, sticky='ns', pady=(1, 10))

        # font style/format location config
        self.style_label.grid(row=0, column=2, sticky='nsew', pady=(10, 1), padx=(15, 1))
        self.entry_format.grid(row=1, column=2, sticky='nsew', pady=(1, 1), padx=(15, 0), columnspan=2)
        self.list_format.grid(row=2, column=2, sticky='nsew', pady=(1, 10), padx=(15, 0))
        self.scroll_format.grid(row=2, column=3, sticky='ns', pady=(1, 10))

        # font size location config
        self.size_label.grid(row=0, column=4, sticky='nsew', pady=(10, 1), padx=(15, 1))
        self.entry_size.grid(row=1, column=4, sticky='nsew', pady=(1, 1), padx=(15, 10), columnspan=2)
        self.list_size.grid(row=2, column=4, sticky='nsew', pady=(1, 10), padx=(15, 0))
        self.scroll_size.grid(row=2, column=5, sticky='nsew', pady=(1, 10), padx=(0, 10))

        # font preview location config
        self.preview_window.grid(row=4, column=2, columnspan=4, sticky='nsew', rowspan=2, padx=15, pady=(0, 10),
                                 ipadx=10, ipady=10)
        self.preview_window.config(height=75)
        preview = Label(self.preview_window, text=text, font=self.preview_font, anchor='center')
        preview.place(relx=0.5, rely=0.5, anchor='center')

        self.script_label.grid(row=6, column=2, sticky='nsw', padx=(15, 0))
        self.script_box.grid(row=7, column=2, sticky='nsw', pady=(1, 30), padx=(15, 0))
        self.script_box.current(0)

        self.more_fonts_label.grid(row=8, column=0, pady=(35, 20), padx=(15, 0), sticky='nsw')

        button_frame = Frame(self)
        button_frame.grid(row=9, column=2, columnspan=4, pady=(0, 10), padx=(10, 0))

        Button(button_frame, text='Ok', command=self.ok).grid(row=0, column=0, padx=4, sticky='ew')
        Button(button_frame, text='Cancel', command=self.quit).grid(row=0, column=1, padx=4, sticky='ew')

        self.list_family.bind('<<ListboxSelect>>', self.update_entry_family)
        self.list_format.bind('<<ListboxSelect>>', self.update_entry_format)
        self.list_size.bind('<<ListboxSelect>>', self.update_entry_size, add=True)
        self.list_family.bind('<KeyPress>', self.keypress)
        self.entry_family.bind('<Return>', self.change_font_family)
        self.entry_family.bind('<Tab>', self.tab)
        self.entry_size.bind('<Return>', self.change_font_size)
        self.more_fonts_label.bind('<Button-1>', search_fonts)

        self.entry_family.bind('<Down>', self.down_family)
        self.entry_size.bind('<Down>', self.down_size)
        self.entry_family.bind('<Up>', self.up_family)
        self.entry_size.bind('<Up>', self.up_size)
        self.bind_class('TEntry', '<Control-a>', self.select_all)

        self.wait_visibility(self)
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()
Beispiel #20
0
class ToggledFrame(Frame):
    """
    A frame that can be toggled to open and close
    """
    def __init__(self, master=None, text="", **kwargs):
        font = kwargs.pop('font', '')
        Frame.__init__(self, master, **kwargs)
        self.style_name = self.cget('style')
        self.toggle_style_name = '%s.Toggle' % ('.'.join(
            self.style_name.split('.')[:-1]))
        self.columnconfigure(1, weight=1)
        self.rowconfigure(1, weight=1)
        self.style = Style(self)
        self.style.configure(self.toggle_style_name,
                             background=self.style.lookup(
                                 self.style_name, 'background'))
        self.style.map(self.toggle_style_name, background=[])
        self._checkbutton = Checkbutton(self,
                                        style=self.toggle_style_name,
                                        command=self.toggle,
                                        cursor='arrow')
        self.label = Label(self,
                           text=text,
                           font=font,
                           style=self.style_name.replace('TFrame', 'TLabel'))
        self.interior = Frame(self, style=self.style_name)
        self.interior.grid(row=1, column=1, sticky="nswe", padx=(4, 0))
        self.interior.grid_remove()
        self.label.bind('<Configure>', self._wrap)
        self.label.bind('<1>', lambda e: self._checkbutton.invoke())
        self._grid_widgets()
        self.bind('<<ThemeChanged>>', self._theme_changed)

    def _theme_changed(self, event):
        self.style.configure(self.toggle_style_name,
                             background=self.style.lookup(
                                 self.style_name, 'background'))

    def _wrap(self, event):
        self.label.configure(wraplength=self.label.winfo_width())

    def _grid_widgets(self):
        self._checkbutton.grid(row=0, column=0)
        self.label.grid(row=0, column=1, sticky="we")

    def toggle(self):
        if 'selected' not in self._checkbutton.state():
            self.interior.grid_remove()
            self.event_generate("<<ToggledFrameClose>>")
        else:
            self.interior.grid()
            self.event_generate("<<ToggledFrameOpen>>")

    def open(self):
        self._checkbutton.state(('selected', ))
        self.interior.grid()
        self.event_generate("<<ToggledFrameOpen>>")

    def close(self):
        self._checkbutton.state(('!selected', ))
        self.interior.grid_remove()
        self.event_generate("<<ToggledFrameClose>>")
Beispiel #21
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")
Beispiel #22
0
class App(Tk):
    def __init__(self):
        Tk.__init__(self, className=cst.APP_NAME)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self.withdraw()

        logging.info('Starting %s', cst.APP_NAME)

        self.im_icon = PhotoImage(master=self, file=cst.IM_ICON_48)
        self.iconphoto(True, self.im_icon)

        # --- style
        self.style = Style(self)
        self.style.theme_use("clam")
        self.style.configure("TScale", sliderlength=20)
        self.style.map("TCombobox",
                       fieldbackground=[('readonly', 'white')],
                       selectbackground=[('readonly', 'white')],
                       selectforeground=[('readonly', 'black')])
        self.style.configure("title.TLabel", font="TkDefaultFont 9 bold")
        self.style.configure("white.TLabel", background="white")
        self.style.map("white.TLabel", background=[("active", "white")])
        self.style.configure('heading.TLabel',
                             relief='ridge',
                             borderwidth=1,
                             padding=(10, 4))
        self.style.configure('manager.TButton', padding=0)
        self.style.map('manager.Treeview', background=[], foreground=[])
        self.style.layout(
            'no_edit.TEntry',
            [('Entry.padding', {
                'children': [('Entry.textarea', {
                    'sticky': 'nswe'
                })],
                'sticky': 'nswe'
            })])
        self.style.configure('no_edit.TEntry',
                             background='white',
                             padding=[4, 0])
        self.style.configure('manager.TEntry', padding=[2, 1])
        self.style.layout('manager.Treeview.Row', [('Treeitem.row', {
            'sticky': 'nswe'
        }), ('Treeitem.image', {
            'side': 'right',
            'sticky': 'e'
        })])
        self.style.layout('manager.Treeview.Item', [('Treeitem.padding', {
            'children': [('Checkbutton.indicator', {
                'side': 'left',
                'sticky': ''
            }), ('Treeitem.text', {
                'side': 'left',
                'sticky': ''
            })],
            'sticky':
            'nswe'
        })])

        self._im_trough = tkPhotoImage(name='trough-scrollbar-vert',
                                       width=15,
                                       height=15,
                                       master=self)
        bg = CONFIG.get("Widget", 'background', fallback='gray10')
        widget_bg = (0, 0, 0)
        widget_fg = (255, 255, 255)
        vmax = self.winfo_rgb('white')[0]
        color = tuple(int(val / vmax * 255) for val in widget_bg)
        active_bg = cst.active_color(color)
        active_bg2 = cst.active_color(cst.active_color(color, 'RGB'))
        slider_vert_insens = Image.new('RGBA', (13, 28), widget_bg)
        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.put(" ".join(["{" + " ".join([bg] * 15) + "}"] * 15))
        self._im_slider_vert_active = PhotoImage(slider_vert_active,
                                                 name='slider-vert-active',
                                                 master=self)
        self._im_slider_vert = PhotoImage(slider_vert,
                                          name='slider-vert',
                                          master=self)
        self._im_slider_vert_prelight = PhotoImage(slider_vert_prelight,
                                                   name='slider-vert-prelight',
                                                   master=self)
        self._im_slider_vert_insens = PhotoImage(slider_vert_insens,
                                                 name='slider-vert-insens',
                                                 master=self)
        self.style.element_create('widget.Vertical.Scrollbar.trough', 'image',
                                  'trough-scrollbar-vert')
        self.style.element_create(
            'widget.Vertical.Scrollbar.thumb',
            'image',
            'slider-vert', ('pressed', '!disabled', 'slider-vert-active'),
            ('active', '!disabled', 'slider-vert-prelight'),
            ('disabled', 'slider-vert-insens'),
            border=6,
            sticky='ns')
        self.style.layout('widget.Vertical.TScrollbar', [
            ('widget.Vertical.Scrollbar.trough', {
                'children': [('widget.Vertical.Scrollbar.thumb', {
                    'expand': '1'
                })],
                'sticky': 'ns'
            })
        ])

        hide = Image.new('RGBA', (12, 12), active_bg2)
        hide_active = Image.new('RGBA', (12, 12), widget_fg)
        hide_pressed = Image.new('RGBA', (12, 12), (150, 0, 0))
        toggle_open = Image.new('RGBA', (9, 9), widget_fg)
        toggle_open_active = Image.new('RGBA', (9, 9), active_bg2)
        toggle_close = Image.new('RGBA', (9, 9), widget_fg)
        toggle_close_active = Image.new('RGBA', (9, 9), active_bg2)
        self._im_hide = PhotoImage(hide, master=self)
        self._im_hide_active = PhotoImage(hide_active, master=self)
        self._im_hide_pressed = PhotoImage(hide_pressed, master=self)
        self._im_open = PhotoImage(toggle_open, master=self)
        self._im_open_active = PhotoImage(toggle_open_active, master=self)
        self._im_close = PhotoImage(toggle_close, master=self)
        self._im_close_active = PhotoImage(toggle_close_active, master=self)

        self.style.element_create(
            "toggle",
            "image",
            self._im_close, ("!hover", "selected", "!disabled", self._im_open),
            ("hover", "!selected", "!disabled", self._im_close_active),
            ("hover", "selected", "!disabled", self._im_open_active),
            border=2,
            sticky='')
        self.style.layout('Toggle', [('Toggle.border', {
            'children': [('Toggle.padding', {
                'children': [('Toggle.toggle', {
                    'sticky': 'nswe'
                })],
                'sticky': 'nswe'
            })],
            'sticky':
            'nswe'
        })])
        self.style.configure('widget.close.TButton',
                             background=bg,
                             relief='flat',
                             image=self._im_hide,
                             padding=0)
        self.style.map('widget.close.TButton',
                       background=[],
                       relief=[],
                       image=[('active', '!pressed', self._im_hide_active),
                              ('active', 'pressed', self._im_hide_pressed)])
        self.option_add('*Toplevel.background',
                        self.style.lookup('TFrame', 'background'))
        self.option_add('*{app_name}.background'.format(app_name=cst.APP_NAME),
                        self.style.lookup('TFrame', 'background'))
        self.widget_style_init()

        # --- tray icon menu
        self.icon = TrayIcon(cst.ICON)
        self.menu_widgets = SubMenu(parent=self.icon.menu)

        self.menu_categories = SubMenu(parent=self.menu_widgets)
        self.menu_categories.add_command(label=_('Hide all'),
                                         command=self.hide_all_cats)
        self.menu_categories.add_command(label=_('Show all'),
                                         command=self.hide_all_cats)
        self.menu_categories.add_separator()

        self.menu_feeds = SubMenu(parent=self.menu_widgets)
        self.menu_feeds.add_command(label=_('Hide all'),
                                    command=self.hide_all_feeds)
        self.menu_feeds.add_command(label=_('Show all'),
                                    command=self.show_all_feeds)
        self.menu_feeds.add_separator()

        self.menu_widgets.add_command(label=_('Hide all'),
                                      command=self.hide_all)
        self.menu_widgets.add_command(label=_('Show all'),
                                      command=self.show_all)
        self.menu_widgets.add_separator()
        self.menu_widgets.add_cascade(label=_('Categories'),
                                      menu=self.menu_categories)
        self.menu_widgets.add_cascade(label=_('Feeds'), menu=self.menu_feeds)

        self.icon.menu.add_cascade(label=_('Widgets'), menu=self.menu_widgets)
        self.icon.menu.add_command(label=_('Add feed'), command=self.add)
        self.icon.menu.add_command(label=_('Update feeds'),
                                   command=self.feed_update)
        self.icon.menu.add_command(label=_('Manage feeds'),
                                   command=self.feed_manage)
        self.icon.menu.add_command(label=_("Suspend"), command=self.start_stop)
        self.icon.menu.add_separator()
        self.icon.menu.add_command(label=_('Settings'), command=self.settings)
        self.icon.menu.add_command(label=_("Check for updates"),
                                   command=lambda: UpdateChecker(self, True))
        self.icon.menu.add_command(label=_("Help"), command=lambda: Help(self))
        self.icon.menu.add_command(label=_("About"),
                                   command=lambda: About(self))
        self.icon.menu.add_command(label=_('Quit'), command=self.quit)
        self.icon.loop(self)

        self._notify_no_internet = True

        self._internet_id = ""
        self._update_id = ""
        self._check_add_id = ""
        self._check_end_update_id = ""
        self._check_result_update_id = {}
        self._check_result_init_id = {}
        self.queues = {}
        self.threads = {}

        # --- category widgets
        self.cat_widgets = {}
        self.cat_widgets['All'] = CatWidget(self, 'All')
        self.cat_widgets['All'].event_generate('<Configure>')
        self.menu_widgets.add_checkbutton(label=_('Latests'),
                                          command=self.toggle_latests_widget)
        cst.add_trace(self.cat_widgets['All'].variable, 'write',
                      self.latests_widget_trace)
        self.cat_widgets['All'].variable.set(
            LATESTS.getboolean('All', 'visible'))
        cats = LATESTS.sections()

        cats.remove('All')
        for category in cats:
            self.cat_widgets[category] = CatWidget(self, category)
            self.cat_widgets[category].event_generate('<Configure>')
            self.menu_categories.add_checkbutton(
                label=category,
                command=lambda c=category: self.toggle_category_widget(c))
            cst.add_trace(self.cat_widgets[category].variable,
                          'write',
                          lambda *args, c=category: self.cat_widget_trace(c))
            self.cat_widgets[category].variable.set(
                LATESTS.getboolean(category, 'visible'))

        # --- feed widgets
        self.feed_widgets = {}
        for title in FEEDS.sections():
            self._check_result_update_id[title] = ''
            self._check_result_init_id[title] = ''
            self.queues[title] = Queue(1)
            self.threads[title] = None
            self.menu_feeds.add_checkbutton(
                label=title,
                command=lambda t=title: self.toggle_feed_widget(t))
            self.feed_widgets[title] = FeedWidget(self, title)
            cst.add_trace(self.feed_widgets[title].variable,
                          'write',
                          lambda *args, t=title: self.feed_widget_trace(t))
            self.feed_widgets[title].variable.set(
                FEEDS.getboolean(title, 'visible', fallback=True))
        self.feed_init()

        # --- check for updates
        if CONFIG.getboolean("General", "check_update"):
            UpdateChecker(self)

        self.bind_class('TEntry', '<Control-a>', self.entry_select_all)

    def widget_style_init(self):
        """Init widgets style."""
        bg = CONFIG.get('Widget', 'background', fallback='gray10')
        feed_bg = CONFIG.get('Widget', 'feed_background', fallback='gray20')
        fg = CONFIG.get('Widget', 'foreground')
        vmax = self.winfo_rgb('white')[0]
        widget_bg = tuple(int(val / vmax * 255) for val in self.winfo_rgb(bg))
        widget_fg = tuple(int(val / vmax * 255) for val in self.winfo_rgb(fg))
        active_bg = cst.active_color(widget_bg)
        active_bg2 = cst.active_color(cst.active_color(widget_bg, 'RGB'))
        slider_alpha = Image.open(cst.IM_SCROLL_ALPHA)
        slider_vert_insens = Image.new('RGBA', (13, 28), widget_bg)
        slider_vert = Image.new('RGBA', (13, 28), active_bg)
        slider_vert.putalpha(slider_alpha)
        slider_vert_active = Image.new('RGBA', (13, 28), widget_fg)
        slider_vert_active.putalpha(slider_alpha)
        slider_vert_prelight = Image.new('RGBA', (13, 28), active_bg2)
        slider_vert_prelight.putalpha(slider_alpha)

        self._im_slider_vert_active.paste(slider_vert_active)
        self._im_slider_vert.paste(slider_vert)
        self._im_slider_vert_prelight.paste(slider_vert_prelight)
        self._im_slider_vert_insens.paste(slider_vert_insens)
        self._im_trough.put(" ".join(["{" + " ".join([bg] * 15) + "}"] * 15))

        hide_alpha = Image.open(cst.IM_HIDE_ALPHA)
        hide = Image.new('RGBA', (12, 12), active_bg)
        hide.putalpha(hide_alpha)
        hide_active = Image.new('RGBA', (12, 12), active_bg2)
        hide_active.putalpha(hide_alpha)
        hide_pressed = Image.new('RGBA', (12, 12), widget_fg)
        hide_pressed.putalpha(hide_alpha)
        toggle_open_alpha = Image.open(cst.IM_OPENED_ALPHA)
        toggle_open = Image.new('RGBA', (9, 9), widget_fg)
        toggle_open.putalpha(toggle_open_alpha)
        toggle_open_active = Image.new('RGBA', (9, 9), active_bg2)
        toggle_open_active.putalpha(toggle_open_alpha)
        toggle_close_alpha = Image.open(cst.IM_CLOSED_ALPHA)
        toggle_close = Image.new('RGBA', (9, 9), widget_fg)
        toggle_close.putalpha(toggle_close_alpha)
        toggle_close_active = Image.new('RGBA', (9, 9), active_bg2)
        toggle_close_active.putalpha(toggle_close_alpha)
        self._im_hide.paste(hide)
        self._im_hide_active.paste(hide_active)
        self._im_hide_pressed.paste(hide_pressed)
        self._im_open.paste(toggle_open)
        self._im_open_active.paste(toggle_open_active)
        self._im_close.paste(toggle_close)
        self._im_close_active.paste(toggle_close_active)

        self.style.configure('widget.TFrame', background=bg)
        self.style.configure('widget.close.TButton', background=bg)
        #                             relief='flat', image=self._im_hide, padding=0)
        #        self.style.map('widget.close.TButton', background=[], relief=[],
        #                       image=[('active', '!pressed', self._im_hide_active),
        #                              ('active', 'pressed', self._im_hide_pressed)])
        self.style.configure('widget.interior.TFrame', background=feed_bg)
        self.style.configure('widget.TSizegrip', background=bg)
        self.style.configure('widget.Horizontal.TSeparator', background=bg)
        self.style.configure('widget.TLabel',
                             background=bg,
                             foreground=fg,
                             font=CONFIG.get('Widget', 'font'))
        self.style.configure('widget.title.TLabel',
                             background=bg,
                             foreground=fg,
                             font=CONFIG.get('Widget', 'font_title'))
        self.style.configure('widget.TButton',
                             background=bg,
                             foreground=fg,
                             padding=1,
                             relief='flat')
        self.style.map('widget.TButton',
                       background=[('disabled', active_bg), ('pressed', fg),
                                   ('active', active_bg)],
                       foreground=[('pressed', bg)])
        #                       relief=[('pressed', 'sunken')])
        #                       bordercolor=[('pressed', active_bg)],
        #                       darkcolor=[('pressed', bg)],
        #                       lightcolor=[('pressed', fg)])

        self.update_idletasks()

    def hide_all(self):
        """Withdraw all widgets."""
        for widget in self.cat_widgets.values():
            widget.withdraw()
        for widget in self.feed_widgets.values():
            widget.withdraw()

    def show_all(self):
        """Deiconify all widgets."""
        for widget in self.cat_widgets.values():
            widget.deiconify()
        for widget in self.feed_widgets.values():
            widget.deiconify()

    def hide_all_feeds(self):
        """Withdraw all feed widgets."""
        for widget in self.feed_widgets.values():
            widget.withdraw()

    def show_all_feeds(self):
        """Deiconify all feed widgets."""
        for widget in self.feed_widgets.values():
            widget.deiconify()

    def hide_all_cats(self):
        """Withdraw all category widgets."""
        for cat, widget in self.cat_widgets.items():
            if cat != 'All':
                widget.withdraw()

    def show_all_cats(self):
        """Deiconify all category widgets."""
        for cat, widget in self.cat_widgets.items():
            if cat != 'All':
                widget.deiconify()

    def start_stop(self):
        """Suspend / restart update checks."""
        if self.icon.menu.get_item_label(4) == _("Suspend"):
            after_ids = [
                self._update_id, self._check_add_id, self._internet_id,
                self._check_end_update_id, self._update_id
            ]
            after_ids.extend(self._check_result_update_id.values())
            after_ids.extend(self._check_result_init_id.values())
            for after_id in after_ids:
                try:
                    self.after_cancel(after_id)
                except ValueError:
                    pass
            self.icon.menu.set_item_label(4, _("Restart"))
            self.icon.menu.disable_item(1)
            self.icon.menu.disable_item(2)
            self.icon.change_icon(cst.ICON_DISABLED, 'feedagregator suspended')
        else:
            self.icon.menu.set_item_label(4, _("Suspend"))
            self.icon.menu.enable_item(1)
            self.icon.menu.enable_item(2)
            self.icon.change_icon(cst.ICON, 'feedagregator')
            for widget in self.feed_widgets.values():
                widget.clear()
            self.feed_init()

    @staticmethod
    def entry_select_all(event):
        event.widget.selection_clear()
        event.widget.selection_range(0, 'end')

    def test_connection(self):
        """
        Launch update check if there is an internet connection otherwise
        check again for an internet connection after 30s.
        """
        if cst.internet_on():
            logging.info('Connected to Internet')
            self._notify_no_internet = True
            for widget in self.feed_widgets.values():
                widget.clear()
            self.feed_init()
        else:
            self._internet_id = self.after(30000, self.test_connection)

    def quit(self):
        for after_id in self.tk.call('after', 'info'):
            try:
                self.after_cancel(after_id)
            except ValueError:
                pass
        for thread in self.threads.values():
            try:
                thread.terminate()
            except AttributeError:
                pass
        for title, widget in self.feed_widgets.items():
            FEEDS.set(title, 'visible', str(widget.variable.get()))
        for cat, widget in self.cat_widgets.items():
            LATESTS.set(cat, 'visible', str(widget.variable.get()))
        try:
            self.destroy()
        except TclError:
            logging.error("Error on quit")
            self.after(500, self.quit)

    def feed_widget_trace(self, title):
        value = self.feed_widgets[title].variable.get()
        self.menu_feeds.set_item_value(title, value)
        FEEDS.set(title, 'visible', str(value))
        cst.save_feeds()

    def cat_widget_trace(self, category):
        value = self.cat_widgets[category].variable.get()
        self.menu_categories.set_item_value(category, value)
        LATESTS.set(category, 'visible', str(value))
        cst.save_latests()

    def latests_widget_trace(self, *args):
        value = self.cat_widgets['All'].variable.get()
        self.menu_widgets.set_item_value(_('Latests'), value)
        LATESTS.set('All', 'visible', str(value))
        cst.save_latests()

    def toggle_category_widget(self, category):
        value = self.menu_categories.get_item_value(category)
        if value:
            self.cat_widgets[category].deiconify()
        else:
            self.cat_widgets[category].withdraw()
        self.update_idletasks()

    def toggle_latests_widget(self):
        value = self.menu_widgets.get_item_value(_('Latests'))
        if value:
            self.cat_widgets['All'].deiconify()
        else:
            self.cat_widgets['All'].withdraw()
        self.update_idletasks()

    def toggle_feed_widget(self, title):
        value = self.menu_feeds.get_item_value(title)
        if value:
            self.feed_widgets[title].deiconify()
        else:
            self.feed_widgets[title].withdraw()
        self.update_idletasks()

    def report_callback_exception(self, *args):
        """Log exceptions."""
        err = "".join(traceback.format_exception(*args))
        logging.error(err)
        showerror(_("Error"), str(args[1]), err, True)

    def settings(self):
        update_delay = CONFIG.get('General', 'update_delay')
        splash_supp = CONFIG.get('General', 'splash_supported', fallback=True)
        dialog = Config(self)
        self.wait_window(dialog)
        cst.save_config()
        self.widget_style_init()
        splash_change = splash_supp != CONFIG.get('General',
                                                  'splash_supported')
        for widget in self.cat_widgets.values():
            widget.update_style()
            if splash_change:
                widget.update_position()
        for widget in self.feed_widgets.values():
            widget.update_style()
            if splash_change:
                widget.update_position()
        if update_delay != CONFIG.get('General', 'update_delay'):
            self.feed_update()

    def add(self):
        dialog = Add(self)
        self.wait_window(dialog)
        url = dialog.url
        self.feed_add(url)

    def category_remove(self, category):
        self.cat_widgets[category].destroy()
        del self.cat_widgets[category]
        self.menu_categories.delete(category)
        LATESTS.remove_section(category)
        cst.save_feeds()
        cst.save_latests()

    @staticmethod
    def feed_get_info(url, queue, mode='latest'):
        feed = feedparser.parse(url)
        feed_title = feed['feed'].get('title', '')
        entries = feed['entries']
        today = datetime.now().strftime('%Y-%m-%d %H:%M')
        if entries:
            entry_title = entries[0].get('title', '')
            summary = entries[0].get('summary', '')
            link = entries[0].get('link', '')
            latest = """<p id=title>{}</p>\n{}""".format(entry_title, summary)
            if 'updated' in entries[0]:
                updated = entries[0].get('updated')
            else:
                updated = entries[0].get('published', today)
            updated = dateutil.parser.parse(
                updated, tzinfos=cst.TZINFOS).strftime('%Y-%m-%d %H:%M')
        else:
            entry_title = ""
            summary = ""
            link = ""
            latest = ""
            updated = today

        if mode == 'all':
            data = []
            for entry in entries:
                title = entry.get('title', '')
                summary = entry.get('summary', '')
                if 'updated' in entry:
                    date = entry.get('updated')
                else:
                    date = entry.get('published', today)
                date = dateutil.parser.parse(
                    date, tzinfos=cst.TZINFOS).strftime('%Y-%m-%d %H:%M')
                link = entry.get('link', '')
                data.append((title, date, summary, link))
            queue.put((feed_title, latest, updated, data))
        else:
            queue.put(
                (feed_title, latest, updated, entry_title, summary, link))

    def _check_result_add(self, thread, queue, url, manager_queue=None):
        if thread.is_alive():
            self._check_add_id = self.after(1000, self._check_result_add,
                                            thread, queue, url, manager_queue)
        else:
            title, latest, date, data = queue.get(False)
            if title:
                try:
                    # check if feed's title already exists
                    FEEDS.add_section(title)
                except configparser.DuplicateSectionError:
                    i = 2
                    duplicate = True
                    while duplicate:
                        # increment i until title~#i does not already exist
                        try:
                            FEEDS.add_section("{}~#{}".format(title, i))
                        except configparser.DuplicateSectionError:
                            i += 1
                        else:
                            duplicate = False
                            name = "{}~#{}".format(title, i)
                else:
                    name = title
                if manager_queue is not None:
                    manager_queue.put(name)
                logging.info("Added feed '%s' %s", name, url)
                if CONFIG.getboolean("General", "notifications",
                                     fallback=True):
                    run([
                        "notify-send", "-i", cst.IM_ICON_SVG, name,
                        cst.html2text(latest)
                    ])
                self.cat_widgets['All'].entry_add(name, date, latest, url)
                filename = cst.new_data_file()
                cst.save_data(filename, latest, data)
                FEEDS.set(name, 'url', url)
                FEEDS.set(name, 'updated', date)
                FEEDS.set(name, 'data', filename)
                FEEDS.set(name, 'visible', 'True')
                FEEDS.set(name, 'geometry', '')
                FEEDS.set(name, 'position', 'normal')
                FEEDS.set(name, 'category', '')
                FEEDS.set(name, 'sort_is_reversed', 'False')
                FEEDS.set(name, 'active', 'True')
                cst.save_feeds()
                self.queues[name] = queue
                self.feed_widgets[name] = FeedWidget(self, name)
                self.menu_feeds.add_checkbutton(
                    label=name, command=lambda: self.toggle_feed_widget(name))
                cst.add_trace(self.feed_widgets[name].variable, 'write',
                              lambda *args: self.feed_widget_trace(name))
                self.feed_widgets[name].variable.set(True)
                for entry_title, date, summary, link in data:
                    self.feed_widgets[name].entry_add(entry_title, date,
                                                      summary, link, -1)
            else:
                if manager_queue is not None:
                    manager_queue.put('')
                if cst.internet_on():
                    logging.error('%s is not a valid feed.', url)
                    showerror(_('Error'),
                              _('{url} is not a valid feed.').format(url=url))
                else:
                    logging.warning('No Internet connection.')
                    showerror(_('Error'), _('No Internet connection.'))

    def feed_add(self, url, manager=False):
        """
        Add feed with given url.

        manager: whether this command is run from the feed manager.
        """
        if url:
            queue = Queue(1)
            manager_queue = Queue(1) if manager else None
            thread = Process(target=self.feed_get_info,
                             args=(url, queue, 'all'),
                             daemon=True)
            thread.start()
            self._check_result_add(thread, queue, url, manager_queue)
            if manager:
                return manager_queue

    def feed_set_active(self, title, active):
        FEEDS.set(title, 'active', str(active))
        cst.save_feeds()
        cat = FEEDS.get(title, 'category', fallback='')
        if active:
            self.menu_feeds.enable_item(title)
            if FEEDS.getboolean(title, 'visible'):
                self.feed_widgets[title].deiconify()
            if cat != '':
                self.cat_widgets[cat].show_feed(title)
            self.cat_widgets['All'].show_feed(title)
            self._feed_update(title)
        else:
            self.menu_feeds.disable_item(title)
            self.feed_widgets[title].withdraw()
            if cat != '':
                self.cat_widgets[cat].hide_feed(title)
            self.cat_widgets['All'].hide_feed(title)

    def feed_change_cat(self, title, old_cat, new_cat):
        if old_cat != new_cat:
            FEEDS.set(title, 'category', new_cat)
            if old_cat != '':
                self.cat_widgets[old_cat].remove_feed(title)
            if new_cat != '':
                if new_cat not in LATESTS.sections():
                    LATESTS.add_section(new_cat)
                    LATESTS.set(new_cat, 'visible', 'True')
                    LATESTS.set(new_cat, 'geometry', '')
                    LATESTS.set(new_cat, 'position', 'normal')
                    LATESTS.set(new_cat, 'sort_order', 'A-Z')
                    self.cat_widgets[new_cat] = CatWidget(self, new_cat)
                    self.cat_widgets[new_cat].event_generate('<Configure>')
                    self.menu_categories.add_checkbutton(
                        label=new_cat,
                        command=lambda: self.toggle_category_widget(new_cat))
                    cst.add_trace(self.cat_widgets[new_cat].variable, 'write',
                                  lambda *args: self.cat_widget_trace(new_cat))
                    self.cat_widgets[new_cat].variable.set(True)
                else:
                    try:
                        filename = FEEDS.get(title, 'data')
                        latest = cst.feed_get_latest(filename)
                    except (configparser.NoOptionError,
                            pickle.UnpicklingError):
                        latest = ''
                    self.cat_widgets[new_cat].entry_add(
                        title, FEEDS.get(title, 'updated'), latest,
                        FEEDS.get(title, 'url'))

    def feed_rename(self, old_name, new_name):
        options = {
            opt: FEEDS.get(old_name, opt)
            for opt in FEEDS.options(old_name)
        }
        FEEDS.remove_section(old_name)
        try:
            # check if feed's title already exists
            FEEDS.add_section(new_name)
        except configparser.DuplicateSectionError:
            i = 2
            duplicate = True
            while duplicate:
                # increment i until new_name~#i does not already exist
                try:
                    FEEDS.add_section("{}~#{}".format(new_name, i))
                except configparser.DuplicateSectionError:
                    i += 1
                else:
                    duplicate = False
                    name = "{}~#{}".format(new_name, i)
        else:
            name = new_name
        logging.info("Renamed feed '%s' to '%s'", old_name, name)
        for opt, val in options.items():
            FEEDS.set(name, opt, val)
        self._check_result_init_id[name] = self._check_result_init_id.pop(
            old_name, '')
        self._check_result_update_id[name] = self._check_result_update_id.pop(
            old_name, '')
        self.threads[name] = self.threads.pop(old_name, None)
        self.queues[name] = self.queues.pop(old_name)
        self.feed_widgets[name] = self.feed_widgets.pop(old_name)
        self.feed_widgets[name].rename_feed(name)
        self.cat_widgets['All'].rename_feed(old_name, name)
        category = FEEDS.get(name, 'category', fallback='')
        if category != '':
            self.cat_widgets[category].rename_feed(old_name, name)
        self.menu_feeds.delete(old_name)
        self.menu_feeds.add_checkbutton(
            label=name, command=lambda: self.toggle_feed_widget(name))
        trace_info = cst.info_trace(self.feed_widgets[name].variable)
        if trace_info:
            cst.remove_trace(self.feed_widgets[name].variable, 'write',
                             trace_info[0][1])
        cst.add_trace(self.feed_widgets[name].variable, 'write',
                      lambda *args: self.feed_widget_trace(name))
        self.menu_feeds.set_item_value(name,
                                       self.feed_widgets[name].variable.get())

        cst.save_feeds()
        return name

    def feed_remove(self, title):
        self.feed_widgets[title].destroy()
        del self.queues[title]
        try:
            del self.threads[title]
        except KeyError:
            pass
        del self.feed_widgets[title]
        try:
            del self._check_result_init_id[title]
        except KeyError:
            pass
        try:
            del self._check_result_update_id[title]
        except KeyError:
            pass
        try:
            os.remove(os.path.join(cst.PATH_DATA, FEEDS.get(title, 'data')))
        except FileNotFoundError:
            pass
        self.menu_feeds.delete(title)
        logging.info("Removed feed '%s' %s", title, FEEDS.get(title, 'url'))
        category = FEEDS.get(title, 'category', fallback='')
        self.cat_widgets['All'].remove_feed(title)
        if category != '':
            self.cat_widgets[category].remove_feed(title)
        FEEDS.remove_section(title)

    def feed_manage(self):
        dialog = Manager(self)
        self.wait_window(dialog)
        self.update_idletasks()
        cst.save_latests()
        if dialog.change_made:
            cst.save_feeds()
            self.feed_update()

    def feed_init(self):
        """Update feeds."""
        for title in FEEDS.sections():
            if FEEDS.getboolean(title, 'active', fallback=True):
                logging.info("Updating feed '%s'", title)
                self.threads[title] = Process(target=self.feed_get_info,
                                              args=(FEEDS.get(title, 'url'),
                                                    self.queues[title], 'all'),
                                              daemon=True)
                self.threads[title].start()
                self._check_result_init(title)
        self._check_end_update_id = self.after(2000, self._check_end_update)

    def _check_result_init(self, title):
        if self.threads[title].is_alive():
            self._check_result_init_id[title] = self.after(
                1000, self._check_result_init, title)
        else:
            t, latest, updated, data = self.queues[title].get()
            if not t:
                if cst.internet_on():
                    run([
                        "notify-send", "-i", "dialog-error",
                        _("Error"),
                        _('{url} is not a valid feed.').format(
                            url=FEEDS.get(title, 'url'))
                    ])
                    logging.error('%s is not a valid feed.',
                                  FEEDS.get(title, 'url'))
                else:
                    if self._notify_no_internet:
                        run([
                            "notify-send", "-i", "dialog-error",
                            _("Error"),
                            _('No Internet connection.')
                        ])
                        logging.warning('No Internet connection')
                        self._notify_no_internet = False
                        self._internet_id = self.after(30000,
                                                       self.test_connection)
                    after_ids = [
                        self._update_id, self._check_add_id,
                        self._check_end_update_id, self._update_id
                    ]
                    after_ids.extend(self._check_result_update_id.values())
                    after_ids.extend(self._check_result_init_id.values())
                    for after_id in after_ids:
                        try:
                            self.after_cancel(after_id)
                        except ValueError:
                            pass
            else:
                date = datetime.strptime(updated, '%Y-%m-%d %H:%M')
                if (date > datetime.strptime(FEEDS.get(title, 'updated'),
                                             '%Y-%m-%d %H:%M')
                        or not FEEDS.has_option(title, 'data')):
                    if CONFIG.getboolean("General",
                                         "notifications",
                                         fallback=True):
                        run([
                            "notify-send", "-i", cst.IM_ICON_SVG, title,
                            cst.html2text(latest)
                        ])
                    FEEDS.set(title, 'updated', updated)
                    category = FEEDS.get(title, 'category', fallback='')
                    self.cat_widgets['All'].update_display(
                        title, latest, updated)
                    if category != '':
                        self.cat_widgets[category].update_display(
                            title, latest, updated)
                    logging.info("Updated feed '%s'", title)
                    self.feed_widgets[title].clear()
                    for entry_title, date, summary, link in data:
                        self.feed_widgets[title].entry_add(
                            entry_title, date, summary, link, -1)
                    logging.info("Populated widget for feed '%s'", title)
                    self.feed_widgets[title].event_generate('<Configure>')
                    self.feed_widgets[title].sort_by_date()
                    try:
                        filename = FEEDS.get(title, 'data')
                    except configparser.NoOptionError:
                        filename = cst.new_data_file()
                        FEEDS.set(title, 'data', filename)
                        cst.save_feeds()
                    cst.save_data(filename, latest, data)
                else:
                    logging.info("Feed '%s' is up-to-date", title)

    def _feed_update(self, title):
        """Update feed with given title."""
        logging.info("Updating feed '%s'", title)
        self.threads[title] = Process(target=self.feed_get_info,
                                      args=(FEEDS.get(title, 'url'),
                                            self.queues[title]),
                                      daemon=True)
        self.threads[title].start()
        self._check_result_update(title)

    def feed_update(self):
        """Update all feeds."""
        try:
            self.after_cancel(self._update_id)
        except ValueError:
            pass
        for thread in self.threads.values():
            try:
                thread.terminate()
            except AttributeError:
                pass
        self.threads.clear()
        for title in FEEDS.sections():
            if FEEDS.getboolean(title, 'active', fallback=True):
                self._feed_update(title)
        self._check_end_update_id = self.after(2000, self._check_end_update)

    def _check_result_update(self, title):
        if self.threads[title].is_alive():
            self._check_result_update_id[title] = self.after(
                1000, self._check_result_update, title)
        else:
            t, latest, updated, entry_title, summary, link = self.queues[
                title].get(False)
            if not t:
                if cst.internet_on():
                    run([
                        "notify-send", "-i", "dialog-error",
                        _("Error"),
                        _('{url} is not a valid feed.').format(
                            url=FEEDS.get(title, 'url'))
                    ])
                    logging.error('%s is not a valid feed.',
                                  FEEDS.get(title, 'url'))
                else:
                    if self._notify_no_internet:
                        run([
                            "notify-send", "-i", "dialog-error",
                            _("Error"),
                            _('No Internet connection.')
                        ])
                        logging.warning('No Internet connection')
                        self._notify_no_internet = False
                        self._internet_id = self.after(30000,
                                                       self.test_connection)
                    after_ids = [
                        self._update_id, self._check_add_id,
                        self._check_end_update_id, self._update_id
                    ]
                    after_ids.extend(self._check_result_update_id.values())
                    after_ids.extend(self._check_result_init_id.values())
                    for after_id in after_ids:
                        try:
                            self.after_cancel(after_id)
                        except ValueError:
                            pass
            else:
                date = datetime.strptime(updated, '%Y-%m-%d %H:%M')
                if date > datetime.strptime(FEEDS.get(title, 'updated'),
                                            '%Y-%m-%d %H:%M'):
                    logging.info("Updated feed '%s'", title)
                    if CONFIG.getboolean("General",
                                         "notifications",
                                         fallback=True):
                        run([
                            "notify-send", "-i", cst.IM_ICON_SVG, title,
                            cst.html2text(latest)
                        ])
                    FEEDS.set(title, 'updated', updated)
                    category = FEEDS.get(title, 'category', fallback='')
                    self.cat_widgets['All'].update_display(
                        title, latest, updated)
                    if category != '':
                        self.cat_widgets[category].update_display(
                            title, latest, updated)
                    self.feed_widgets[title].entry_add(entry_title, updated,
                                                       summary, link, 0)
                    self.feed_widgets[title].sort_by_date()
                    try:
                        filename = FEEDS.get(title, 'data')
                        old, data = cst.load_data(filename)
                    except pickle.UnpicklingError:
                        cst.save_data(filename, latest,
                                      [(entry_title, updated, summary, link)])
                    except configparser.NoOptionError:
                        filename = cst.new_data_file()
                        FEEDS.set(title, 'data', filename)
                        cst.save_data(filename, latest,
                                      [(entry_title, updated, summary, link)])
                    else:
                        data.insert(0, (entry_title, updated, summary, link))
                        cst.save_data(filename, latest, data)
                else:
                    logging.info("Feed '%s' is up-to-date", title)

    def _check_end_update(self):
        b = [t.is_alive() for t in self.threads.values() if t is not None]
        if sum(b):
            self._check_end_update_id = self.after(1000,
                                                   self._check_end_update)
        else:
            cst.save_feeds()
            for widget in self.cat_widgets.values():
                widget.sort()
            self._update_id = self.after(
                CONFIG.getint("General", "update_delay"), self.feed_update)