Exemple #1
0
class AutoCorrectConfig(Frame):
    """Configuration window for autocorrect."""
    def __init__(self, master, app, **kwargs):
        Frame.__init__(self, master, padding=4, **kwargs)
        self.rowconfigure(2, weight=1)
        self.columnconfigure(0, weight=1)
        self.columnconfigure(1, weight=1)

        self.tree = Treeview(self,
                             columns=('replace', 'by'),
                             show='',
                             selectmode='browse')
        scroll_x = AutoScrollbar(self,
                                 orient='horizontal',
                                 command=self.tree.xview)
        scroll_y = AutoScrollbar(self,
                                 orient='vertical',
                                 command=self.tree.yview)
        self.tree.configure(xscrollcommand=scroll_x.set,
                            yscrollcommand=scroll_y.set)

        self.reset()

        self.replace = StringVar(self)
        self.by = StringVar(self)

        add_trace(self.replace, 'write', self._trace_replace)
        add_trace(self.by, 'write', self._trace_by)

        b_frame = Frame(self)
        self.b_add = Button(b_frame, text=_('New'), command=self.add)
        self.b_rem = Button(b_frame, text=_('Delete'), command=self.remove)
        self.b_add.state(('disabled', ))
        self.b_rem.state(('disabled', ))
        self.b_add.pack(pady=4, fill='x')
        self.b_rem.pack(pady=4, fill='x')
        Button(b_frame, text=_('Reset'), command=self.reset).pack(pady=8,
                                                                  fill='x')

        Label(self, text=_('Replace')).grid(row=0,
                                            column=0,
                                            sticky='w',
                                            pady=4)
        Label(self, text=_('By')).grid(row=0, column=1, sticky='w', pady=4)
        Entry(self, textvariable=self.replace).grid(row=1,
                                                    column=0,
                                                    sticky='ew',
                                                    pady=4,
                                                    padx=(0, 4))
        Entry(self, textvariable=self.by).grid(row=1,
                                               column=1,
                                               sticky='ew',
                                               pady=4)
        self.tree.grid(row=2, columnspan=2, sticky='ewsn', pady=(4, 0))
        scroll_x.grid(row=3, columnspan=2, sticky='ew', pady=(0, 4))
        scroll_y.grid(row=2, column=2, sticky='ns', pady=(4, 0))
        b_frame.grid(row=1, rowspan=2, padx=(4, 0), sticky='nw', column=3)

        self.tree.bind('<<TreeviewSelect>>', self._on_treeview_select)

    def _trace_by(self, *args):
        key = self.replace.get().strip()
        val = self.by.get().strip()
        self.by.set(val)
        if key in self.tree.get_children(''):
            if val != self.tree.set(key, 'by'):
                self.b_add.state(('!disabled', ))
            else:
                self.b_add.state(('disabled', ))
        else:
            self.b_add.state(('!disabled', ))
        if not val:
            self.b_add.state(('disabled', ))

    def _trace_replace(self, *args):
        key = self.replace.get().strip()
        val = self.by.get().strip()
        self.replace.set(key)
        if not key:
            self.b_add.state(('disabled', ))
            self.b_rem.state(('disabled', ))
        else:
            self.b_add.state(('!disabled', ))
            sel = self.tree.selection()
            if key in self.tree.get_children(''):
                if key not in sel:
                    self.tree.selection_set(key)
                self.b_add.configure(text=_('Replace'))
                self.b_rem.state(('!disabled', ))
                if val != self.tree.set(key, 'by'):
                    self.b_add.state(('!disabled', ))
                else:
                    self.b_add.state(('disabled', ))
            else:
                self.b_rem.state(('disabled', ))
                self.b_add.configure(text=_('New'))
                if sel:
                    self.tree.selection_remove(*sel)
        if not val:
            self.b_add.state(('disabled', ))

    def _on_treeview_select(self, event):
        sel = self.tree.selection()
        if sel:
            key, val = self.tree.item(sel[0], 'values')
            self.replace.set(key)
            self.by.set(val)

    def reset(self):
        self.tree.delete(*self.tree.get_children(''))
        keys = list(AUTOCORRECT.keys())
        keys.sort()
        for key in keys:
            self.tree.insert('', 'end', key, values=(key, AUTOCORRECT[key]))

    def add(self):
        key = self.replace.get().strip()
        val = self.by.get().strip()
        if key in self.tree.get_children(''):
            self.tree.item(key, values=(key, val))
        elif key and val:
            self.tree.insert('', 'end', key, values=(key, val))

    def remove(self):
        key = self.replace.get()
        if key in self.tree.get_children(''):
            self.tree.delete(key)

    def ok(self):
        keys = self.tree.get_children('')
        AUTOCORRECT.clear()
        for key in keys:
            AUTOCORRECT[key] = self.tree.set(key, 'by')
Exemple #2
0
class Manager(Toplevel):
    def __init__(self, master):
        Toplevel.__init__(self, master, class_=APP_NAME)
        self.title(_("Manage Feeds"))
        self.grab_set()
        self.columnconfigure(0, weight=1)
        self.rowconfigure(0, weight=1)
        self.im_moins = PhotoImage(master=self, file=IM_MOINS)
        self.im_moins_sel = PhotoImage(master=self, file=IM_MOINS_SEL)
        self.im_moins_clicked = PhotoImage(master=self, file=IM_MOINS_CLICKED)
        self.im_plus = PhotoImage(master=self, file=IM_PLUS)

        self._no_edit_entry = self.register(lambda: False)

        self.change_made = False

        self.categories = set(LATESTS.sections())
        self.categories.remove('All')
        self.categories.add('')

        # --- treeview
        self.tree = Treeview(self,
                             columns=('Title', 'URL', 'Category', 'Remove'),
                             style='manager.Treeview',
                             selectmode='none')
        self.tree.heading('Title',
                          text=_('Title'),
                          command=lambda: self._sort_column('Title', False))
        self.tree.heading('URL',
                          text=_('URL'),
                          command=lambda: self._sort_column('URL', False))
        self.tree.heading('Category',
                          text=_('Category'),
                          command=lambda: self._sort_column('Category', False))
        self.tree.column('#0', width=6)
        self.tree.column('Title', width=250)
        self.tree.column('URL', width=350)
        self.tree.column('Category', width=150)
        self.tree.column('Remove', width=20, minwidth=20, stretch=False)

        y_scroll = AutoScrollbar(self,
                                 orient='vertical',
                                 command=self.tree.yview)
        x_scroll = AutoScrollbar(self,
                                 orient='horizontal',
                                 command=self.tree.xview)
        self.tree.configure(xscrollcommand=x_scroll.set,
                            yscrollcommand=y_scroll.set)

        self.tree.bind('<Motion>', self._highlight_active)
        self.tree.bind('<Leave>', self._leave)
        self._last_active_item = None

        # --- populate treeview
        for title in sorted(FEEDS.sections(), key=lambda x: x.lower()):
            item = self.tree.insert('',
                                    'end',
                                    values=(title, FEEDS.get(title, 'url'),
                                            FEEDS.get(title,
                                                      'category',
                                                      fallback=''), ''))
            if FEEDS.getboolean(title, 'active', fallback=True):
                self.tree.selection_add(item)
            self.tree.item(item, tags=item)
            self.tree.tag_configure(item, image=self.im_moins)
            self.tree.tag_bind(
                item,
                '<ButtonRelease-1>',
                lambda event, i=item: self._click_release(event, i))
            self.tree.tag_bind(item,
                               '<ButtonPress-1>',
                               lambda event, i=item: self._press(event, i))
            self.tree.tag_bind(item,
                               '<Double-1>',
                               lambda event, i=item: self._edit(event, i))

        self.tree.grid(row=0, column=0, sticky='ewsn')
        x_scroll.grid(row=1, column=0, sticky='ew')
        y_scroll.grid(row=0, column=1, sticky='ns')
        Button(self,
               image=self.im_plus,
               command=self.feed_add,
               style='manager.TButton').grid(row=2,
                                             column=0,
                                             columnspan=2,
                                             sticky='e',
                                             padx=4,
                                             pady=4)
        self._check_add_id = ''

    def destroy(self):
        try:
            self.after_cancel(self._check_add_id)
        except ValueError:
            pass
        Toplevel.destroy(self)

    def _edit(self, event, item):
        """Edit feed title."""
        column = self.tree.identify_column(event.x)
        if column in ['#1', '#2']:
            bbox = self.tree.bbox(item, column)
            entry = Entry(self.tree)
            entry.place(x=bbox[0],
                        y=bbox[1],
                        width=bbox[2],
                        height=bbox[3],
                        anchor='nw')
            entry.bind('<Escape>', lambda e: entry.destroy())
            entry.bind('<FocusOut>', lambda e: entry.destroy())
            if column == '#1':
                entry.insert(0, self.tree.item(item, 'values')[0])
                entry.configure(style='manager.TEntry')

                def ok(event):
                    name = entry.get()
                    if name:
                        name = self.master.feed_rename(
                            self.tree.set(item, 'Title'), name)
                        self.tree.set(item, 'Title', name)
                    entry.destroy()

                entry.bind('<Return>', ok)
            else:
                entry.insert(0, self.tree.item(item, 'values')[1])
                entry.configure(style='no_edit.TEntry',
                                validate='key',
                                validatecommand=self._no_edit_entry)

            entry.selection_range(0, 'end')
            entry.focus_set()
        elif column == '#3':

            def focus_out(event):
                x, y = self.tree.winfo_pointerxy()
                x0 = combo.winfo_rootx()
                x1 = x0 + combo.winfo_width()
                y0 = combo.winfo_rooty()
                y1 = y0 + combo.winfo_height()
                if not (x0 <= x <= x1 and y0 <= y <= y1):
                    combo.destroy()

            def ok(event):
                category = combo.get().strip()
                self.categories.add(category)
                self.master.feed_change_cat(self.tree.set(item, 'Title'),
                                            self.tree.set(item, 'Category'),
                                            category)
                self.tree.set(item, 'Category', category)
                combo.destroy()

            bbox = self.tree.bbox(item, column)
            cat = list(self.categories)
            combo = AutoCompleteCombobox(self.tree,
                                         values=cat,
                                         allow_other_values=True)
            combo.place(x=bbox[0],
                        y=bbox[1],
                        width=bbox[2],
                        height=bbox[3],
                        anchor='nw')
            combo.bind('<Escape>', lambda e: combo.destroy())
            combo.bind('<FocusOut>', focus_out)
            combo.bind('<Return>', ok)
            combo.bind('<<ComboboxSelected>>', ok)
            combo.current(cat.index(self.tree.set(item, '#3')))

    def _press(self, event, item):
        if self.tree.identify_column(event.x) == '#4':
            self.tree.tag_configure(item, image=self.im_moins_clicked)

    def _click_release(self, event, item):
        """Handle click on items."""
        if self.tree.identify_row(event.y) == item:
            if self.tree.identify_column(event.x) == '#4':
                title = self.tree.item(item, 'values')[0]
                rep = True
                if CONFIG.getboolean('General',
                                     'confirm_remove',
                                     fallback=True):
                    rep = askokcancel(
                        _('Confirmation'),
                        _('Do you want to remove the feed {feed}?').format(
                            feed=title))
                if rep:
                    self.master.feed_remove(title)
                    self.tree.delete(item)
                    self.change_made = True
            elif self.tree.identify_element(
                    event.x, event.y) == 'Checkbutton.indicator':
                sel = self.tree.selection()
                if item in sel:
                    self.tree.selection_remove(item)
                    self.master.feed_set_active(self.tree.set(item, '#1'),
                                                False)
                else:
                    self.tree.selection_add(item)
                    self.master.feed_set_active(self.tree.set(item, '#1'),
                                                True)
                self.change_made = True
        else:
            self.tree.tag_configure(item, image=self.im_moins)

    def _leave(self, event):
        """Remove highlight when mouse leave the treeview."""
        if self._last_active_item is not None:
            self.tree.tag_configure(self._last_active_item,
                                    image=self.im_moins)

    def _highlight_active(self, event):
        """Highlight minus icon under the mouse."""
        if self._last_active_item is not None:
            self.tree.tag_configure(self._last_active_item,
                                    image=self.im_moins)
        if self.tree.identify_column(event.x) == '#4':
            item = self.tree.identify_row(event.y)
            if item:
                self.tree.tag_configure(item, image=self.im_moins_sel)
                self._last_active_item = item
            else:
                self._last_active_item = None
        else:
            self._last_active_item = None

    def _sort_column(self, column, reverse):
        """Sort column by (reversed) alphabetical order."""
        l = [(self.tree.set(c, column), c) for c in self.tree.get_children('')]
        l.sort(reverse=reverse, key=lambda x: x[0].lower())
        for index, (val, c) in enumerate(l):
            self.tree.move(c, "", index)
        self.tree.heading(
            column, command=lambda: self._sort_column(column, not reverse))

    def feed_add(self):
        dialog = Add(self)
        self.wait_window(dialog)
        url = dialog.url
        if url:
            self.configure(cursor='watch')
            queue = self.master.feed_add(url, manager=True)
            self._check_add_id = self.after(1000, self._check_add_finished,
                                            url, queue)

    def _check_add_finished(self, url, queue):

        if queue.empty():
            self._check_add_id = self.after(1000, self._check_add_finished,
                                            url, queue)
        else:
            title = queue.get(False)
            if title:
                item = self.tree.insert('', 'end', values=(title, url, ''))
                self.tree.item(item, tags=item)
                self.tree.tag_configure(item, image=self.im_moins)
                self.tree.tag_bind(
                    item, '<ButtonRelease-1>',
                    lambda event: self._click_release(event, item))
                self.tree.tag_bind(item, '<ButtonPress-1>',
                                   lambda event: self._press(event, item))
                self.tree.tag_bind(item, '<Double-1>',
                                   lambda event: self._edit(event, item))
                self.tree.selection_add(item)

                self.change_made = True
            self.configure(cursor='arrow')
            self.focus_set()
            self.grab_set()
