Пример #1
0
    def change_name(self, event):
        """Change category name."""
        def ok(event):
            cats = [l.cget('text').lower() for l in self.cat_labels.values()]
            cat = name.get().strip().lower()
            if cat and cat not in cats:
                label.configure(text=cat.capitalize())
                if old_cat == self.default_category.get():
                    self.default_category.set(cat.capitalize())
                self.update_def_cat_menu()

            name.destroy()

        label = event.widget
        old_cat = label.cget('text')
        name = Entry(self, justify='center')
        name.insert(0, label.cget('text'))
        name.place(in_=label,
                   relx=0,
                   rely=0,
                   anchor='nw',
                   relwidth=1,
                   relheight=1)
        name.bind('<FocusOut>', lambda e: name.destroy())
        name.bind('<Escape>', lambda e: name.destroy())
        name.bind('<Return>', ok)
        name.selection_range(0, 'end')
        name.focus_set()
Пример #2
0
class EmpDatTableCanvas(TableCanvas):
    """
    Override for TableCanvas
    """
    def __init__(self,
                 *args,
                 col_modifiers: dict = None,
                 on_change=None,
                 on_unsaved=None,
                 on_selected=None,
                 **kwargs):
        """
        TableCanvas constructor
        :param args: blanket passthrough
        :param col_modifiers: dictionary modifying column entry
            Example:
                {
                    0: {
                        'read_only': True
                    },
                    1: {
                        'options': ['A', 'B', 'C"]  # Options A, B, and C
                    },
                    2: {
                        'render_as': lambda X: Y    # Render X as Y
                    }
                }
        :param on_change: callback called on every change
        :param on_unsaved: callback called on every change, passes 1 parameter
                            on whether there are pending changes
        :param on_selected: callback called on when a row is selected
        :param kwargs: blanket passthrough
        """
        super().__init__(*args, **kwargs)

        self.col_modifiers = col_modifiers
        self.unsaved = set()
        self.on_change = on_change
        self.on_unsaved = on_unsaved
        self.on_selected = on_selected

    def drawText(self, row, col, celltxt, fgcolor=None, align=None):
        """Draw the text inside a cell area"""

        col_name = self.model.getColumnName(col)

        if col_name in self.col_modifiers and 'render_as' in self.col_modifiers[
                col_name]:
            celltxt = self.col_modifiers[col_name]['render_as'](celltxt)
        if len(celltxt) == 0 or celltxt == 'None':
            celltxt = ''

        self.delete('celltext' + str(col) + '_' + str(row))
        h = self.rowheight
        x1, y1, x2, y2 = self.getCellCoords(row, col)
        w = x2 - x1
        wrap = False
        pad = 5
        # If celltxt is a number then we make it a string
        if type(celltxt) is float or type(celltxt) is int:
            celltxt = str(celltxt)
        length = len(celltxt)
        if length == 0:
            return
        # if cell width is less than x, print nothing
        if w <= 10:
            return

        if fgcolor == None or fgcolor == "None":
            fgcolor = 'black'
        if align == None:
            align = 'w'
        if align == 'w':
            x1 = x1 - w / 2 + pad
        elif align == 'e':
            x1 = x1 + w / 2 - pad

        if w < 18:
            celltxt = '.'
        else:
            fontsize = self.fontsize
            colname = self.model.getColumnName(col)
            # scaling between canvas and text normalised to about font 14
            scale = 8.5 * float(fontsize) / 12
            size = length * scale
            if size > w:
                newlength = w / scale
                # print w, size, length, newlength
                celltxt = celltxt[0:int(math.floor(newlength))]

        # if celltxt is dict then we are drawing a hyperlink
        if self.isLink(celltxt) == True:
            haslink = 0
            linktext = celltxt['text']
            if len(linktext) > w / scale or w < 28:
                linktext = linktext[0:int(w / fontsize * 1.2) - 2] + '..'
            if celltxt['link'] != None and celltxt['link'] != '':
                f, s = self.thefont
                linkfont = (f, s, 'underline')
                linkcolor = 'blue'
                haslink = 1
            else:
                linkfont = self.thefont
                linkcolor = fgcolor

            rect = self.create_text(x1 + w / 2,
                                    y1 + h / 2,
                                    text=linktext,
                                    fill=linkcolor,
                                    font=linkfont,
                                    tag=('text', 'hlink', 'celltext' +
                                         str(col) + '_' + str(row)))
            if haslink == 1:
                self.tag_bind(rect, '<Double-Button-1>', self.check_hyperlink)

        # just normal text
        else:
            rect = self.create_text(x1 + w / 2,
                                    y1 + h / 2,
                                    text=celltxt,
                                    fill=fgcolor,
                                    font=self.thefont,
                                    anchor=align,
                                    tag=('text', 'celltext' + str(col) + '_' +
                                         str(row)))
        return

    def drawCellEntry(self, row, col, text=None):
        """When the user single/double clicks on a text/number cell, bring up entry window"""

        col_name = self.model.getColumnName(col)

        if self.read_only or (col_name in self.col_modifiers
                              and 'read_only' in self.col_modifiers[col_name]
                              and self.col_modifiers[col_name]['read_only']):
            return
        # absrow = self.get_AbsoluteRow(row)
        height = self.rowheight
        model = self.getModel()
        cellvalue = self.model.getCellRecord(row, col)
        if Formula.isFormula(cellvalue):
            return
        else:
            text = self.model.getValueAt(row, col)
        if text == 'None':
            text = ''
        x1, y1, x2, y2 = self.getCellCoords(row, col)
        w = x2 - x1
        # Draw an entry window
        txtvar = StringVar()
        txtvar.set(text)

        def callback(e):
            value = txtvar.get()
            if value == '=':
                # do a dialog that gets the formula into a text area
                # then they can click on the cells they want
                # when done the user presses ok and its entered into the cell
                self.cellentry.destroy()
                # its all done here..
                self.formula_Dialog(row, col)
                return

            coltype = self.model.getColumnType(col)
            if coltype == 'number':
                sta = self.checkDataEntry(e)
                if sta == 1:
                    self.unsaved.add(self.model.getRecName(row))
                    model.setValueAt(value, row, col)
            elif coltype == 'text':
                self.unsaved.add(self.model.getRecName(row))
                model.setValueAt(value, row, col)

            color = self.model.getColorAt(row, col, 'fg')
            self.drawText(row, col, value, color, align=self.align)
            if not isinstance(e, str) and e.keysym == 'Return':
                self.delete('entry')
                # self.drawRect(row, col)
                # self.gotonextCell(e)
            if self.on_change:
                self.on_change()
            if len(self.unsaved) > 0:
                self.on_unsaved(False, row, col)
            else:
                self.on_unsaved(True, row, col)

            is_required = True if col_name in self.col_modifiers and \
                                  'required' in self.col_modifiers[col_name] else False
            if col_name in self.col_modifiers and 'validator' in self.col_modifiers[
                    col_name]:
                if not is_required and value == '':
                    self.model.setColorAt(row, col, 'white')
                    self.redrawCell(row, col)
                    return

                if callable(self.col_modifiers[col_name]['validator']):
                    if not self.col_modifiers[col_name]['validator'](value):
                        self.model.setColorAt(row, col, 'coral')
                        self.redrawCell(row, col)
                else:
                    if not is_valid_against(
                            self.col_modifiers[col_name]['validator'], value):
                        self.model.setColorAt(row, col, 'coral')
                        self.redrawCell(row, col)
            return

        if col_name in self.col_modifiers and 'options' in self.col_modifiers[
                col_name]:
            options = list(self.col_modifiers[col_name]['options'])

            if cellvalue in options:
                first = cellvalue
                options.remove(first)
                options.insert(0, first)

            self.cellentry = OptionMenu(self.parentframe,
                                        txtvar,
                                        *options,
                                        command=callback)
        elif col_name in self.col_modifiers and 'date' in self.col_modifiers[
                col_name]:
            self.cellentry = DateEntry(self.parentframe,
                                       width=20,
                                       textvariable=txtvar,
                                       takefocus=1,
                                       font=self.thefont)
            self.cellentry.bind('<<DateEntrySelected>>', callback)
        else:
            self.cellentry = Entry(self.parentframe,
                                   width=20,
                                   textvariable=txtvar,
                                   takefocus=1,
                                   font=self.thefont)
            self.cellentry.selection_range(0, END)

        try:
            self.cellentry.icursor(END)
        except AttributeError:
            pass
        self.cellentry.bind('<Return>', callback)
        self.cellentry.bind('<KeyRelease>', callback)
        self.cellentry.focus_set()
        self.entrywin = self.create_window(x1 + self.inset,
                                           y1 + self.inset,
                                           width=w - self.inset * 2,
                                           height=height - self.inset * 2,
                                           window=self.cellentry,
                                           anchor='nw',
                                           tag='entry')

        return

    def handle_left_click(self, event):
        """Respond to a single press"""

        self.on_selected()
        super().handle_left_click(event)

    def popupMenu(self, event, rows=None, cols=None, outside=None):
        """Add left and right click behaviour for canvas, should not have to override
            this function, it will take its values from defined dicts in constructor"""

        defaultactions = {
            "Set Fill Color": lambda: self.setcellColor(rows, cols, key='bg'),
            "Set Text Color": lambda: self.setcellColor(rows, cols, key='fg'),
            "Copy": lambda: self.copyCell(rows, cols),
            "Paste": lambda: self.pasteCell(rows, cols),
            "Fill Down": lambda: self.fillDown(rows, cols),
            "Fill Right": lambda: self.fillAcross(cols, rows),
            "Add Row(s)": lambda: self.addRows(),
            "Delete Row(s)": lambda: self.deleteRow(),
            "View Record": lambda: self.getRecordInfo(row),
            "Clear Data": lambda: self.deleteCells(rows, cols),
            "Select All": self.select_All,
            "Auto Fit Columns": self.autoResizeColumns,
            "Filter Records": self.showFilteringBar,
            "New": self.new,
            "Load": self.load,
            "Save": self.save,
            "Import text": self.importTable,
            "Export csv": self.exportTable,
            "Plot Selected": self.plotSelected,
            "Plot Options": self.plotSetup,
            "Export Table": self.exportTable,
            "Preferences": self.showtablePrefs,
            "Formulae->Value": lambda: self.convertFormulae(rows, cols)
        }

        main = [
            "Set Fill Color", "Set Text Color", "Copy", "Paste", "Fill Down",
            "Fill Right", "Clear Data"
        ]
        general = [
            "Select All", "Auto Fit Columns", "Filter Records", "Preferences"
        ]

        def createSubMenu(parent, label, commands):
            menu = Menu(parent, tearoff=0)
            popupmenu.add_cascade(label=label, menu=menu)
            for action in commands:
                menu.add_command(label=action, command=defaultactions[action])
            return menu

        def add_commands(fieldtype):
            """Add commands to popup menu for column type and specific cell"""
            functions = self.columnactions[fieldtype]
            for f in functions.keys():
                func = getattr(self, functions[f])
                popupmenu.add_command(label=f, command=lambda: func(row, col))
            return

        popupmenu = Menu(self, tearoff=0)

        def popupFocusOut(event):
            popupmenu.unpost()

        if outside == None:
            # if outside table, just show general items
            row = self.get_row_clicked(event)
            col = self.get_col_clicked(event)
            coltype = self.model.getColumnType(col)

            def add_defaultcommands():
                """now add general actions for all cells"""
                for action in main:
                    if action == 'Fill Down' and (rows == None
                                                  or len(rows) <= 1):
                        continue
                    if action == 'Fill Right' and (cols == None
                                                   or len(cols) <= 1):
                        continue
                    else:
                        popupmenu.add_command(label=action,
                                              command=defaultactions[action])
                return

            if coltype in self.columnactions:
                add_commands(coltype)
            add_defaultcommands()

        for action in general:
            popupmenu.add_command(label=action, command=defaultactions[action])

        popupmenu.add_separator()
        # createSubMenu(popupmenu, 'File', filecommands)
        # createSubMenu(popupmenu, 'Plot', plotcommands)
        popupmenu.bind("<FocusOut>", popupFocusOut)
        popupmenu.focus_set()
        popupmenu.post(event.x_root, event.y_root)
        return popupmenu
