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()
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
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')))
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 })
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()
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)
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()