Exemple #3
0
class SearchApplication(GenericFrame):
    FAVICON = "../assets/favicon.ico"

    def __init__(self, parent=None, app_window=None):
        self.lastValue = None
        self.category_option = StringVar("")
        self.column_id = [
            'ProductDescription', 'ManufacturerName', 'ManufacturerPartNumber',
            'DigiKeyPartNumber', 'Category'
        ]
        Frame.__init__(self, parent)
        self.pack()
        self.parent = parent
        self.app_window = app_window
        self.selectedField = None

        self.parent.title("Partlocater - Advanced Database Search")
        self.parent.iconbitmap(self.FAVICON)
        self.menubar = Frame(self, background='white')

        self.menubar.pack(side=TOP, fill=X, expand=YES)
        self.win_frame = Frame(self)
        self.win_frame.pack(side=TOP, fill=BOTH, expand=YES)
        self.editbutton = Menubutton(self.menubar,
                                     text='Edit',
                                     background='grey98')
        self.editbutton.pack(side=LEFT, fill=X)
        self.editmenu = Menu(self.editbutton, tearoff=0)
        self.editbutton.config(menu=self.editmenu)
        self.copySourcesMenu = Menu(self.editbutton, tearoff=0)
        self.editmenu.add_cascade(label='Copy', menu=self.copySourcesMenu)
        self.copySourcesMenu.add_command(label='Part Number',
                                         state=DISABLED,
                                         command=self.on_copy_partnumber)
        self.partnumber_index = 0
        self.copySourcesMenu.add_command(label='Selected Parameter',
                                         state=DISABLED,
                                         command=self.on_copy_parameters)
        self.selectedParameter_index = 1
        self.copySourcesMenu.add_command(label='Selected Part All Parameters',
                                         state=DISABLED,
                                         command=self.on_copy_all_parameters)
        self.allParameters_index = 2
        self.editmenu.add_command(label='Delete Part',
                                  state=DISABLED,
                                  command=self.on_delete)

        self.searchLF = LabelFrame(self.win_frame, text="Search")
        self.searchLF.pack(side=LEFT, fill=X, expand=YES, pady=4, padx=6)
        self.searchLeftF = Frame(self.searchLF)
        self.searchLeftF.pack(side=LEFT, anchor=W)
        self.searchRightF = Frame(self.searchLF)
        self.searchRightF.pack(side=LEFT, anchor=N)
        self.searchLabelWidth = 20
        self.catF = Frame(self.searchLeftF)
        self.catF.pack(side=TOP, anchor=W)
        self.catL = Label(self.catF,
                          text='Category',
                          width=self.searchLabelWidth,
                          anchor=W,
                          justify=LEFT)
        self.catL.pack(side=LEFT, fill=X, expand=YES)
        self.cat = StringVar()
        self.catE = Entry(self.catF,
                          textvariable=self.cat,
                          width=50,
                          state=DISABLED)
        self.catE.config(disabledbackground=self.catE.cget("bg"))
        self.catE.config(disabledforeground=self.catE.cget("fg"))
        self.catE.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.category_option = StringVar()
        self.cat.set("All")
        option_list = ['All', 'All'] + Config().tables
        self.catM = OptionMenu(self.searchRightF,
                               self.category_option,
                               *option_list,
                               command=self.on_category)
        self.catM.pack(side=TOP, anchor=N, fill=X, expand=YES)

        self.manF = Frame(self.searchLeftF)
        self.manF.pack(side=TOP, anchor=W)
        self.manL = Label(self.manF,
                          text='ManufacturerName',
                          width=self.searchLabelWidth,
                          anchor=W,
                          justify=LEFT)
        self.manL.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.man = StringVar()
        self.manE = Entry(self.manF, width=50, textvariable=self.man)
        self.manE.pack(side=LEFT, fill=X, expand=YES, pady=4)

        self.mpnF = Frame(self.searchLeftF)
        self.mpnF.pack(side=TOP, anchor=W)
        self.mpnL = Label(self.mpnF,
                          text='ManufacturerPartNumber',
                          width=self.searchLabelWidth,
                          anchor=W,
                          justify=LEFT)
        self.mpnL.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.mpn = StringVar()
        self.mpnE = Entry(self.mpnF, width=50, textvariable=self.mpn)
        self.mpnE.pack(side=LEFT, fill=X, expand=YES, pady=4)

        self.spnF = Frame(self.searchLeftF)
        self.spnF.pack(side=TOP, anchor=W)
        self.spnL = Label(self.spnF,
                          text='DigiKeyPartNumber',
                          width=self.searchLabelWidth,
                          anchor=W,
                          justify=LEFT)
        self.spnL.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.spn = StringVar()
        self.spnE = Entry(self.spnF, width=50, textvariable=self.spn)
        self.spnE.pack(side=LEFT, fill=X, expand=YES, pady=4)

        self.descF = Frame(self.searchLeftF)
        self.descF.pack(side=TOP, anchor=W)
        self.descL = Label(self.descF,
                           text='ProductDescription',
                           width=self.searchLabelWidth,
                           anchor=W,
                           justify=LEFT)
        self.descL.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.desc = StringVar()
        self.descE = Entry(self.descF, width=50, textvariable=self.desc)
        self.descE.pack(side=LEFT, fill=X, expand=YES, pady=4)
        self.descE.focus_force()

        self.findF = Frame(self.searchLeftF)
        self.findF.pack(side=TOP, anchor=E)
        self.findB = ttk.Button(self.findF,
                                text="Find",
                                width=12,
                                command=lambda event=None: self.do_find(event))
        self.findB.pack(side=LEFT, pady=4)
        self.clearB = ttk.Button(self.findF,
                                 text="Clear",
                                 width=6,
                                 command=self.on_clear_search)
        self.clearB.pack(side=LEFT, pady=4)

        self.partsLF = LabelFrame(self, text="Found Components")
        self.partsLF.pack(side=TOP, fill=X, expand=YES, pady=4, padx=4)
        self.partsF = Frame(self.partsLF)
        self.partsF.pack(side=TOP, pady=4, padx=4)
        # change treeview for search here
        self.partsTV = Treeview(self.partsF,
                                selectmode=BROWSE,
                                show='tree headings',
                                columns=self.column_id)

        self.partsTV.bind('<Double-Button-1>', self.on_edit_item)
        self.partsTV.bind('<<TreeviewSelect>>', self.fieldChanged)
        self.partsTV.bind('<Escape>', self.clearSelection)
        self.partsTV.bind('<MouseWheel>', self.mousewheel)
        self.partsTV.bind('<Button-4>', self.mousewheel)
        self.partsTV.bind('<Button-5>', self.mousewheel)
        vcmd = (self.register(self.validateEntry), '%P')
        self.editfield = ttk.Entry(self.partsTV,
                                   validate='key',
                                   validatecommand=vcmd)
        self.editfield.bind('<Return>', self.updateField)
        self.editfield.bind('<Escape>', self.clearSelection)

        self.partsTV.bind('<Control-c>', self.on_copy_element)
        self.partsTV.column("#0", minwidth=0, width=18, stretch=NO)
        for t in self.column_id:
            self.partsTV.heading(t, text=Config().parameter[t])
        self.partsTV.column('Category', width=60)
        self.scrollbar = Scrollbar(self.partsF,
                                   orient='vertical',
                                   command=self.partsTV.yview)
        self.scrollbar.pack(side=RIGHT, fill=Y, expand=YES, anchor=E)
        self.partsTV.configure(yscroll=self.scrollbar.set)
        self.scrollbar.config(command=self.yview)

        self.partsTV.pack(side=TOP, anchor=W, fill=X, expand=YES)
        self.partsTV.delete(*self.partsTV.get_children())
        # end change of treeview
        # change the following to menu item
        #self.part_buttonF = Frame(self.partsLF)
        #self.delete_partB = ttk.Button(self.partsLF, text="Delete Part from Database", command=self.on_delete,
        #state=DISABLED)
        #self.delete_partB.pack(side=RIGHT, anchor=W, expand=NO, pady=4, padx=6)
        #self.partsB = ttk.Button(self.partsLF, text="Copy Selected To Part Find", command=self.on_copy, state=DISABLED)
        #self.partsB.pack(side=RIGHT, anchor=W, expand=NO, pady=4, padx=6)
        #self.part_buttonF.pack(side=BOTTOM)
        # start remove vvv
        #self.element_labelframe = LabelFrame(self, text="Modify Name/Value")
        #self.element_labelframe.pack(side=TOP, fill=X, expand=YES, pady=4, padx=6)
        #self.element_frame = Frame(self.element_labelframe)
        #self.element_frame.pack(side=TOP)

        #self.element_name = StringVar()
        #self.element_label = Label(self.element_frame, textvariable=self.element_name, width=30, anchor=W, justify=LEFT)
        #self.element_label.pack(side=LEFT, anchor=W, fill=X, expand=YES, pady=4)
        #self.element_value = StringVar()
        #self.element_entry = Entry(self.element_frame, width=50, textvariable=self.element_value)
        #self.element_entry.pack(side=LEFT, fill=X, expand=YES, pady=4)
        #self.default_color = self.element_entry.cget('background')

        #self.element_update = ttk.Button(self.element_frame, text="Update", command=self.on_update_element,
        #state=DISABLED)
        #self.element_update.pack(side=LEFT, fill=X, expand=YES, pady=4)
        #self.element_cancel = ttk.Button(self.element_frame, text="Cancel", command=self.on_clear_element,
        #state=DISABLED)
        #self.element_cancel.pack(side=LEFT, fill=X, expand=YES, pady=4)
        # end remove ^^^

        self.statusLF = LabelFrame(self, text="Status")
        self.statusLF.pack(side=BOTTOM, fill=X, expand=YES, pady=4, padx=6)
        self.statusF = Frame(self.statusLF)
        self.statusF.pack(side=TOP, fill=X, expand=YES, padx=6)
        self.status = self.StatusBar(self.statusF, self)

    def validateEntry(self, P):
        if (len(P) <= 120):
            return True
        else:
            self.bell()
            return False

    # scroll bar event
    def yview(self, *args):
        if self.selectedField is not None:
            self.editfield.place_forget()
            self.selectedField = None
        self.partsTV.yview(*args)

    # mousewheel and button4/5 event
    def mousewheel(self, event):
        if self.selectedField is not None:
            self.editfield.place_forget()
            self.selectedField = None

    # escape event in treeview or editfield
    def clearSelection(self, event):
        self.editfield.place_forget()
        self.selectedField = None
        self.partsTV.selection_remove(self.partsTV.selection())
        self.status.set("")

    # double button event
    def on_edit_item(self, event):
        if self.partsTV.parent(self.partsTV.selection()
                               ) == '':  # testing should not edit a parent
            self.selectedField = None
            return
        if (self.partsTV.identify_region(event.x, event.y) == 'cell'):
            self.selectedField = self.partsTV.identify_row(event.y)
            x, y, width, height = self.partsTV.bbox(self.selectedField, '#2')
            v = self.partsTV.set(self.selectedField, 1)
            self.editfield.pack()
            self.editfield.delete(0, len(self.editfield.get()))
            self.editfield.insert(0, v)
            self.editfield.selection_range(0, 'end')
            self.editfield.focus_force()
            self.editfield.place(x=x, y=y, width=width, height=height)

    # find button event
    def on_find(self):
        category = self.cat.get()
        search_list = []
        col_list = []
        search_str = self.man.get()
        if not (validate(search_str)):
            raise Exception("Invalid Manufacture Name")
        search_list.append(search_str)
        col_list.append(Config().parameter['ManufacturerName'])
        search_str = self.mpn.get()
        if not (validate(search_str)):
            raise Exception("Invalid Manufacture Part Number")
        search_list.append(search_str)
        col_list.append(Config().parameter['ManufacturerPartNumber'])
        search_str = self.spn.get()
        if not (validate(search_str)):
            raise Exception("Invalid Supplier Part Number")
        search_list.append(search_str)
        col_list.append(Config().parameter['DigiKeyPartNumber'])
        search_str = self.desc.get().split()
        if not (validate(search_str)):
            raise Exception("Invalid Description")
        search_list += search_str
        col_list.append(Config().parameter['ProductDescription'])
        select = "SELECT * FROM `" + Config().loaded_db.name + "`."
        where = "WHERE"
        like = ""
        i = 0
        for item in search_list:
            if len(item) > 0:
                item = item.replace('%', '\\%')
                item = item.replace('"', '')
                item = item.replace("'", "")
                if i < 3:
                    like += where + " `" + col_list[
                        i] + "` LIKE '" + item + "%'"
                else:
                    like += where + " (`" + col_list[i] + "` LIKE '" + item + "%' OR `" + \
                            col_list[i] + "` LIKE '% " + item + "%')"
                where = " AND"
            i = i + 1 if (i < 3) else i
        self.partsTV.delete(*self.partsTV.get_children())
        count = 0
        if category == "All":
            for table in Config().tables:
                qry = select + "`" + table + "` " + like
                result = Config().loaded_db.query(qry)
                for record in result:
                    v = []
                    spn = record[Config().parameter['DigiKeyPartNumber']]
                    count += 1
                    for id in self.column_id:
                        if id == 'Category':
                            v.append(table)
                        else:
                            v.append(record[Config().parameter[id]])
                    id = self.partsTV.insert('',
                                             'end',
                                             iid=spn,
                                             text=spn,
                                             values=v)
                    for params in record:
                        if record[params] is not None:
                            self.partsTV.insert(id,
                                                'end',
                                                text=spn,
                                                values=(params,
                                                        record[params]))
        else:
            qry = select + "`" + category + "` " + like
            result = Config().loaded_db.query(qry)
            for record in result:
                v = []
                count += 1
                spn = record[Config().parameter['DigiKeyPartNumber']]
                for id in self.column_id:
                    if id == 'Category':
                        v.append(category)
                    else:
                        v.append(record[Config().parameter[id]])
                id = self.partsTV.insert('',
                                         'end',
                                         iid=spn,
                                         text=spn,
                                         values=v)
                for params in record:
                    if record[params] is not None:
                        self.partsTV.insert(id,
                                            'end',
                                            text=spn,
                                            values=(params, record[params]))
        self.status.set(("No" if count == 0 else str(count)) + " items found")

    # return event
    def updateField(self, event):
        value = self.editfield.get()
        self.editfield.place_forget()
        name = self.partsTV.item(self.selectedField, "text")
        if not validate(value):
            self.status.seterror("Invalid value, must not have quotes")
            return
        self.partsTV.set(self.selectedField, "#2", value)
        key = self.partsTV.set(self.selectedField, "#1")
        self.editfield.place_forget()
        element_parent = self.partsTV.parent(self.selectedField)
        table_name = self.partsTV.item(
            element_parent, "values")[self.column_id.index('Category')]
        part_number = self.partsTV.item(
            element_parent,
            "values")[self.column_id.index('DigiKeyPartNumber')]
        set_param = "SET `" + key + "` = '" + value + "' "
        where = "WHERE `" + Config(
        ).parameter['DigiKeyPartNumber'] + "` = '" + part_number + "'"
        qry = "UPDATE `" + Config(
        ).loaded_db.name + "`.`" + table_name + "` " + set_param + where
        print(qry)
        try:
            Config().loaded_db.query(qry)
        except Exception as e:
            self.status.seterror("Database query failed: %s", e)
            return
        self.status.set("Changed " + key + " to " + value + " for part " +
                        part_number + ".")
        self.partsTV.see(self.selectedField)

    # clear button in search frame
    def on_clear_search(self):
        self.man.set("")
        self.mpn.set("")
        self.spn.set("")
        self.desc.set("")
        self.cat.set("All")
        self.category_option.set("All")
        self.partsTV.delete(*self.partsTV.get_children())

    def do_flash(self):
        current_color = self.element_entry.cget("background")
        if current_color == self.default_color:
            self.element_entry.config(background="red")
        else:
            self.element_entry.config(background=self.default_color)
            return
        self.after(250, self.do_flash)

    # category option menu
    def on_category(self, value):
        self.catE.config(state=NORMAL)
        self.cat.set(value)
        self.catE.config(state=DISABLED)

    #def on_copy(self):
    #selected = self.partsTV.selection()[0]
    #key = self.partsTV.item(selected, "values")[self.column_id.index('DigiKeyPartNumber')]
    #self.app_window.part_num_string.set(key)
    #self.status.set("Part Number '" + key + "' copied to Part Find")
    # Edit -> Delete menu
    def on_delete(self):
        selected = self.partsTV.selection()[0]
        key = self.partsTV.item(
            selected, "values")[self.column_id.index('DigiKeyPartNumber')]
        table = self.partsTV.item(selected,
                                  "values")[self.column_id.index('Category')]
        if messagebox.askokcancel(
                "Delete", "Click OK if you really want to delete '" + key +
                "' from database?"):
            Config().loaded_db.query("DELETE FROM `" + table + "` WHERE `" +
                                     Config().parameter['DigiKeyPartNumber'] +
                                     "` = '" + key + "'")
            self.status.set("Part Number '" + key + "' deleted from database")

    # treeview select event
    def fieldChanged(self, event):
        selected = self.partsTV.selection()
        if len(selected) > 0:
            self.copySourcesMenu.entryconfig(self.partnumber_index,
                                             state=NORMAL)
            self.copySourcesMenu.entryconfig(self.allParameters_index,
                                             state=NORMAL)
        else:
            self.copySourcesMenu.entryconfig(self.partnumber_index,
                                             state=DISABLED)
            self.copySourcesMenu.entryconfig(self.allParameters_index,
                                             state=DISABLED)
            return
        if self.partsTV.parent(selected) == '':
            self.copySourcesMenu.entryconfig(self.selectedParameter_index,
                                             state=DISABLED)
        else:
            self.copySourcesMenu.entryconfig(self.selectedParameter_index,
                                             state=NORMAL)
        if selected != self.selectedField:
            self.editfield.place_forget()
            self.selectedField = None

    def on_copy_parameters(self):
        selected = self.partsTV.selection()
        if len(selected) == 0 or self.partsTV.parent(selected) == '':
            return
        try:
            property = self.partsTV.item(selected, "values")
            self.parent.clipboard_clear()
            self.parent.clipboard_append(property[0] + '\t' + property[1])
            self.parent.update()
            self.status.set(property[0] + ' ' + property[1] +
                            " copied to clipboard")
        except Exception as e:
            pass

    def on_copy_partnumber(self):
        selected = self.partsTV.selection()
        if len(selected) == 0 or self.partsTV.parent(selected) == '':
            return
        try:
            if self.partsTV.parent(selected) != '':
                selected = self.partsTV.parent(selected)
            partnumber = self.partsTV.item(
                selected, "values")[self.column_id.index('DigiKeyPartNumber')]
            self.parent.clipboard_clear()
            self.parent.clipboard_append(partnumber)
            self.parent.update()
            self.status.set(" '" + partnumber + "' copied to clipboard")
        except Exception as e:
            pass

    def on_copy_all_parameters(self):
        selected = self.partsTV.selection()
        if len(selected) == 0:
            return
        try:
            if self.partsTV.parent(selected) != '':
                selected = self.partsTV.parent(selected)
            partnumber = self.partsTV.item(
                selected, "values")[self.column_id.index('DigiKeyPartNumber')]
            elements = self.partsTV.get_children(selected)
            self.parent.clipboard_clear()
            self.parent.clipboard_clear()
            for i in elements:
                element = self.partsTV.item(i, "values")
                self.parent.clipboard_append(element[0] + "\t" + element[1] +
                                             "\n")
            self.parent.update()
            self.status.set("All properties of " + partnumber +
                            " copied to clipboard")
        except Exception as e:
            pass

        # deprecate
    def on_copy_element(self, event):
        try:
            selected = self.partsTV.selection()[0]
            if self.partsTV.parent(selected) == '':
                partnumber = self.partsTV.item
                elements = self.partsTV.get_children(selected)
                self.parent.clipboard_clear()
                for i in elements:
                    element = self.partsTV.item(i, "values")
                    self.parent.clipboard_append(element[0] + "\t" +
                                                 element[1] + "\n")
                self.parent.update()
                self.status.set("All properties of " +
                                self.partsTV.item(selected, "values")[3] +
                                " copied to clipboard")
            else:
                key = self.partsTV.item(selected, "values")[0]
                val = self.partsTV.item(selected, "values")[1]
                self.parent.clipboard_clear()
                self.parent.clipboard_append(val)
                self.parent.update()
                self.status.set(key + " '" + val + "' copied to clipboard")
        except Exception as e:
            pass

    def do_find(self, event):
        try:
            self.on_find()
        except Exception as e:
            self.status.seterror(e)