Пример #3
0
    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')))
Пример #4
0
class MainWindow:
    def __init__(self):
        global root

        self.provider = None
        self.session = None
        self.show_image = False
        self.original_image = None
        self.original_image_name = None
        self.main_image = None
        self.main_image_orig = None
        self.resized = False
        self.thumb_prefix = None
        self.proxies = None
        self.gallery_url = None
        self.hist_stack = []
        self.fwd_stack = []
        self.interrupt = False
        self.thumb_url = None
        self.image_url = None

        self.menu_bar = Menu(root)
        self.menu_bar.add_command(label="<< Back",
                                  command=self.back_in_history)
        self.menu_bar.add_command(label="Forward >>",
                                  command=self.forward_in_history)
        self.menu_bar.add_command(label="View gallery",
                                  command=self.view_gallery_url)
        self.menu_bar.add_command(label="Cancel", command=self.calcel)
        root.config(menu=self.menu_bar)

        frm_top = Frame(root)
        self.frm_main = ScrollFrame(root)
        frm_status = Frame(root)

        frm_center = Frame(self.frm_main.view_port)
        frm_left = Frame(self.frm_main.view_port)
        frm_right = Frame(self.frm_main.view_port)

        frm_caption = Frame(frm_center)
        frm_image = Frame(frm_center)

        self.btn_prev = LinkButton(self, frm_caption, text="Previous")
        self.btn_prev.link = "prev link"
        self.btn_prev.pack(side=LEFT)

        self.btn_save = Button(frm_caption,
                               text="Save",
                               command=self.save_image)
        self.btn_save.pack(side=LEFT)

        self.btn_next = LinkButton(self, frm_caption, text="Next")
        self.btn_next.link = "next link"
        self.btn_next.pack(side=LEFT)

        self.btn_paste = Button(frm_top,
                                text="Paste",
                                command=self.paste_from_clipboard)
        self.btn_paste.pack(side=LEFT)

        self.btn_update = Button(frm_top,
                                 text="Load image",
                                 command=self.load_image_from_input)
        self.btn_update.pack(side=LEFT)

        self.sv_url = StringVar()
        self.entry_url = Entry(frm_top, textvariable=self.sv_url, width=100)
        self.entry_url.bind("<FocusIn>", self.focus_callback)
        self.entry_url.bind('<Return>', self.enter_callback)
        self.entry_url.pack(side=LEFT)

        self.use_proxy = BooleanVar()
        self.use_proxy.set(False)
        self.use_proxy.trace('w', self.on_use_proxy_change)

        self.chk_use_proxy = Checkbutton(frm_top,
                                         text='Use proxy',
                                         variable=self.use_proxy)
        self.chk_use_proxy.pack(side=LEFT)

        self.sv_proxy = StringVar()
        self.entry_proxy = Entry(frm_top,
                                 textvariable=self.sv_proxy,
                                 width=30,
                                 state=DISABLED)
        self.entry_proxy.pack(side=LEFT)

        self.btn_force = Button(frm_top,
                                text="Force load",
                                command=self.force_load_image)
        self.btn_force.pack(side=LEFT)

        try:
            with open("proxy.txt") as f:
                self.sv_proxy.set(f.readline().strip())
        except BaseException as error:
            print(error)
            traceback.print_exc()

        self.btn_image = Button(frm_image, command=self.resize_image)
        self.btn_image.bind("<Button-3>", self.load_original_image_in_thread)
        self.btn_image.pack()

        self.left_buttons = self.fill_panel(frm_left)
        self.right_buttons = self.fill_panel(frm_right)

        self.status = StringVar()
        self.status_label = Label(frm_status,
                                  bd=1,
                                  relief=SUNKEN,
                                  anchor=W,
                                  textvariable=self.status)
        self.status_label.pack(side=LEFT, fill=BOTH, expand=1)
        self.status.set('Status Bar')

        self.progress_bar = ttk.Progressbar(frm_status,
                                            orient=HORIZONTAL,
                                            length=200,
                                            mode='indeterminate')

        root.bind("<FocusIn>", self.focus_callback)
        root.bind("<BackSpace>", self.backspace_callback)
        root.bind("<space>", self.space_callback)
        root.protocol("WM_DELETE_WINDOW", self.on_close)

        frm_caption.pack()
        frm_image.pack()

        frm_left.pack(side=LEFT, fill=BOTH, expand=1)
        frm_center.pack(side=LEFT)
        frm_right.pack(side=RIGHT, fill=BOTH, expand=1)

        frm_top.pack()
        self.frm_main.pack(fill=BOTH, expand=1)
        frm_status.pack(fill=X)

        self.hist_logger = logging.getLogger('history')
        self.hist_logger.setLevel(logging.INFO)

        self.fh_hist = logging.FileHandler(
            os.path.join(LOGS, f'hist_{int(time.time())}.log'))
        self.fh_hist.setLevel(logging.INFO)
        self.hist_logger.addHandler(self.fh_hist)

    def force_load_image(self):
        self.load_page_in_thread(self.sv_url.get().strip(), True, True)

    def load_image_from_input(self):
        self.load_page_in_thread(self.sv_url.get().strip())

    def load_page_in_thread(self,
                            input_url,
                            remember=True,
                            ignore_cache=False):
        self.set_controls_state(DISABLED)
        self.interrupt = False
        future = executor.submit(self.load_image_retry, input_url, remember,
                                 ignore_cache)
        future.add_done_callback(lambda f: self.set_controls_state(NORMAL))

    def load_image_retry(self, input_url, remember, ignore_cache):
        global root

        try:
            err_count = 0
            while err_count < MAX_ERRORS:
                root.after_idle(self.set_undefined_state)
                if self.load_image(input_url, remember, ignore_cache):
                    break

                if self.interrupt:
                    break

                err_count += 1
        except BaseException as error:
            print("Exception URL: " + input_url)
            print(error)
            traceback.print_exc()

    def load_image(self, input_url, remember, ignore_cache):
        global root

        if len(input_url) == 0:
            return False

        root.after_idle(self.sv_url.set, input_url)

        self.provider = self.get_provider()
        if self.provider is None:
            return False

        cache_path = os.path.join(CACHE, self.provider.get_domen())
        if not os.path.exists(cache_path):
            os.mkdir(cache_path)

        proxy = self.sv_proxy.get().strip()
        if self.use_proxy.get() and len(proxy.strip()) > 0:
            self.proxies = {
                "http": "http://" + proxy,
                "https": "https://" + proxy
            }
            with open("proxy.txt", "w") as f:
                f.write(proxy)
        else:
            self.proxies = None

        http_session = requests.Session()
        http_session.headers.update(HEADERS)

        ident = self.get_id(input_url)
        if ident is None:
            print("ident is None")
            return False

        input_url = "https://" + self.provider.get_host() + "/" + ident

        root.after_idle(root.title, input_url)

        try:
            html = self.get_from_cache(ident)
            if ignore_cache or (html is None) or (len(html) == 0):
                html = self.get_final_page(ident, input_url, http_session)

            if (html is None) or (len(html) == 0):
                return False

            html = html.decode('utf-8')

            if not self.render_page(ident, html, http_session):
                return False

            if remember and (input_url is not None):
                if len(self.hist_stack) == 0 or (input_url !=
                                                 self.hist_stack[-1]):
                    self.hist_stack.append(input_url)
                    self.hist_logger.info(f'{input_url}\t{self.thumb_url}')
                if len(self.fwd_stack) > 0 and (input_url
                                                == self.fwd_stack[-1]):
                    self.fwd_stack.pop()
                else:
                    self.fwd_stack.clear()

        except BaseException as error:
            print("Exception URL: " + input_url)
            print(error)
            traceback.print_exc()
            return False
        finally:
            http_session.close()

        return True

    def get_final_page(self, ident, input_url, http_session):
        global root

        response = http_session.get(input_url,
                                    proxies=self.proxies,
                                    timeout=TIMEOUT)
        if response.status_code == 404:
            print("input_url response.status_code == 404")
            return None

        html = response.content.decode('utf-8')

        if DEBUG:
            with open('1.html', 'w') as f:
                f.write(html)

        # sometimes this functions fails (i don't want to tamper with this)
        redirect_url = self.provider.get_redirect_url(html)
        if redirect_url is not None:
            if len(redirect_url) == 0:
                print("(redirect_url is None) or (len(redirect_url) == 0)")
                return None

            http_session.headers.update({'Referer': input_url})
            response = http_session.get(redirect_url,
                                        proxies=self.proxies,
                                        timeout=TIMEOUT)
            if response.status_code == 404:
                print("redirect_url response.status_code == 404")
                return None

            html = response.content.decode('utf-8')

            if DEBUG:
                with open('2.html', 'w') as f:
                    f.write(html)

        pos = html.find('File Not Found')
        if pos >= 0:
            print("File Not Found: " + input_url)
            return None

        param = self.provider.get_post_param(html)
        if len(param) == 0:
            print("len(param) == 0")
            return None

        post_fields = {'op': 'view', 'id': ident, 'pre': 1, param: 1}
        response = http_session.post(redirect_url,
                                     data=post_fields,
                                     proxies=self.proxies,
                                     timeout=TIMEOUT)
        if response.status_code == 404:
            print("POST: redirect_url response.status_code == 404")
            return None

        html = response.content

        if DEBUG:
            with open('3.html', 'wb') as f:
                f.write(html)

        self.put_to_cache(ident, html)

        return html

    def render_page(self, ident, html, http_session):
        self.thumb_url = get_thumb(html)
        if (self.thumb_url is None) or (len(self.thumb_url) == 0):
            print("len(thumb_url) == 0")
            return False

        slash_pos = self.thumb_url.rfind('/')
        self.thumb_prefix = self.thumb_url[:slash_pos + 1]
        thumb_filename = self.thumb_url[slash_pos + 1:]
        dot_pos = thumb_filename.rfind('.')
        thumb_filename = thumb_filename[:dot_pos]

        self.gallery_url = search('href="([^"]*)">More from gallery</a>', html)

        self.reconfigure_prev_button(http_session, html)
        self.reconfigure_next_button(http_session, html)

        executor.submit(self.reconfigure_left_buttons, html)
        executor.submit(self.reconfigure_right_buttons, html)

        self.image_url = self.provider.get_image_url(html)

        fname = get_filename(self.image_url)
        dot_pos = fname.rfind('.')
        self.original_image_name = fname[:dot_pos] + '_' + ident
        self.original_image = self.get_from_cache(self.original_image_name)

        bg_color = 'green'
        self.resized = True
        if (self.original_image is None) or (len(self.original_image) == 0):
            self.original_image = self.get_from_cache(thumb_filename)
            self.resized = False

        if (self.original_image is None) or (len(self.original_image) == 0):
            response = http_session.get(self.thumb_url,
                                        proxies=self.proxies,
                                        timeout=TIMEOUT)
            if response.status_code == 404:
                print("image_url response.status_code == 404")
                return False

            self.original_image = response.content

            # if DEBUG:
            #     with open(self.original_image_name, 'wb') as f:
            #         f.write(self.original_image)

            self.put_to_cache(thumb_filename, self.original_image)
            bg_color = 'red'
            self.resized = False

        img = Image.open(io.BytesIO(self.original_image))
        w, h = img.size
        k = MAIN_IMG_WIDTH / w
        img_resized = img.resize((MAIN_IMG_WIDTH, int(h * k)))

        root.after_idle(root.title, f"{root.title()} ({w}x{h})")

        self.main_image_orig = ImageTk.PhotoImage(img)
        self.main_image = ImageTk.PhotoImage(img_resized)

        photo_image = self.main_image if self.resized else self.main_image_orig
        root.after_idle(self.btn_image.config, {
            'image': photo_image,
            'background': bg_color
        })

        if os.path.exists(os.path.join(OUTPUT, self.original_image_name)):
            root.after_idle(self.btn_save.config, {'background': 'green'})

        return True

    def focus_callback(self, event):
        self.entry_url.selection_range(0, END)

    def enter_callback(self, event):
        self.load_image_from_input()

    def backspace_callback(self, event):
        self.back_in_history()

    def space_callback(self, event):
        self.forward_in_history()

    def on_close(self):
        global root

        root.update_idletasks()
        root.destroy()
        self.fh_hist.close()
        self.hist_logger.removeHandler(self.fh_hist)

    def calcel(self):
        self.interrupt = True

    def set_undefined_state(self):
        global root

        self.main_image = None
        self.main_image_orig = None
        self.original_image = None
        self.original_image_name = None
        self.btn_image.config(image='', background="SystemButtonFace")
        self.btn_save.config(background="SystemButtonFace")
        root.title(None)
        self.btn_prev.reset()
        self.btn_next.reset()
        for btn in self.left_buttons:
            btn.reset()
        for btn in self.right_buttons:
            btn.reset()

        self.frm_main.scroll_top_left()

    def paste_from_clipboard(self):
        self.sv_url.set(clipboard.paste())
        self.entry_url.selection_range(0, END)

    def save_image(self):
        if self.original_image is None:
            return

        filename = self.original_image_name
        i = 1
        while os.path.exists(os.path.join(OUTPUT, filename)):
            filename = f'{self.original_image_name}_{i:04}'
            i += 1

        with open(os.path.join(OUTPUT, filename), 'wb') as f:
            f.write(self.original_image)

        self.btn_save.config(background="green")

    def on_enter(self, event):
        self.status.set(event.widget.link)

    def on_leave(self, enter):
        self.status.set("")

    def fill_panel(self, panel):
        buttons = []
        for i in range(4):
            Grid.columnconfigure(panel, i, weight=1)
            for j in range(2):
                Grid.rowconfigure(panel, j, weight=1)
                btn = LinkButton(self, panel, text=f"({i}, {j})")
                btn.link = None
                btn.grid(row=i, column=j, sticky=NSEW, padx=PAD, pady=PAD)
                buttons.append(btn)

        return buttons

    def resize_image(self):
        self.btn_image.config(
            image=(self.main_image_orig if self.resized else self.main_image))
        self.resized = not self.resized
        self.frm_main.scroll_top_left()

    def get_id(self, url):
        found = re.search(
            r"https?://" + self.provider.get_domen() +
            r"\.[a-z]+/(.+?)(?:/|$)", url)
        if (found is None) or (found.group(0) is None):
            return None

        return found.group(1)

    def reconfigure_left_buttons(self, html):
        tab = get_more_from_author(html)
        self.reconfigure_buttons(self.left_buttons, tab)

    def reconfigure_right_buttons(self, html):
        tab = get_more_from_gallery(html)
        self.reconfigure_buttons(self.right_buttons, tab)

    def reconfigure_prev_button(self, http_session, html):
        url = get_prev_url(html)
        if len(url) == 0:
            return

        ident = self.get_id(url)
        img_url = self.thumb_prefix + ident + '_t.jpg'
        self.reconfigure_button(http_session, self.btn_prev, url, img_url)

    def reconfigure_next_button(self, http_session, html):
        url = get_next_url(html)
        if len(url) == 0:
            return

        ident = self.get_id(url)
        img_url = self.thumb_prefix + ident + '_t.jpg'
        self.reconfigure_button(http_session, self.btn_next, url, img_url)

    def reconfigure_button(self, http_session, btn, url, img_url):
        global root

        filename = get_filename(img_url)
        dot_pos = filename.rfind('.')
        filename = filename[:dot_pos]
        bg_color = "green"
        image = self.get_from_cache(filename)
        if (image is None) or (len(image) == 0):
            image = download_image(http_session, img_url)
            self.put_to_cache(filename, image)
            bg_color = "red"

        if (image is None) or (len(image) == 0):
            return

        img = Image.open(io.BytesIO(image))
        w, h = img.size
        k = IMG_WIDTH / w
        img_resized = img.resize((IMG_WIDTH, int(h * k)))
        photo_image = ImageTk.PhotoImage(img_resized)
        if photo_image is None:
            return

        root.after_idle(btn.set_values, url,
                        partial(self.load_page_in_thread, url), photo_image,
                        bg_color)

    def reconfigure_buttons(self, buttons, html):
        http_session = requests.Session()
        http_session.headers.update(HEADERS)

        try:
            for btn in buttons:
                btn.reset()

            i = 0
            for m in re.finditer('<td>.*?href="(.*?)".*?src="(.*?)".*?</td>',
                                 html, re.MULTILINE | re.DOTALL):
                self.reconfigure_button(http_session, buttons[i], m.group(1),
                                        m.group(2))
                i += 1
        except BaseException as error:
            print(error)
            traceback.print_exc()
        finally:
            http_session.close()

    def on_use_proxy_change(self, *args):
        if self.use_proxy.get():
            self.entry_proxy.config(state=NORMAL)
            self.entry_proxy.focus_set()
            self.entry_proxy.selection_range(0, END)
        else:
            self.entry_proxy.config(state=DISABLED)

    def get_provider(self):
        input_url = self.sv_url.get()

        pos = input_url.find(ImgRock.DOMEN)
        if pos >= 0:
            return ImgRock()

        pos = input_url.find(ImgView.DOMEN)
        if pos >= 0:
            return ImgView()

        pos = input_url.find(ImgTown.DOMEN)
        if pos >= 0:
            return ImgTown()

        pos = input_url.find(ImgOutlet.DOMEN)
        if pos >= 0:
            return ImgOutlet()

        pos = input_url.find(ImgMaze.DOMEN)
        if pos >= 0:
            return ImgMaze()

        pos = input_url.find(ImgDew.DOMEN)
        if pos >= 0:
            return ImgDew()

        return None

    def view_gallery_url(self):
        if (self.gallery_url is None) or (len(self.gallery_url) == 0):
            return

        clipboard.copy(self.gallery_url)
        GalleryWindow(self, Toplevel(root))

    def back_in_history(self):
        if len(self.hist_stack) < 2:
            return

        self.fwd_stack.append(self.hist_stack.pop())

        self.load_page_in_thread(self.hist_stack[-1], False)

    def forward_in_history(self):
        if len(self.fwd_stack) == 0:
            return

        self.load_page_in_thread(self.fwd_stack[-1])

    def get_from_cache(self, filename):
        full_path = os.path.join(CACHE, self.provider.get_domen(), filename)
        if not os.path.exists(full_path):
            return None

        mod_time = time.time()
        os.utime(full_path, (mod_time, mod_time))

        with open(full_path, 'rb') as f:
            return f.read()[::-1]

    def put_to_cache(self, filename, data):
        if (data is None) or (len(data) == 0):
            return

        full_path = os.path.join(CACHE, self.provider.get_domen(), filename)

        with open(full_path, 'wb') as f:
            f.write(data[::-1])

    def set_controls_state(self, status):
        self.btn_prev.config(state=status)
        self.btn_next.config(state=status)
        self.btn_update.config(state=status)
        self.btn_force.config(state=status)
        self.entry_url.config(state=status)
        self.btn_paste.config(state=status)
        self.chk_use_proxy.config(state=status)
        self.entry_proxy.config(state=status)
        self.menu_bar.entryconfig("<< Back", state=status)
        self.menu_bar.entryconfig("Forward >>", state=status)

        for btn in self.left_buttons:
            btn.config(state=status)
        for btn in self.right_buttons:
            btn.config(state=status)

        if status == DISABLED:
            self.progress_bar.pack(side=LEFT)
            self.progress_bar.start()
        else:
            self.progress_bar.pack_forget()
            self.progress_bar.stop()

    def load_original_image_in_thread(self, event):
        executor.submit(self.load_original_image)

    def load_original_image(self):
        response = requests.get(self.image_url,
                                proxies=self.proxies,
                                timeout=TIMEOUT)
        if response.status_code == 404:
            print("image_url response.status_code == 404")
            return

        self.original_image = response.content

        # if DEBUG:
        #     with open(self.original_image_name, 'wb') as f:
        #         f.write(self.original_image)

        self.put_to_cache(self.original_image_name, self.original_image)
        bg_color = 'red'

        self.resized = True

        img = Image.open(io.BytesIO(self.original_image))
        w, h = img.size
        k = MAIN_IMG_WIDTH / w
        img_resized = img.resize((MAIN_IMG_WIDTH, int(h * k)))

        root.after_idle(root.title, f"{root.title()} ({w}x{h})")

        self.main_image_orig = ImageTk.PhotoImage(img)
        self.main_image = ImageTk.PhotoImage(img_resized)

        root.after_idle(self.btn_image.config, {
            'image': self.main_image,
            'background': bg_color
        })
Пример #5
0
class FontChooser(Toplevel):
    """Font chooser dialog."""

    def __init__(self, master, font_dict={}, text="Abcd", title="Font Chooser",
                 **kwargs):
        """
        Create a new FontChooser instance.
        Arguments:
            master : Tk or Toplevel instance
                master window
            font_dict : dict
                dictionnary, like the one returned by the ``actual`` method of a ``Font`` object:
                ::
                    {'family': str,
                     'size': int,
                     'weight': 'bold'/'normal',
                     'slant': 'italic'/'roman',
                     'underline': bool,
                     'overstrike': bool}
            text : str
                text to be displayed in the preview label
            title : str
                window title
            kwargs : dict
                additional keyword arguments to be passed to ``Toplevel.__init__``
        """
        Toplevel.__init__(self, master, **kwargs)
        self.title(title)
        self.resizable(False, False)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        self.wait_visibility(self)
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()

    def select_all(self, event):
        """Select all entry content."""
        event.widget.selection_range(0, "end")

    def keypress(self, event):
        """Select the first font whose name begin by the key pressed."""
        key = event.char.lower()
        l = [i for i in self.fonts if i[0].lower() == key]
        if l:
            i = self.fonts.index(l[0])
            self.list_family.selection_clear(0, "end")
            self.list_family.selection_set(i)
            self.list_family.see(i)
            self.update_entry_family()

    def up_family(self, event):
        """Navigate in the family listbox with up key."""
        try:
            i = self.list_family.curselection()[0]
            self.list_family.selection_clear(0, "end")
            if i <= 0:
                i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        except TclError:
            self.list_family.selection_clear(0, "end")
            i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        self.list_family.event_generate('<<ListboxSelect>>')

    def up_size(self, event):
        """Navigate in the size listbox with up key."""
        try:
            s = self.var_size.get()
            if s in self.sizes:
                i = self.sizes.index(s)
            elif s:
                sizes = list(self.sizes)
                sizes.append(s)
                sizes.sort(key=lambda x: int(x))
                i = sizes.index(s)
            else:
                i = 0
            self.list_size.selection_clear(0, "end")
            if i <= 0:
                i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        except TclError:
            i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        self.list_size.event_generate('<<ListboxSelect>>')

    def down_family(self, event):
        """Navigate in the family listbox with down key."""
        try:
            i = self.list_family.curselection()[0]
            self.list_family.selection_clear(0, "end")
            if i >= len(self.fonts):
                i = -1
            self.list_family.see(i + 1)
            self.list_family.select_set(i + 1)
        except TclError:
            self.list_family.selection_clear(0, "end")
            self.list_family.see(0)
            self.list_family.select_set(0)
        self.list_family.event_generate('<<ListboxSelect>>')

    def down_size(self, event):
        """Navigate in the size listbox with down key."""
        try:
            s = self.var_size.get()
            if s in self.sizes:
                i = self.sizes.index(s)
            elif s:
                sizes = list(self.sizes)
                sizes.append(s)
                sizes.sort(key=lambda x: int(x))
                i = sizes.index(s) - 1
            else:
                s = len(self.sizes) - 1
            self.list_size.selection_clear(0, "end")
            if i < len(self.sizes) - 1:
                self.list_size.selection_set(i + 1)
                self.list_size.see(i + 1)
            else:
                self.list_size.see(0)
                self.list_size.select_set(0)
        except TclError:
            self.list_size.selection_set(0)
        self.list_size.event_generate('<<ListboxSelect>>')

    def toggle_bold(self):
        """Update font preview weight."""
        b = self.var_bold.get()
        self.preview_font.configure(weight=["normal", "bold"][b])

    def toggle_italic(self):
        """Update font preview slant."""
        b = self.var_italic.get()
        self.preview_font.configure(slant=["roman", "italic"][b])

    def toggle_underline(self):
        """Update font preview underline."""
        b = self.var_underline.get()
        self.preview_font.configure(underline=b)

    def toggle_overstrike(self):
        """Update font preview overstrike."""
        b = self.var_overstrike.get()
        self.preview_font.configure(overstrike=b)

    def change_font_family(self, event=None):
        """Update font preview family."""
        family = self.entry_family.get()
        if family.replace(" ", "\ ") in self.fonts:
            self.preview_font.configure(family=family)

    def change_font_size(self, event=None):
        """Update font preview size."""
        size = int(self.var_size.get())
        self.preview_font.configure(size=size)

    def validate_font_size(self, d, ch, V):
        """Validation of the size entry content."""
        l = [i for i in self.sizes if i[:len(ch)] == ch]
        i = None
        if l:
            i = self.sizes.index(l[0])
        elif ch.isdigit():
            sizes = list(self.sizes)
            sizes.append(ch)
            sizes.sort(key=lambda x: int(x))
            i = min(sizes.index(ch), len(self.sizes))
        if i is not None:
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            deb = self.list_size.nearest(0)
            fin = self.list_size.nearest(self.list_size.winfo_height())
            if V != "forced":
                if i < deb or i > fin:
                    self.list_size.see(i)
                return True
        if d == '1':
            return ch.isdigit()
        else:
            return True

    def tab(self, event):
        """Move at the end of selected text on tab press."""
        self.entry_family = event.widget
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        return "break"

    def validate_font_family(self, action, modif, pos, prev_txt, V):
        """Completion of the text in the entry with existing font names."""
        if self.entry_family.selection_present():
            sel = self.entry_family.selection_get()
            txt = prev_txt.replace(sel, '')
        else:
            txt = prev_txt
        if action == "0":
            txt = txt[:int(pos)] + txt[int(pos) + 1:]
            return True
        else:
            txt = txt[:int(pos)] + modif + txt[int(pos):]
            ch = txt.replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(ch)] == ch]
            if l:
                i = self.fonts.index(l[0])
                self.list_family.selection_clear(0, "end")
                self.list_family.selection_set(i)
                deb = self.list_family.nearest(0)
                fin = self.list_family.nearest(self.list_family.winfo_height())
                index = self.entry_family.index("insert")
                self.entry_family.delete(0, "end")
                self.entry_family.insert(0, l[0].replace("\ ", " "))
                self.entry_family.selection_range(index + 1, "end")
                self.entry_family.icursor(index + 1)
                if V != "forced":
                    if i < deb or i > fin:
                        self.list_family.see(i)
                return True
            else:
                return False

    def update_entry_family(self, event=None):
        """Update family entry when an item is selected in the family listbox."""
        #  family = self.list_family.get("@%i,%i" % (event.x , event.y))
        family = self.list_family.get(self.list_family.curselection()[0])
        self.entry_family.delete(0, "end")
        self.entry_family.insert(0, family)
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.change_font_family()

    def update_entry_size(self, event):
        """Update size entry when an item is selected in the size listbox."""
        #  size = self.list_size.get("@%i,%i" % (event.x , event.y))
        size = self.list_size.get(self.list_size.curselection()[0])
        self.var_size.set(size)
        self.change_font_size()

    def ok(self):
        """Validate choice."""
        self.res = self.preview_font.actual()
        self.quit()

    def get_res(self):
        """Return chosen font."""
        return self.res

    def quit(self):
        self.destroy()