Exemple #4
0
class Multicolumn_Listbox(Frame):
    _style_index = 0

    class List_Of_Rows(object):
        def __init__(self, multicolumn_listbox):
            self._multicolumn_listbox = multicolumn_listbox

        def data(self, index):
            return self._multicolumn_listbox.row_data(index)

        def get(self, index):
            return Row(self._multicolumn_listbox, index)

        def insert(self, data, index=None):
            self._multicolumn_listbox.insert_row(data, index)

        def delete(self, index):
            self._multicolumn_listbox.delete_row(index)

        def update(self, index, data):
            self._multicolumn_listbox.update_row(index, data)

        def select(self, index):
            self._multicolumn_listbox.select_row(index)

        def deselect(self, index):
            self._multicolumn_listbox.deselect_row(index)

        def set_selection(self, indices):
            self._multicolumn_listbox.set_selection(indices)

        def __getitem__(self, index):
            return self.get(index)

        def __setitem__(self, index, value):
            return self._multicolumn_listbox.update_row(index, value)

        def __delitem__(self, index):
            self._multicolumn_listbox.delete_row(index)

        def __len__(self):
            return self._multicolumn_listbox.number_of_rows

    class List_Of_Columns(object):
        def __init__(self, multicolumn_listbox):
            self._multicolumn_listbox = multicolumn_listbox

        def data(self, index):
            return self._multicolumn_listbox.get_column(index)

        def get(self, index):
            return Column(self._multicolumn_listbox, index)

        def delete(self, index):
            self._multicolumn_listbox.delete_column(index)

        def update(self, index, data):
            self._multicolumn_listbox.update_column(index, data)

        def __getitem__(self, index):
            return self.get(index)

        def __setitem__(self, index, value):
            return self._multicolumn_listbox.update_column(index, value)

        def __delitem__(self, index):
            self._multicolumn_listbox.delete_column(index)

        def __len__(self):
            return self._multicolumn_listbox.number_of_columns

    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)

    @property
    def row_height(self):
        return self._rowheight

    @property
    def font(self):
        return self._cell_font

    def configure_column(self,
                         index,
                         width=None,
                         minwidth=None,
                         anchor=None,
                         stretch=None):
        kwargs = {}
        for config_name in ("width", "anchor", "stretch", "minwidth"):
            config_value = locals()[config_name]
            if config_value is not None:
                kwargs[config_name] = config_value

        self.interior.column('#%s' % (index + 1), **kwargs)

    def row_data(self, index):
        try:
            item_ID = self.interior.get_children()[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        return self.item_ID_to_row_data(item_ID)

    def update_row(self, index, data):
        try:
            item_ID = self.interior.get_children()[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        if len(data) == len(self._columns):
            self.interior.item(item_ID, values=data)
        else:
            raise ValueError("The multicolumn listbox has only %d columns" %
                             self._number_of_columns)

    def delete_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        self.interior.delete(item_ID)
        self._number_of_rows -= 1

        if self._stripped_rows:
            for i in range(index, self._number_of_rows):
                self.interior.tag_configure(list_of_items[i + 1],
                                            background=self._stripped_rows[i %
                                                                           2])

    def insert_row(self, data, index=None):
        if len(data) != self._number_of_columns:
            raise ValueError("The multicolumn listbox has only %d columns" %
                             self._number_of_columns)

        if index is None:
            index = self._number_of_rows - 1

        item_ID = self.interior.insert('', index, values=data)
        self.interior.item(item_ID, tags=item_ID)

        self._number_of_rows += 1

        if self._stripped_rows:
            list_of_items = self.interior.get_children()

            self.interior.tag_configure(item_ID,
                                        background=self._stripped_rows[index %
                                                                       2])

            for i in range(index + 1, self._number_of_rows):
                self.interior.tag_configure(list_of_items[i],
                                            background=self._stripped_rows[i %
                                                                           2])

    def column_data(self, index):
        return [
            self.interior.set(child_ID, index)
            for child_ID in self.interior.get_children('')
        ]

    def update_column(self, index, data):
        for i, item_ID in enumerate(self.interior.get_children()):
            data_row = self.item_ID_to_row_data(item_ID)
            data_row[index] = data[i]

            self.interior.item(item_ID, values=data_row)

        return data

    def clear(self):
        # Another possibility:
        #  self.interior.delete(*self.interior.get_children())

        for row in self.interior.get_children():
            self.interior.delete(row)

        self._number_of_rows = 0

    def update(self, data):
        self.clear()

        for row in data:
            self.insert_row(row)

    def focus(self, index=None):
        if index is None:
            return self.interior.item(self.interior.focus())
        else:
            item = self.interior.get_children()[index]
            self.interior.focus(item)

    def state(self, state=None):
        if state is None:
            return self.interior.state()
        else:
            self.interior.state(state)

    @property
    def number_of_rows(self):
        return self._number_of_rows

    @property
    def number_of_columns(self):
        return self._number_of_columns

    def toogle_selection(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        self.interior.selection_toggle(item_ID)

    def select_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        self.interior.selection_add(item_ID)

    def deselect_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d" % index)

        self.interior.selection_remove(item_ID)

    def deselect_all(self):
        self.interior.selection_remove(self.interior.selection())

    def set_selection(self, indices):
        list_of_items = self.interior.get_children()

        self.interior.selection_set(" ".join(list_of_items[row_index]
                                             for row_index in indices))

    @property
    def selected_rows(self):
        data = []
        for item_ID in self.interior.selection():
            data_row = self.item_ID_to_row_data(item_ID)
            data.append(data_row)

        return data

    @property
    def indices_of_selected_rows(self):
        list_of_indices = []
        for index, item_ID in enumerate(self.interior.get_children()):
            if item_ID in self.interior.selection():
                list_of_indices.append(index)

        return list_of_indices

    def delete_all_selected_rows(self):
        selected_items = self.interior.selection()
        for item_ID in selected_items:
            self.interior.delete(item_ID)

        number_of_deleted_rows = len(selected_items)
        self._number_of_rows -= number_of_deleted_rows

        return number_of_deleted_rows

    def _on_select(self, event):
        for item_ID in event.widget.selection():
            data_row = self.item_ID_to_row_data(item_ID)
            self._command(data_row)

    def item_ID_to_row_data(self, item_ID):
        item = self.interior.item(item_ID)
        return item["values"]

    @property
    def table_data(self):
        data = []

        for item_ID in self.interior.get_children():
            data_row = self.item_ID_to_row_data(item_ID)
            data.append(data_row)

        return data

    @table_data.setter
    def table_data(self, data):
        self.update(data)

    def cell_data(self, row, column):
        """Get the value of a table cell"""
        try:
            item = self.interior.get_children()[row]
        except IndexError:
            raise ValueError("Row index out of range: %d" % row)

        return self.interior.set(item, column)

    def update_cell(self, row, column, value):
        """Set the value of a table cell"""

        item_ID = self.interior.get_children()[row]

        data = self.item_ID_to_row_data(item_ID)

        data[column] = value
        self.interior.item(item_ID, values=data)

    def __getitem__(self, index):
        if isinstance(index, tuple):
            row, column = index
            return self.cell_data(row, column)
        else:
            raise Exception("Row and column indices are required")

    def __setitem__(self, index, value):
        if isinstance(index, tuple):
            row, column = index
            self.update_cell(row, column, value)
        else:
            raise Exception("Row and column indices are required")

    def bind(self, event, handler):
        self.interior.bind(event, handler)

    def sort_by(self, col, descending):
        """
        sort tree contents when a column header is clicked
        """
        # grab values to sort
        data = [(self.interior.set(child_ID, col), child_ID)
                for child_ID in self.interior.get_children('')]

        # if the data to be sorted is numeric change to float
        try:
            data = [(float(number), child_ID) for number, child_ID in data]
        except ValueError:
            pass

        # now sort the data in place
        data.sort(reverse=descending)
        for idx, item in enumerate(data):
            self.interior.move(item[1], '', idx)

        # switch the heading so that it will sort in the opposite direction
        self.interior.heading(
            col, command=lambda col=col: self.sort_by(col, not descending))

        if self._stripped_rows:
            list_of_items = self.interior.get_children('')
            for i in range(len(list_of_items)):
                self.interior.tag_configure(list_of_items[i],
                                            background=self._stripped_rows[i %
                                                                           2])

    def destroy(self):
        self.interior.destroy()

    def item_ID(self, index):
        return self.interior.get_children()[index]
Exemple #5
0
class StaffFrame(LabelFrame):
    """Contains data of staffmeeteng"""
    def __init__(self, window, base, **kwargs):
        super().__init__(window, **kwargs)
        self.base = base
        self.staff_id = None
        self.all_staff = [i.name for i in Staff.select()]
        self.data_label = Label(self, text="Data zespołu")
        self.data_label.grid(row=0, column=0, padx=5, pady=1, sticky='e')
        self.data_entry = Entry(self, width=20)
        self.data_entry.grid(row=0, column=1, padx=5, pady=1, sticky='w')

        self.table = Treeview(self, columns=("name", "speciality"))
        self.table.heading('#1', text='imię i nazwisko')
        self.table.heading('#2', text='specjalizacja')
        self.table.column('#1', width=200)
        self.table.column('#2', width=200)

        self.table.grid(row=1,
                        column=0,
                        rowspan=1,
                        columnspan=2,
                        padx=5,
                        pady=5)
        self.table['show'] = 'headings'
        self.table.bind('<<TreeviewSelect>>', self.on_treeview_selected)

        self.another_stuff_frame = LabelFrame(self, text="Inni specjaliści")
        self.another_stuff_frame.grid(row=2, column=0, columnspan=2, pady=5)

        self.another_staff = Listbox(self.another_stuff_frame,
                                     width=48,
                                     height=12)
        self.another_staff.grid(row=0, column=0, rowspan=2, padx=5, pady=5)
        self.another_staff.bind('<<ListboxSelect>>', self.on_listbox_select)
        self.add_button = Button(self,
                                 text="Dodaj członka",
                                 command=self.add_member)
        self.add_button.grid(row=3, column=0, padx=5, pady=5)
        self.delete_button = Button(self,
                                    text="Usuń członka",
                                    command=self.remove_member)
        self.delete_button.grid(row=3, column=1, padx=5, pady=5)

    def get_staff_from_table(self):
        '''return list of member of staff_meeting from table
        eg. [[name, speciality],[name, speciality]]'''
        staff_meeting_list = []
        for child in self.table.get_children():
            staff_meeting_list.append(self.table.item(child)["values"])
        return staff_meeting_list

    def insert_staff(self, base, staff):
        self.base = base
        self.staff = staff
        self.staff_id = staff['id']
        if not self.staff_id:
            self.tables_tidying()
            return
        self.data_entry.delete(0, 'end')
        self.data_entry.insert(0, staff['date'])
        self.table.delete(*self.table.get_children())
        self.another_staff.delete(0, 'end')
        for i in staff['team']:
            self.table.insert('', 'end', values=(i[0], i[1]))
        self.tables_tidying()

    def on_listbox_select(self, event):
        '''Remove selection from table after clicking listbox.'''
        if self.table.selection():
            self.table.selection_remove(self.table.selection()[0])

    def on_treeview_selected(self, event):
        '''Remove selection from lisbox after clicking table'''
        self.another_staff.select_clear(0, 'end')

    def tables_tidying(self):
        '''Remove unused staff from table.'''
        self.another_staff.delete(0, 'end')
        used_staff = [x[0] for x in self.get_staff_from_table()]
        for member in Staff.select():
            if member.name not in used_staff:
                self.another_staff.insert('end', member.name)

    def add_member(self):
        '''Add member from listbox to table.'''
        if not self.another_staff.curselection():
            pass
        else:
            self.table.insert(
                '',
                'end',
                values=(self.another_staff.selection_get(),
                        Staff.get(Staff.name == self.another_staff.
                                  selection_get()).speciality))

            self.tables_tidying()

    def remove_member(self):
        '''Removes member from table to listbox.'''
        selected_item = self.table.selection()
        if selected_item:
            self.table.delete(selected_item)
        self.tables_tidying()

    def clear(self):
        self.table.delete(*self.table.get_children())
        self.tables_tidying()
class App(Tk):
    DEFAULT_SIGN_WIDTH = 150.0
    DEFAULT_SIGN_HEIGHT = 22.0
    DEFAULT_SHEET_WIDHT = 300.0
    DEFAULT_SHEET_HEIGHT = 300.0
    DEFAULT_SHEETS_PER_FILE = 0
    MAX_SHEET_WIDTH = 470
    MAX_SHEET_HEIGHT = 310
    MAX_SHEETS_PER_FILE = 100
    SPINBOX_WIDTH = 8
    PADDING = 2
    DXF_VERSIONS = ('R2000', 'R2004', 'R2007', 'R2010', 'R2013', 'R2018')

    # Initialize GUI layout.
    def __init__(self) -> None:
        super().__init__()
        self.title('KylttiMaker')
        self.minsize(640, 480)

        # Tree widget that displays fields and their relative marks in a hierarchy.
        self.tree = Treeview(self, selectmode='browse')
        self.tree.heading('#0', text='Fields', command=self.remove_selection)
        self.tree.bind('<Button-3>', self.tree_right_click)
        self.tree.bind('<<TreeviewSelect>>', self.tree_selection_changed)
        self.tree.bind('<Double-Button-1>', self.rename)
        self.bind('<Escape>', self.remove_selection)
        self.bind('<Delete>', self.remove)
        self.tree.pack(side=LEFT, fill=BOTH)
        self.properties = LabelFrame(self, text='Properties')
        self.properties.pack(side=RIGHT, fill=BOTH, expand=1)
        self.fields = {}
        self.selected_iid = None

        # Entry field that get's temporarily shown to the user whilst renaming a field or a mark.
        self.new_name = StringVar(self.tree)
        self.new_name_entry = Entry(self.tree, textvariable=self.new_name)
        self.new_name_entry.bind('<Key-Return>', self.new_name_entered)

        # Output options that get's shown to the user when nothing else is selected from the hierarchy.
        self.frame = Frame(self.properties)
        Label(self.frame, text='Sheet size').grid(column=0,
                                                  row=0,
                                                  sticky='E',
                                                  pady=App.PADDING)
        self.sheet_width_var = StringVar(self.frame)
        self.sheet_width_var.set(App.DEFAULT_SHEET_WIDHT)
        Spinbox(self.frame,
                to=App.MAX_SHEET_WIDTH,
                textvariable=self.sheet_width_var,
                width=App.SPINBOX_WIDTH).grid(column=1, row=0, sticky='WE')
        Label(self.frame, text='x').grid(column=2, row=0)
        self.sheet_height_var = StringVar(self.frame)
        self.sheet_height_var.set(App.DEFAULT_SHEET_HEIGHT)
        Spinbox(self.frame,
                to=App.MAX_SHEET_HEIGHT,
                textvariable=self.sheet_height_var,
                width=App.SPINBOX_WIDTH).grid(column=3, row=0, sticky='WE')
        Label(self.frame, text='Sign size').grid(column=0,
                                                 row=1,
                                                 sticky='E',
                                                 pady=App.PADDING)
        self.sign_width_var = StringVar(self.frame)
        self.sign_width_var.set(App.DEFAULT_SIGN_WIDTH)
        Spinbox(self.frame,
                to=App.MAX_SHEET_WIDTH,
                textvariable=self.sign_width_var,
                width=App.SPINBOX_WIDTH).grid(column=1, row=1, sticky='WE')
        Label(self.frame, text='x').grid(column=2, row=1)
        self.sign_height_var = StringVar(self.frame)
        self.sign_height_var.set(App.DEFAULT_SIGN_HEIGHT)
        Spinbox(self.frame,
                to=App.MAX_SHEET_HEIGHT,
                textvariable=self.sign_height_var,
                width=App.SPINBOX_WIDTH).grid(column=3, row=1, sticky='WE')
        Label(self.frame, text='Layers per sheet').grid(column=0,
                                                        row=2,
                                                        sticky='W',
                                                        pady=App.PADDING)
        self.layers_per_sheet_var = StringVar(self.frame)
        self.layers_per_sheet_var.set(App.DEFAULT_SHEETS_PER_FILE)
        Spinbox(self.frame,
                to=App.MAX_SHEETS_PER_FILE,
                textvariable=self.layers_per_sheet_var,
                width=App.SPINBOX_WIDTH).grid(column=1, row=2, sticky='WE')
        Label(self.frame, text='(0 = No limit)').grid(column=2,
                                                      row=2,
                                                      columnspan=2,
                                                      sticky='W')
        Label(self.frame, text='DXF version').grid(column=0,
                                                   row=4,
                                                   sticky='E',
                                                   pady=App.PADDING)
        self.dxf_version = StringVar(self.frame)
        OptionMenu(self.frame, self.dxf_version, App.DXF_VERSIONS[0],
                   *App.DXF_VERSIONS).grid(column=1, row=4, sticky='W')
        Button(self.frame, text='Create',
               command=self.create).grid(column=2, row=4, columnspan=2)
        self.frame.pack()

    # Display a popup menu with relevant options when right clicking on the tree widget item.
    def tree_right_click(self, event: Event) -> None:
        menu = Menu(self, tearoff=0)
        iid = self.tree.identify_row(event.y)
        if iid:
            if iid in self.fields:
                menu.add_command(label='Add QR',
                                 command=lambda: self.add_mark(QR, iid))
                menu.add_command(label='Add Text',
                                 command=lambda: self.add_mark(Text, iid))
                menu.add_command(label='Add Hole',
                                 command=lambda: self.add_mark(Hole, iid))
            menu.add_command(label='Rename',
                             command=lambda: self.rename(iid=iid))
            menu.add_command(label='Remove',
                             command=lambda: self.remove(iid=iid))
        else:
            menu.add_command(label='Add field', command=self.add_field)
        menu.tk_popup(event.x_root, event.y_root)

    # Display the properties of the selected item.
    def tree_selection_changed(self, event: Event) -> None:
        # Hide the items previously shown in the properties pane.
        self.new_name_entry.place_forget()
        for child in self.properties.winfo_children():
            child.pack_forget()

        selected_items = self.tree.selection()
        if selected_items:
            self.selected_iid = selected_items[0]
            # Check if the selected item is a field or a mark object, in which case show its properties.
            if self.selected_iid in self.fields:
                self.fields[self.selected_iid].frame.pack()
            else:
                for field_iid in self.fields:
                    if self.selected_iid in self.fields[field_iid].marks:
                        self.fields[field_iid].marks[
                            self.selected_iid].frame.pack()
        else:
            # Clear the properties pane.
            self.selected_iid = None
            self.frame.pack()

    # Create a new field object and add a corresponding node to the hierarchy.
    def add_field(self) -> None:
        iid = self.tree.insert('', END, text='Field')
        self.fields[iid] = Field(self.properties)

    # Display a entry for the user to input a new name for the item to be renamed.
    def rename(self, event: Event = None, iid: int = None) -> None:
        if not iid:
            if self.selected_iid:
                iid = self.selected_iid
            else:
                return
        self.editing_iid = iid
        self.new_name.set(self.tree.item(iid)['text'])
        self.new_name_entry.place(x=20, y=0)
        self.new_name_entry.focus_set()
        self.new_name_entry.select_range(0, END)

    # Display the renamed item in the hierarchy.
    def new_name_entered(self, event: Event) -> None:
        self.tree.item(self.editing_iid, text=self.new_name.get())
        self.new_name_entry.place_forget()

    # Link a new mark speciefied by mark_type parameter to the field speciefied by field_iid parameter.
    def add_mark(self,
                 mark_type: Union[QR, Text, Hole],
                 field_iid: int = None) -> None:
        if not field_iid:
            if self.selected_iid in self.fields:
                field_iid = self.selected_iid
            else:
                print('Select a field first.')
                return
        iid = self.tree.insert(field_iid, END, text=mark_type.__name__)
        self.fields[field_iid].marks[iid] = mark_type(self.properties)
        self.tree.see(iid)

    # Remove a tree item speciefied by iid parameter, else removes the currently selected item.
    def remove(self, event: Event = None, iid: int = None) -> None:
        if not iid:
            if self.selected_iid:
                iid = self.selected_iid
            else:
                print('Select something first.')
                return
        # Check if the item to be removed is a field item, else check if it is a mark item.
        if iid in self.fields:
            self.remove_selection()
            self.tree.delete(iid)
            del self.fields[iid]
        else:
            for field_iid in self.fields:
                if iid in self.fields[field_iid].marks:
                    self.remove_selection()
                    self.tree.delete(iid)
                    del self.fields[field_iid].marks[iid]

    # Clear the selection.
    def remove_selection(self, event: Event = None) -> None:
        for item in self.tree.selection():
            self.tree.selection_remove(item)

    # Create sheets according to entered settings.
    def create(self) -> None:
        if not self.fields:
            print('No fields.')
            return

        # Calculate the length of the longest field (some fields can have less values than others).
        total_signs = 0
        for field_iid in self.fields:
            total_signs = max(total_signs, len(self.fields[field_iid].data))
        if total_signs == 0:
            print('No fields with data.')
            return
        try:
            sheet_width = float(self.sheet_width_var.get())
            sheet_height = float(self.sheet_height_var.get())
            sign_width = float(self.sign_width_var.get())
            sign_height = float(self.sign_height_var.get())
            layers_per_sheet = int(self.layers_per_sheet_var.get())
            assert sign_width > 0, 'Sign width must be greater than 0.'
            assert sign_height > 0, 'Sign height must be greater than 0.'
            assert sheet_width >= sign_width, 'Sheet width must be greater than sign width.'
            assert sheet_height >= sign_height, 'Sheet height must be greater than sign height.'
        except ValueError:
            print('Invalid dimensions.')
            return
        except AssertionError as e:
            print(e)
            return

        # Show progress bar.
        progress_bar = Progressbar(self.frame)
        progress_bar.grid(column=0, row=5, columnspan=4, sticky='WE')

        # Calculate the needed values to define sheet layout.
        signs_per_row = int(sheet_width // sign_width)
        signs_per_column = int(sheet_height // sign_height)
        signs_per_layer = signs_per_row * signs_per_column
        # Ceiling division.
        total_layers = -int(-total_signs // signs_per_layer)
        if layers_per_sheet > 0:
            total_sheets = -int(-total_layers // layers_per_sheet)
        else:
            total_sheets = 1

        print(
            f'Marking total of {total_signs} signs ({sign_width} x {sign_height}).'
        )
        print(
            f'Sheet size of {sheet_width} x {sheet_height} fits {signs_per_row} x {signs_per_column} signs,'
        )
        print(
            f'so the effective sheet size is {signs_per_row * sign_width} x {signs_per_column * sign_height}.'
        )
        print(f'Total of {total_layers} layer(s) are needed.')
        if layers_per_sheet == 0:
            print(
                'There is no limit on the maximum amount of layers per sheet,')
        else:
            print(
                f'There are maximum of {layers_per_sheet} layer(s) per sheet,')
        print(f'so total of {total_sheets} sheet(s) are needed.')

        # Create needed sheet objects.
        print('Creating sheets.')
        sheets = []
        for _ in range(total_sheets):
            sheets.append(ezdxf.new(self.dxf_version.get()))

        # Iterate over all layers and draw their outline based on how many signs that layer will have.
        print('Drawing layer outlines.')
        for layer in range(total_layers):
            max_x = sign_width * signs_per_row
            max_y = -sign_height * signs_per_column
            if layer == total_layers - 1:  # If last layer.
                signs_in_last_sheet = total_signs - layer * signs_per_layer
                if signs_in_last_sheet < signs_per_row:
                    max_x = sign_width * signs_in_last_sheet
                max_y = sign_height * (-signs_in_last_sheet // signs_per_row)
            if layers_per_sheet > 0:
                sheet_index = layer // layers_per_sheet
            else:
                sheet_index = 0
            # Draw layer outline (left and top side bounds).
            sheets[sheet_index].modelspace().add_lwpolyline(
                [(0, max_y), (0, 0), (max_x, 0)],
                dxfattribs={'layer': str(layer)})

        # Iterate over each sign.
        print('Drawing marks.')
        for sign_index in range(total_signs):
            # Update progress bar value.
            progress_bar['value'] = (sign_index + 1) / total_signs * 100
            progress_bar.update()

            # Calculate in which position, in which layer of which sheet the current sign should be drawn.
            layer = sign_index // signs_per_layer
            layer_position = sign_index % signs_per_layer
            sign_origin_x = (layer_position % signs_per_row) * sign_width
            sign_origin_y = -(layer_position // signs_per_row) * sign_height
            if layers_per_sheet > 0:
                sheet_index = layer // layers_per_sheet
            else:
                sheet_index = 0
            sheet = sheets[sheet_index]

            # Draw marks (QR, Text and Hole objects).
            for field_iid in self.fields:
                try:
                    self.fields[field_iid].draw(sign_index, sheet, layer,
                                                sign_origin_x, sign_origin_y,
                                                sign_width, sign_height)
                except Exception as e:
                    print(e)
                    progress_bar.grid_forget()
                    return

            # Draw sign outline (right and bottom side bounds).
            sign_outline = [(sign_origin_x, sign_origin_y - sign_height),
                            (sign_origin_x + sign_width,
                             sign_origin_y - sign_height),
                            (sign_origin_x + sign_width, sign_origin_y)]
            sheet.modelspace().add_lwpolyline(sign_outline,
                                              dxfattribs={'layer': str(layer)})

        # Save sheets.
        # Get a output directory if there are multiple sheets to be saved, otherwise get path for the single output (.dxf) file.
        print('Saving.')
        if total_sheets > 1:
            if directory := tkinter.filedialog.askdirectory():
                for index, sheet in enumerate(sheets):
                    # Indicate save progress.
                    progress_bar['value'] = (index + 1) / len(sheets) * 100
                    progress_bar.update()
                    sheet.saveas(Path(directory) / f'sheet{index}.dxf')
        elif path := tkinter.filedialog.asksaveasfilename(
                defaultextension='.dxf',
                filetypes=(('DXF', '*.dxf'), ('All files', '*.*'))):
            sheets[0].saveas(path)
Exemple #7
0
class FormChildTemplate:
    def __init__(self, frm_parent, connection):
        self.connection = connection
        self.directive = Message()
        self.decide_template = True
        self.id_selected = 0
        self.frm_child_list = LabelFrame(frm_parent)
        self.frm_child_crud = LabelFrame(frm_parent)
        self.frm_child_crud.config(fg=TEXT_COLOR, font=SUBTITLE_FONT)
        self.initialize_components()

    def initialize_components(self):
        """
        Method that initialize the visual components for each form associated with the local administration
        """
        # Resources for the Forms
        self.new_icon = PhotoImage(file=r"./Resources/create.png")
        self.modify_icon = PhotoImage(file=r"./Resources/modify.png")
        self.remove_icon = PhotoImage(file=r"./Resources/delete.png")
        self.save_icon = PhotoImage(file=r"./Resources/save.png")
        self.cancel_icon = PhotoImage(file=r"./Resources/cancel.png")
        self.add_icon = PhotoImage(file=r"./Resources/right.png")
        self.delete_icon = PhotoImage(file=r"./Resources/left.png")
        self.up_arrow = PhotoImage(file=r"./Resources/up_arrow.png")
        self.down_arrow = PhotoImage(file=r"./Resources/down_arrow.png")
        self.star_icon = PhotoImage(file=r"./Resources/star.png")
        self.back_icon = PhotoImage(file=r"./Resources/back.png")
        self.view_icon = PhotoImage(file=r"./Resources/view.png")

        # Components for List FRM
        lbl_sep1 = Label(self.frm_child_list)
        lbl_sep1.grid(row=0, column=0, padx=10, pady=25)
        self.trv_available = Treeview(self.frm_child_list,
                                      height=20,
                                      columns=('N', 'Name'))
        self.trv_available.heading('#0', text='ID', anchor=CENTER)
        self.trv_available.heading('#1', text='N', anchor=CENTER)
        self.trv_available.heading('#2', text='Name', anchor=CENTER)
        self.trv_available.column('#0', width=0, minwidth=50, stretch=NO)
        self.trv_available.column('#1', width=20, minwidth=20, stretch=NO)
        self.trv_available.column('#2', width=375, minwidth=375, stretch=NO)
        self.trv_available.bind("<ButtonRelease-1>",
                                self.select_template_summary)
        self.trv_available.grid(row=0, column=1, sticky=W, pady=25)
        vsb_trv_av = Scrollbar(self.frm_child_list,
                               orient="vertical",
                               command=self.trv_available.yview)
        vsb_trv_av.grid(row=0, column=2, pady=25, sticky=NS)
        self.trv_available.configure(yscrollcommand=vsb_trv_av.set)
        frm_aux4 = Frame(self.frm_child_list)
        btn_new = Button(frm_aux4, image=self.new_icon, command=self.click_new)
        btn_new.grid(row=0, column=0, pady=5, padx=5, sticky=E)
        btn_new_ttp = CreateToolTip(btn_new, 'New template')
        btn_view = Button(frm_aux4,
                          image=self.view_icon,
                          command=self.click_view)
        btn_view.grid(row=1, column=0, pady=5, padx=5, sticky=E)
        btn_view_ttp = CreateToolTip(btn_new, 'View template')
        btn_edit = Button(frm_aux4,
                          image=self.modify_icon,
                          command=self.click_update)
        btn_edit.grid(row=2, column=0, pady=5, padx=5, sticky=E)
        btn_edit_ttp = CreateToolTip(btn_edit, 'Edit template')
        btn_delete = Button(frm_aux4,
                            image=self.remove_icon,
                            command=self.click_delete)
        btn_delete.grid(row=3, column=0, pady=5, padx=5, sticky=E)
        btn_delete_ttp = CreateToolTip(btn_delete, 'Delete template')
        frm_aux4.grid(row=0, column=3, pady=25, padx=25, sticky=NW)
        sep_template = Separator(self.frm_child_list, orient=VERTICAL)
        sep_template.grid(row=0, column=4, sticky=NS, padx=25)
        frm_aux3 = Frame(self.frm_child_list)
        lbl_sep3 = Label(frm_aux3)
        lbl_sep3.grid(row=0, column=0, padx=10, pady=25, rowspan=3)
        lbl_details = Label(frm_aux3, text='Details')
        lbl_details.config(fg=TEXT_COLOR, font=SUBTITLE_FONT)
        lbl_details.grid(row=0, column=1, sticky=W, pady=25, columnspan=2)
        self.txt_summary = Text(frm_aux3, height=22, width=50)
        self.txt_summary.config(font=TEXT_FONT, bg=DISABLED_COLOR)
        self.txt_summary.grid(row=1, column=1)
        vsb_txt_sum = Scrollbar(frm_aux3,
                                orient="vertical",
                                command=self.txt_summary.yview)
        vsb_txt_sum.grid(row=1, column=2, sticky=NS)
        self.txt_summary.configure(yscrollcommand=vsb_txt_sum.set)
        lbl_sep4 = Label(frm_aux3)
        lbl_sep4.grid(row=0, column=3, padx=10, pady=25, rowspan=3)
        lbl_sep5 = Label(frm_aux3)
        lbl_sep5.grid(row=2, column=1, pady=5, columnspan=2)
        frm_aux3.grid(row=0, column=5)

        # Components for CRUD FRM
        lbl_sep6 = Label(self.frm_child_crud)
        lbl_sep6.grid(row=0, column=0, padx=10, pady=25, rowspan=10)
        lbl_name = Label(self.frm_child_crud, text='Name*')
        lbl_name.config(fg=TEXT_COLOR, font=LABEL_FONT)
        lbl_name.grid(row=0, column=1, pady=25, sticky=NW)
        lbl_description = Label(self.frm_child_crud, text='Description*')
        lbl_description.config(fg=TEXT_COLOR, font=LABEL_FONT)
        lbl_description.grid(row=0, column=6, pady=25, sticky=NW)
        lbl_sep3 = Label(self.frm_child_crud)
        lbl_sep3.grid(row=0, column=2, padx=10, pady=25)
        self.txt_name = Entry(self.frm_child_crud, width=30, font=TEXT_FONT)
        self.txt_name.grid(row=0, column=3, pady=25, sticky=NW)
        lbl_sep4 = Label(self.frm_child_crud)
        lbl_sep4.grid(row=0, column=7, padx=10, pady=25)
        self.txt_description = Text(self.frm_child_crud, height=5, width=49)
        self.txt_description.config(font=TEXT_FONT)
        self.txt_description.grid(row=0, column=8, pady=25, sticky=W)
        vsb_txt_desc = Scrollbar(self.frm_child_crud,
                                 orient="vertical",
                                 command=self.txt_description.yview)
        vsb_txt_desc.grid(row=0, column=9, pady=25, sticky=NS)
        self.txt_description.configure(yscrollcommand=vsb_txt_desc.set)
        lbl_sep7 = Label(self.frm_child_crud)
        lbl_sep7.grid(row=0, column=5, padx=10, pady=25, rowspan=3)
        lbl_sep8 = Label(self.frm_child_crud)
        lbl_sep8.grid(row=0, column=10, padx=10, pady=25, rowspan=2)
        lbl_available_d = Label(self.frm_child_crud, text='Available sections')
        lbl_available_d.config(fg=TEXT_COLOR, font=LABEL_FONT)
        lbl_available_d.grid(row=1, column=1, pady=10, sticky=W, columnspan=4)
        lbl_selected_d = Label(self.frm_child_crud, text='Selected sections*')
        lbl_selected_d.config(fg=TEXT_COLOR, font=LABEL_FONT)
        lbl_selected_d.grid(row=1, column=6, pady=10, sticky=W, columnspan=4)
        self.trv_available_sections = Treeview(self.frm_child_crud,
                                               height=10,
                                               columns=('N', 'Name',
                                                        'Data Type'))
        self.trv_available_sections.heading('#0', text='ID', anchor=CENTER)
        self.trv_available_sections.heading('#1', text='N', anchor=CENTER)
        self.trv_available_sections.heading('#2', text='Name', anchor=CENTER)
        self.trv_available_sections.heading('#3',
                                            text='Data Type',
                                            anchor=CENTER)
        self.trv_available_sections.column('#0',
                                           width=0,
                                           minwidth=20,
                                           stretch=NO)
        self.trv_available_sections.column('#1',
                                           width=20,
                                           minwidth=20,
                                           stretch=NO)
        self.trv_available_sections.column('#2',
                                           width=150,
                                           minwidth=150,
                                           stretch=NO)
        self.trv_available_sections.column('#3',
                                           width=120,
                                           minwidth=120,
                                           stretch=NO)
        self.trv_available_sections.bind("<Button-1>",
                                         self.click_trv_asections)
        self.trv_available_sections.grid(row=2,
                                         column=1,
                                         rowspan=7,
                                         columnspan=3,
                                         sticky=W,
                                         pady=10)
        vsb_trv_avs = Scrollbar(self.frm_child_crud,
                                orient="vertical",
                                command=self.trv_available_sections.yview)
        vsb_trv_avs.grid(row=2, column=4, rowspan=7, pady=10, sticky=NS)
        self.trv_available_sections.configure(yscrollcommand=vsb_trv_avs.set)
        self.trv_selected_sections = Treeview(self.frm_child_crud,
                                              height=10,
                                              columns=('N', 'Name',
                                                       'Data type',
                                                       'Mandatory', 'Main'))
        self.trv_selected_sections.heading('#0', text='ID', anchor=CENTER)
        self.trv_selected_sections.heading('#1', text='N', anchor=CENTER)
        self.trv_selected_sections.heading('#2', text='Name', anchor=CENTER)
        self.trv_selected_sections.heading('#3',
                                           text='Data type',
                                           anchor=CENTER)
        self.trv_selected_sections.heading('#4',
                                           text='Mandatory',
                                           anchor=CENTER)
        self.trv_selected_sections.heading('#5', text='Main', anchor=CENTER)
        self.trv_selected_sections.column('#0',
                                          width=0,
                                          minwidth=20,
                                          stretch=NO)
        self.trv_selected_sections.column('#1',
                                          width=20,
                                          minwidth=20,
                                          stretch=NO)
        self.trv_selected_sections.column('#2',
                                          width=150,
                                          minwidth=150,
                                          stretch=NO)
        self.trv_selected_sections.column('#3',
                                          width=120,
                                          minwidth=120,
                                          stretch=NO)
        self.trv_selected_sections.column('#4',
                                          width=80,
                                          minwidth=80,
                                          stretch=NO)
        self.trv_selected_sections.column('#5',
                                          width=80,
                                          minwidth=80,
                                          stretch=NO)
        self.trv_selected_sections.bind("<Button-1>", self.click_trv_ssections)
        self.trv_selected_sections.bind("<Double-1>",
                                        self.click_switch_mandatory)
        self.trv_selected_sections.grid(row=2,
                                        column=6,
                                        rowspan=7,
                                        columnspan=3,
                                        sticky=W,
                                        pady=10)
        vsb_trv_ses = Scrollbar(self.frm_child_crud,
                                orient="vertical",
                                command=self.trv_selected_sections.yview)
        vsb_trv_ses.grid(row=2, column=9, rowspan=7, pady=10, sticky=NS)
        self.trv_selected_sections.configure(yscrollcommand=vsb_trv_ses.set)
        self.lbl_note_optional = Label(
            self.frm_child_crud,
            text=
            'NOTES:\tTo switch between optional and mandatory, double click '
            'on selected section.\n\tChoose one or up to three main sections '
            'by first selecting the target sections\n\tand then clicking the '
            'star button.\n')
        self.lbl_note_optional.config(fg=TEXT_COLOR,
                                      font=NOTE_FONT,
                                      justify=LEFT)
        self.btn_add = Button(self.frm_child_crud,
                              image=self.add_icon,
                              command=self.click_add)
        btn_add_ttp = CreateToolTip(self.btn_add, 'Add section')
        self.btn_remove = Button(self.frm_child_crud,
                                 image=self.delete_icon,
                                 command=self.click_remove)
        btn_remove_ttp = CreateToolTip(self.btn_remove, 'Remove section')
        self.btn_main_section = Button(self.frm_child_crud,
                                       image=self.star_icon,
                                       command=self.click_main_section)
        btn_main_section_ttp = CreateToolTip(self.btn_main_section,
                                             'Main section(s)')
        self.btn_up = Button(self.frm_child_crud,
                             image=self.up_arrow,
                             command=self.click_up)
        btn_up_ttp = CreateToolTip(self.btn_up, 'Move up')
        self.btn_down = Button(self.frm_child_crud,
                               image=self.down_arrow,
                               command=self.click_down)
        btn_down_ttp = CreateToolTip(self.btn_down, 'Move down')
        sep_aux1 = Separator(self.frm_child_crud, orient=VERTICAL)
        sep_aux1.grid(row=0, column=11, sticky=NS, rowspan=10)
        frm_aux1 = Frame(self.frm_child_crud)
        self.btn_save = Button(frm_aux1,
                               image=self.save_icon,
                               command=self.click_save)
        btn_save_ttp = CreateToolTip(self.btn_save, 'Save template')
        self.btn_back = Button(frm_aux1,
                               image=self.back_icon,
                               command=self.click_back)
        btn_back_ttp = CreateToolTip(self.btn_back, 'Go back')
        self.btn_cancel = Button(frm_aux1,
                                 image=self.cancel_icon,
                                 command=self.click_cancel)
        btn_cancel_ttp = CreateToolTip(self.btn_cancel, 'Cancel')
        frm_aux1.grid(row=0,
                      column=12,
                      pady=10,
                      padx=25,
                      sticky=NW,
                      rowspan=10)

    def retrieve_list(self):
        # Remove existing elements in the list
        for item in self.trv_available.get_children():
            self.trv_available.delete(item)
        self.directive = Message(action=37)
        self.connection = self.directive.send_directive(self.connection)
        # Adding elements into the list
        for index, item in enumerate(self.connection.message.information):
            elements = item.split('¥')
            self.trv_available.insert('',
                                      'end',
                                      text=elements[0],
                                      values=(index + 1,
                                              summarize_text(elements[1],
                                                             375)))
        if len(self.trv_available.get_children()) != 0:
            self.trv_available.selection_set(
                self.trv_available.get_children()[0])
            self.select_template_summary()

    def select_template_summary(self, event=None):
        """
        Function activated when the event of selecting an item in the available templates TV is generated. It fills the
        summary text box with information of the selected template
        :param event:
        """
        if self.trv_available.item(
                self.trv_available.selection())['text'] != '':
            # Clear summary txt box
            self.txt_summary['state'] = NORMAL
            self.txt_summary.delete('1.0', 'end-1c')
            self.id_selected = int(
                self.trv_available.item(self.trv_available.selection())
                ['text'])  # Retrieve id of selected item from TreeView
            self.directive = Message(action=40,
                                     information=[self.id_selected
                                                  ])  # ask for the template
            self.connection = self.directive.send_directive(self.connection)
            # Insert template's name and description
            self.txt_summary.insert(
                'end-1c',
                "Name:\n{}\n\n".format(self.connection.message.information[0]))
            self.txt_summary.insert(
                'end-1c', "Description:\n{}\n\nSections:\n".format(
                    self.connection.message.information[1]))
            self.directive = Message(action=77, information=[
                self.id_selected
            ])  # ask for the sections of the selected template
            self.connection = self.directive.send_directive(self.connection)
            # Adding elements in the summary text box
            index = 0
            for item in self.connection.message.information:
                elements = item.split('¥')
                self.txt_summary.insert('end-1c', "{}) {}\t\t{}\t\t{}\n".format(index + 1, elements[3], 'optional' \
                    if elements[7] == '' else 'mandatory', '' if elements[8] == '' else '(MAIN)'))
                index += 1
            self.txt_summary['state'] = DISABLED

    def show_frm(self):
        self.retrieve_list()
        self.frm_child_list.grid(row=1,
                                 column=0,
                                 columnspan=9,
                                 rowspan=8,
                                 pady=10,
                                 padx=10)

    def hide_frm(self):
        self.clear_fields()
        self.frm_child_list.grid_forget()
        self.frm_child_crud.grid_forget()

    def click_new(self):
        self.view_decision = False  # Decision when viewing a template
        self.template = Template()
        self.retrieve_sections()
        self.txt_name.focus_set()
        self.show_cu_buttons()
        self.frm_child_crud['text'] = 'New template'
        self.frm_child_list.grid_forget()
        self.frm_child_crud.grid(row=1,
                                 column=0,
                                 columnspan=9,
                                 rowspan=8,
                                 pady=10,
                                 padx=10)

    def click_view(self):
        if len(self.trv_available.selection()) == 1:
            self.view_decision = True  # Decision when viewing a template
            self.directive = Message(action=40, information=[self.id_selected])
            self.connection = self.directive.send_directive(self.connection)
            self.template = Template(
                id=self.id_selected,
                name=self.connection.message.information[0],
                description=self.connection.message.information[1],
                sections=self.connection.message.information[2])
            self.txt_name.insert(0, self.template.name)
            self.txt_description.insert('1.0', self.template.description)
            self.retrieve_sections(self.template.sections)
            self.txt_name.focus_set()
            self.disable_visual_components()
            self.btn_back.grid(row=0, column=0, padx=5, pady=5, sticky=W)
            self.frm_child_list.grid_forget()
            self.frm_child_crud['text'] = 'View template'
            self.frm_child_crud.grid(row=1,
                                     column=0,
                                     columnspan=9,
                                     rowspan=8,
                                     pady=10,
                                     padx=10)
        else:
            messagebox.showwarning(parent=self.frm_child_list,
                                   title='No selection',
                                   message='You must select one item')

    def click_update(self):
        if len(self.trv_available.selection()) == 1:
            self.view_decision = False  # Decision when viewing a template
            self.directive = Message(
                action=40, information=[self.id_selected, 'validate'])
            self.connection = self.directive.send_directive(self.connection)
            if self.connection.message.action == 5:  # An error ocurred while trying to update the item
                messagebox.showerror(
                    parent=self.frm_child_list,
                    title='Can not edit the template',
                    message=self.connection.message.information[0])
            else:
                self.template = Template(
                    id=self.id_selected,
                    name=self.connection.message.information[0],
                    description=self.connection.message.information[1],
                    sections=self.connection.message.information[2])
                self.txt_name.insert(0, self.template.name)
                self.txt_description.insert('1.0', self.template.description)
                self.retrieve_sections(self.template.sections)
                self.txt_name.focus_set()
                self.show_cu_buttons()
                self.frm_child_crud['text'] = 'Update template'
                self.frm_child_list.grid_forget()
                self.frm_child_crud.grid(row=1,
                                         column=0,
                                         columnspan=9,
                                         rowspan=8,
                                         pady=10,
                                         padx=10)
        else:
            messagebox.showwarning(parent=self.frm_child_list,
                                   title='No selection',
                                   message='You must select one item')

    def click_delete(self):
        if len(self.trv_available.selection()) == 1:
            decision = messagebox.askyesno(
                parent=self.frm_child_list,
                title='Confirmation',
                message='Are you sure you want to delete the item?')
            if decision:
                self.directive = Message(action=39,
                                         information=[self.id_selected])
                self.connection = self.directive.send_directive(
                    self.connection)
                if self.connection.message.action == 5:  # An error ocurred while deleting the item
                    messagebox.showerror(
                        parent=self.frm_child_list,
                        title='Can not delete the item',
                        message=self.connection.message.information[0])
                else:
                    self.retrieve_list()
        else:
            messagebox.showwarning(parent=self.frm_child_list,
                                   title='No selection',
                                   message='You must select one item')

    def show_cu_buttons(self):
        self.btn_add.grid(row=5, column=5, padx=25)
        self.btn_remove.grid(row=6, column=5, padx=25)
        self.btn_main_section.grid(row=2, column=10, padx=25)
        self.btn_down.grid(row=6, column=10, padx=25)
        self.btn_up.grid(row=5, column=10, padx=25)
        self.btn_save.grid(row=0, column=0, padx=5, pady=5, sticky=W)
        self.btn_cancel.grid(row=1, column=0, padx=5, pady=5, sticky=W)
        self.lbl_note_optional.grid(row=9, column=6, columnspan=4, sticky=W)

    def disable_visual_components(self):
        self.txt_name['bg'] = DISABLED_COLOR
        self.txt_description['bg'] = DISABLED_COLOR
        self.txt_name['state'] = DISABLED
        self.txt_description['state'] = DISABLED
        self.btn_add.grid_forget()
        self.btn_remove.grid_forget()
        self.btn_main_section.grid_forget()
        self.btn_down.grid_forget()
        self.btn_up.grid_forget()

    def retrieve_sections(self, s_sections=None):
        if s_sections is None:
            s_sections = []
        self.directive = Message(action=32)
        self.connection = self.directive.send_directive(self.connection)
        a_sections = self.connection.message.information
        for item in self.trv_available_sections.get_children():
            self.trv_available_sections.delete(item)
        for item in self.trv_selected_sections.get_children():
            self.trv_selected_sections.delete(item)
        for item in s_sections:
            item_aux1 = item.split('¥')[2:6]
            item_aux2 = [item.split('¥')[-1]]
            item = item_aux1 + item_aux2
            item = '¥'.join(item)
            if item in a_sections:
                a_sections.remove(item)
        for index, item in enumerate(a_sections):
            elements = item.split('¥')
            self.trv_available_sections.insert(
                '',
                'end',
                text=elements[0],
                values=(index + 1, summarize_text(elements[1], 150),
                        summarize_text(elements[3], 120)))
        for index, item in enumerate(s_sections):
            elements = item.split('¥')
            self.trv_selected_sections.insert(
                '',
                'end',
                text=elements[2],
                values=(index + 1, summarize_text(elements[3], 150),
                        summarize_text(elements[5],
                                       120), elements[7], elements[8]))

    def click_add(self):
        """
        Function that moves a 'Section' from available tree view to selected tree view (in frm_child_crud)
        """
        if len(self.trv_available_sections.selection()) != 0 and len(
                self.trv_selected_sections.selection()) == 0:
            if len(self.trv_selected_sections.get_children()) != 0:
                index = self.trv_selected_sections.item(
                    self.trv_selected_sections.get_children()[-1])['values'][0]
            else:
                index = 0
            for row in self.trv_available_sections.selection():
                index += 1
                values = self.trv_available_sections.item(row)['values']
                self.trv_selected_sections.insert(
                    '',
                    'end',
                    text=self.trv_available_sections.item(row)['text'],
                    values=(index, values[1], values[2], '✓', ''))
                self.trv_available_sections.delete(row)

    def click_remove(self):
        """
        Function that moves a 'Section' from selected tree view to available tree view (in frm_child_crud)
        """
        if len(self.trv_selected_sections.selection()) != 0 and len(
                self.trv_available_sections.selection()) == 0:
            if len(self.trv_available_sections.get_children()) != 0:
                index = self.trv_available_sections.item(
                    self.trv_available_sections.get_children()
                    [-1])['values'][0]
            else:
                index = 0
            for row in self.trv_selected_sections.selection():
                index += 1
                values = self.trv_selected_sections.item(row)['values']
                self.trv_available_sections.insert(
                    '',
                    'end',
                    text=self.trv_selected_sections.item(row)['text'],
                    values=(index, values[1], values[2]))
                self.trv_selected_sections.delete(row)

    def click_up(self):
        # Make sure only one item in 'selected sections' is selected
        if len(self.trv_selected_sections.selection()) == 1 and len(
                self.trv_available_sections.selection()) == 0:
            item = self.trv_selected_sections.selection()
            index = self.trv_selected_sections.index(item)
            self.trv_selected_sections.move(item, '', index - 1)

    def click_down(self):
        # Make sure only one item in 'selected sections' is selected
        if len(self.trv_selected_sections.selection()) == 1 and len(
                self.trv_available_sections.selection()) == 0:
            item = self.trv_selected_sections.selection()
            index = self.trv_selected_sections.index(item)
            self.trv_selected_sections.move(item, '', index + 1)

    def click_main_section(self):
        # Make sure a max of three items of 'selected sections' are selected
        if 4 > len(self.trv_selected_sections.selection()) > 0 and len(
                self.trv_available_sections.selection()) == 0:
            # First change all sections as normal (not main)
            for item in self.trv_selected_sections.get_children():
                if self.trv_selected_sections.item(item)['values'][3] != '':
                    values = self.trv_selected_sections.item(item)['values']
                    self.trv_selected_sections.item(
                        item,
                        values=(values[0], values[1], values[2], values[3],
                                ''))
            # Set new main sections
            cont_error = False
            for row in self.trv_selected_sections.selection():
                values = self.trv_selected_sections.item(row)['values']
                if values[2] == 'Classification' or values[2] == 'Text':
                    self.trv_selected_sections.item(
                        row,
                        values=(values[0], values[1], values[2], values[3],
                                '✓'))
                else:  # File sections can not be main
                    cont_error = True
            if cont_error:
                messagebox.showwarning(
                    parent=self.frm_child_crud,
                    title='Main section(s)',
                    message=
                    "Main section(s) must be of 'Text' or 'Classification' data type"
                )
        else:
            messagebox.showwarning(
                parent=self.frm_child_crud,
                title='Main section(s)',
                message=
                'You must select a minimum of one and a maximum of three sections'
            )

    def click_trv_asections(self, event):
        """
        Function that removes selection from 'available' tree view when 'selected' tree view is selected (in frm_child_crud)
        """
        self.trv_selected_sections.selection_remove(
            self.trv_selected_sections.selection())

    def click_trv_ssections(self, event):
        """
        Function that removes selection from 'selected' tree view when 'available' tree view is selected (in frm_child_crud)
        """
        self.trv_available_sections.selection_remove(
            self.trv_available_sections.selection())

    def click_save(self):
        if self.validate_fields():
            self.template.name = self.txt_name.get()
            self.template.description = self.txt_description.get(
                '1.0', 'end-1c')
            if self.template.id == 0:  # Creating a template
                self.directive = Message(action=36,
                                         information=[
                                             self.template.name,
                                             self.template.description, [], [],
                                             []
                                         ])
                for item in self.trv_selected_sections.get_children():
                    values = self.trv_selected_sections.item(item)['values']
                    self.directive.information[2].append(
                        int(self.trv_selected_sections.item(item)['text']))
                    self.directive.information[3].append(values[4])
                    if values[4] != '':
                        self.directive.information[4].append('✓')
                    else:
                        self.directive.information[4].append(values[3])
            else:  # Updating a template
                self.directive = Message(action=38,
                                         information=[
                                             self.template.id,
                                             self.template.name,
                                             self.template.description, [], [],
                                             []
                                         ])
                for item in self.trv_selected_sections.get_children():
                    values = self.trv_selected_sections.item(item)['values']
                    self.directive.information[3].append(
                        int(self.trv_selected_sections.item(item)['text']))
                    self.directive.information[4].append(values[4])
                    if values[4] != '':
                        self.directive.information[5].append('✓')
                    else:
                        self.directive.information[5].append(values[3])
            self.connection = self.directive.send_directive(self.connection)
            self.clear_fields()
            self.frm_child_crud.grid_forget()
            self.show_frm()

    def click_cancel(self):
        decision = True
        if self.txt_name.get() != self.template.name or \
                self.txt_description.get('1.0', 'end-1c') != self.template.description or \
                len(self.trv_selected_sections.get_children()) != len(self.template.sections):
            decision = messagebox.askyesno(
                parent=self.frm_child_crud,
                title='Cancel',
                message='Are you sure you want to cancel?')
        if decision:
            self.click_back()

    def click_back(self):
        self.clear_fields()
        self.frm_child_crud.grid_forget()
        self.show_frm()

    def validate_fields(self):
        text_section = False
        if len(self.txt_name.get()) == 0:
            messagebox.showwarning(parent=self.frm_child_crud,
                                   title='Missing information',
                                   message='You must provide a name')
            return False
        if len(self.txt_description.get('1.0', 'end-1c')) == 0:
            messagebox.showwarning(parent=self.frm_child_crud,
                                   title='Missing information',
                                   message='You must provide a description')
            return False
        if len(self.trv_selected_sections.get_children()) == 0:
            messagebox.showwarning(
                parent=self.frm_child_crud,
                title='Missing information',
                message='You must select at least one section')
            return False
        for item in self.trv_selected_sections.get_children():
            values = self.trv_selected_sections.item(item)['values']
            if values[2] == 'Text' or values[2] == 'Classification':
                text_section = True
                break
        if not text_section:
            messagebox.showwarning(
                parent=self.frm_child_crud,
                title='Missing information',
                message='At least one section has to be of text type')
            return False
        for item in self.trv_selected_sections.get_children():
            values = self.trv_selected_sections.item(item)['values']
            if values[4] == '✓':
                if values[2] == 'Text' or values[2] == 'Classification':
                    return True
                else:
                    messagebox.showwarning(
                        parent=self.frm_child_crud,
                        title='Main section',
                        message=
                        'The main section has to be of text or classification type'
                    )
                    return False
        messagebox.showwarning(
            parent=self.frm_child_crud,
            title='Main section',
            message='You must set one of the selected section as main')
        return False

    def clear_fields(self):
        self.txt_name['state'] = NORMAL
        self.txt_description['state'] = NORMAL
        self.txt_name['bg'] = ENABLED_COLOR
        self.txt_description['bg'] = ENABLED_COLOR
        self.txt_name.delete(0, END)
        self.txt_description.delete('1.0', 'end-1c')
        self.btn_save.grid_forget()
        self.btn_cancel.grid_forget()
        self.btn_back.grid_forget()
        self.lbl_note_optional.grid_forget()

    def click_switch_mandatory(self, event):
        if not self.view_decision:  # Only if not viewing a template
            # Make sure only one item in 'selected sections' is selected
            if len(self.trv_selected_sections.selection()) == 1 and len(
                    self.trv_available_sections.selection()) == 0:
                values = self.trv_selected_sections.item(
                    self.trv_selected_sections.focus())['values']
                if values[3] == '':
                    self.trv_selected_sections.item(
                        self.trv_selected_sections.focus(),
                        values=(values[0], values[1], values[2], '✓',
                                values[4]))
                else:
                    self.trv_selected_sections.item(
                        self.trv_selected_sections.focus(),
                        values=(values[0], values[1], values[2], '',
                                values[4]))
Exemple #8
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