Пример #6
0
class AutoCompleteEntryListbox(Frame):
    def __init__(self,
                 master=None,
                 completevalues=[],
                 allow_other_values=False,
                 **kwargs):
        """
        Create a Entry + Listbox with autocompletion.

        Keyword arguments:
         - allow_other_values (boolean): whether the user is allowed to enter values not in the list
        """
        exportselection = kwargs.pop('exportselection', False)
        width = kwargs.pop('width', None)
        justify = kwargs.pop('justify', None)
        font = kwargs.pop('font', None)
        Frame.__init__(self, master, padding=4, **kwargs)
        self.columnconfigure(0, weight=1)
        self.rowconfigure(1, weight=1)
        self._allow_other_values = allow_other_values
        self._completevalues = completevalues
        self._validate = self.register(self.validate)
        self.entry = Entry(self,
                           width=width,
                           justify=justify,
                           font=font,
                           validate='key',
                           exportselection=exportselection,
                           validatecommand=(self._validate, "%d", "%S", "%i",
                                            "%s", "%P"))
        f = Frame(self, style='border.TFrame', padding=1)
        self.listbox = Listbox(f,
                               width=width,
                               justify=justify,
                               font=font,
                               exportselection=exportselection,
                               selectmode="browse",
                               highlightthickness=0,
                               relief='flat')
        self.listbox.pack(fill='both', expand=True)
        scroll = AutoHideScrollbar(self,
                                   orient='vertical',
                                   command=self.listbox.yview)
        self.listbox.configure(yscrollcommand=scroll.set)
        self.entry.grid(sticky='ew')
        f.grid(sticky='nsew')
        scroll.grid(row=1, column=1, sticky='ns')
        for c in self._completevalues:
            self.listbox.insert('end', c)

        self.listbox.bind('<<ListboxSelect>>', self.update_entry)
        self.listbox.bind("<KeyPress>", self.keypress)
        self.entry.bind("<Tab>", self.tab)
        self.entry.bind("<Down>", self.down)
        self.entry.bind("<Up>", self.up)
        self.entry.focus_set()

    def tab(self, event):
        """Move at the end of selected text on tab press."""
        self.entry = event.widget
        self.entry.selection_clear()
        self.entry.icursor("end")
        return "break"

    def keypress(self, event):
        """Select the first item which name begin by the key pressed."""
        key = event.char.lower()
        l = [i for i in self._completevalues if i[0].lower() == key]
        if l:
            i = self._completevalues.index(l[0])
            self.listbox.selection_clear(0, "end")
            self.listbox.selection_set(i)
            self.listbox.see(i)
            self.update_entry()

    def up(self, event):
        """Navigate in the listbox with up key."""
        try:
            i = self.listbox.curselection()[0]
            self.listbox.selection_clear(0, "end")
            if i <= 0:
                i = len(self._completevalues)
            self.listbox.see(i - 1)
            self.listbox.select_set(i - 1)
        except (TclError, IndexError):
            self.listbox.selection_clear(0, "end")
            i = len(self._completevalues)
            self.listbox.see(i - 1)
            self.listbox.select_set(i - 1)
        self.listbox.event_generate('<<ListboxSelect>>')

    def down(self, event):
        """Navigate in the listbox with down key."""
        try:
            i = self.listbox.curselection()[0]
            self.listbox.selection_clear(0, "end")
            if i >= len(self._completevalues):
                i = -1
            self.listbox.see(i + 1)
            self.listbox.select_set(i + 1)
        except (TclError, IndexError):
            self.listbox.selection_clear(0, "end")
            self.listbox.see(0)
            self.listbox.select_set(0)
        self.listbox.event_generate('<<ListboxSelect>>')

    def validate(self, action, modif, pos, prev_txt, new_txt):
        """Complete the text in the entry with values."""
        try:
            sel = self.entry.selection_get()
            txt = prev_txt.replace(sel, '')
        except TclError:
            txt = prev_txt
        if action == "0":
            txt = txt[:int(pos)] + txt[int(pos) + 1:]
            return True
        else:
            txt = txt[:int(pos)] + modif + txt[int(pos):]
            l = [i for i in self._completevalues if i[:len(txt)] == txt]
            if l:
                i = self._completevalues.index(l[0])
                self.listbox.selection_clear(0, "end")
                self.listbox.selection_set(i)
                self.listbox.see(i)
                index = self.entry.index("insert")
                self.entry.delete(0, "end")
                self.entry.insert(0, l[0].replace("\ ", " "))
                self.entry.selection_range(index + 1, "end")
                self.entry.icursor(index + 1)
                return True
            else:
                return self._allow_other_values

    def __getitem__(self, key):
        return self.cget(key)

    def update_entry(self, event=None):
        """Update entry when an item is selected in the listbox."""
        try:
            sel = self.listbox.get(self.listbox.curselection()[0])
        except (TclError, IndexError):
            return
        self.entry.delete(0, "end")
        self.entry.insert(0, sel)
        self.entry.selection_clear()
        self.entry.icursor("end")
        self.event_generate('<<ItemSelect>>')

    def keys(self):
        keys = Combobox.keys(self)
        keys.append('allow_other_values')
        return keys

    def get(self):
        return self.entry.get()

    def cget(self, key):
        if key == 'allow_other_values':
            return self._allow_other_values
        elif key == 'completevalues':
            return self._completevalues
        else:
            return self.cget(self, key)

    def config(self, dic={}, **kwargs):
        self.configure(dic={}, **kwargs)

    def configure(self, dic={}, **kwargs):
        dic2 = {}
        dic2.update(dic)
        dic2.update(kwargs)
        self._allow_other_values = dic2.pop('allow_other_values',
                                            self._allow_other_values)
        self._completevalues = dic2.pop('completevalues', self._completevalues)
        self.config(self, dic2)
Пример #7
0
class FontChooser(Toplevel):
    """ Font chooser toplevel """
    def __init__(self,
                 master,
                 font_dict={},
                 text="Abcd",
                 title="Font Chooser",
                 **kwargs):
        """
            Create a new FontChooser instance.

            font: dictionnary, like the one returned by the .actual
                  method of a Font object

                    {'family': 'DejaVu Sans',
                     'overstrike':False,
                     'size': 12,
                     'slant': 'italic' or 'roman',
                     'underline': False,
                     'weight': 'bold' or 'normal'}

            text: text to be displayed in the preview label

            title: window title

            **kwargs: additional keyword arguments to be passed to
                      Toplevel.__init__
        """
        Toplevel.__init__(self, master, **kwargs)
        self.title(title)
        self.resizable(False, False)
        self.protocol("WM_DELETE_WINDOW", self.quit)
        self._validate_family = self.register(self.validate_font_family)
        self._validate_size = self.register(self.validate_font_size)

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

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

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

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

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

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

        i = self.fonts.index(self.entry_family.get().replace(" ", "\ "))
        self.list_family.selection_clear(0, "end")
        self.list_family.selection_set(i)
        self.list_family.see(i)
        i = self.sizes.index(entry_size.get())
        self.list_size.selection_clear(0, "end")
        self.list_size.selection_set(i)
        self.list_size.see(i)

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

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

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

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

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

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

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

        self.update_idletasks()
        self.grab_set()
        self.entry_family.focus_set()
        self.lift()

    def select_all(self, event):
        event.widget.selection_range(0, "end")

    def keypress(self, event):
        key = event.char.lower()
        l = [i for i in self.fonts if i[0].lower() == key]
        if l:
            i = self.fonts.index(l[0])
            self.list_family.selection_clear(0, "end")
            self.list_family.selection_set(i)
            self.list_family.see(i)
            self.update_entry_family()

    def up_family(self, event):
        try:
            txt = self.entry_family.get().replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(txt)] == txt]
            if l:
                self.list_family.selection_clear(0, "end")
                i = self.fonts.index(l[0])
                if i > 0:
                    self.list_family.selection_set(i - 1)
                    self.list_family.see(i - 1)
                else:
                    i = len(self.fonts)
                    self.list_family.see(i - 1)
                    self.list_family.select_set(i - 1)

        except TclError:
            i = len(self.fonts)
            self.list_family.see(i - 1)
            self.list_family.select_set(i - 1)
        self.list_family.event_generate('<<ListboxSelect>>')

    def up_size(self, event):
        try:
            s = self.var_size.get()
            i = self.sizes.index(s)
            self.list_size.selection_clear(0, "end")
            if i > 0:
                self.list_size.selection_set(i - 1)
                self.list_size.see(i - 1)
            else:
                i = len(self.sizes)
                self.list_size.see(i - 1)
                self.list_size.select_set(i - 1)
        except TclError:
            i = len(self.sizes)
            self.list_size.see(i - 1)
            self.list_size.select_set(i - 1)
        self.list_size.event_generate('<<ListboxSelect>>')

    def down_family(self, event):
        try:
            txt = self.entry_family.get().replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(txt)] == txt]
            if l:
                self.list_family.selection_clear(0, "end")
                i = self.fonts.index(l[0])
                if i < len(self.fonts) - 1:
                    self.list_family.selection_set(i + 1)
                    self.list_family.see(i + 1)
                else:
                    self.list_family.see(0)
                    self.list_family.select_set(0)

        except TclError:
            self.list_family.selection_set(0)
        self.list_family.event_generate('<<ListboxSelect>>')

    def down_size(self, event):
        try:
            s = self.var_size.get()
            i = self.sizes.index(s)
            self.list_size.selection_clear(0, "end")
            if i < len(self.sizes) - 1:
                self.list_size.selection_set(i + 1)
                self.list_size.see(i + 1)
            else:
                self.list_size.see(0)
                self.list_size.select_set(0)
        except TclError:
            self.list_size.selection_set(0)
        self.list_size.event_generate('<<ListboxSelect>>')

    def toggle_bold(self):
        b = self.var_bold.get()
        self.preview_font.configure(weight=["normal", "bold"][b])

    def toggle_italic(self):
        b = self.var_italic.get()
        self.preview_font.configure(slant=["roman", "italic"][b])

    def toggle_underline(self):
        b = self.var_underline.get()
        self.preview_font.configure(underline=b)

    def toggle_overstrike(self):
        b = self.var_overstrike.get()
        self.preview_font.configure(overstrike=b)

    def change_font_family(self, event=None):
        family = self.entry_family.get()
        if family.replace(" ", "\ ") in self.fonts:
            self.preview_font.configure(family=family)

    def change_font_size(self, event=None):
        size = int(self.var_size.get())
        self.preview_font.configure(size=size)

    def validate_font_size(self, d, ch, V):
        ''' Validation of the size entry content '''
        l = [i for i in self.sizes if i[:len(ch)] == ch]
        if l:
            i = self.sizes.index(l[0])
            self.list_size.selection_clear(0, "end")
            self.list_size.selection_set(i)
            deb = self.list_size.nearest(0)
            fin = self.list_size.nearest(self.list_size.winfo_height())
            if V != "forced":
                if i < deb or i > fin:
                    self.list_size.see(i)
                return True
        if d == '1':
            return ch.isdigit()
        else:
            return True

    def tab(self, event):
        self.entry_family = event.widget
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        return "break"

    def validate_font_family(self, action, modif, pos, prev_txt, V):
        """ completion of the text in the path entry with existing
            folder/file names """
        if self.entry_family.selection_present():
            sel = self.entry_family.selection_get()
            txt = prev_txt.replace(sel, '')
        else:
            txt = prev_txt
        if action == "0":
            txt = txt[:int(pos)] + txt[int(pos) + 1:]
            return True
        else:
            txt = txt[:int(pos)] + modif + txt[int(pos):]
            ch = txt.replace(" ", "\ ")
            l = [i for i in self.fonts if i[:len(ch)] == ch]
            if l:
                i = self.fonts.index(l[0])
                self.list_family.selection_clear(0, "end")
                self.list_family.selection_set(i)
                deb = self.list_family.nearest(0)
                fin = self.list_family.nearest(self.list_family.winfo_height())
                index = self.entry_family.index("insert")
                self.entry_family.delete(0, "end")
                self.entry_family.insert(0, l[0].replace("\ ", " "))
                self.entry_family.selection_range(index + 1, "end")
                self.entry_family.icursor(index + 1)
                if V != "forced":
                    if i < deb or i > fin:
                        self.list_family.see(i)
                return True
            else:
                return False

    def update_entry_family(self, event=None):
        #  family = self.list_family.get("@%i,%i" % (event.x , event.y))
        family = self.list_family.get(self.list_family.curselection()[0])
        self.entry_family.delete(0, "end")
        self.entry_family.insert(0, family)
        self.entry_family.selection_clear()
        self.entry_family.icursor("end")
        self.change_font_family()

    def update_entry_size(self, event):
        #  size = self.list_size.get("@%i,%i" % (event.x , event.y))
        size = self.list_size.get(self.list_size.curselection()[0])
        self.var_size.set(size)
        self.change_font_size()

    def ok(self):
        self.res = self.preview_font.actual()
        self.quit()

    def get_res(self):
        return self.res

    def quit(self):
        self.destroy()