def add_link(self): def ok(eveny=None): lien = link.get() txt = text.get() if lien: if not txt: txt = lien self.nb_links += 1 if self.txt.tag_ranges("sel"): index = self.txt.index("sel.first") self.txt.delete('sel.first', 'sel.last') else: index = "current" tags = self.txt.tag_names(index) + ("link", "link#%i" % self.nb_links) self.txt.insert("current", txt, tags) if not lien[:4] == "http": lien = "http://" + lien self.links[self.nb_links] = lien self.txt.tag_bind("link#%i" % self.nb_links, "<Button-1>", lambda e: open_url(lien)) top.destroy() top = Toplevel(self) top.transient(self) top.update_idletasks() top.geometry("+%i+%i" % top.winfo_pointerxy()) top.grab_set() top.resizable(True, False) top.title(_("Link")) top.columnconfigure(1, weight=1) text = Entry(top) link = Entry(top) if self.txt.tag_ranges('sel'): txt = self.txt.get('sel.first', 'sel.last') else: txt = '' text.insert(0, txt) text.icursor("end") Label(top, text=_("Text")).grid(row=0, column=0, sticky="e", padx=4, pady=4) Label(top, text=_("Link")).grid(row=1, column=0, sticky="e", padx=4, pady=4) text.grid(row=0, column=1, sticky="ew", padx=4, pady=4) link.grid(row=1, column=1, sticky="ew", padx=4, pady=4) Button(top, text="Ok", command=ok).grid(row=2, columnspan=2, padx=4, pady=4) text.focus_set() text.bind("<Return>", ok) link.bind("<Return>", ok)
def add_latex(self, img_name=None): def ok(event): latex = r'%s' % text.get() if latex: if img_name is None: l = [ int(os.path.splitext(f)[0]) for f in os.listdir(PATH_LATEX) ] l.sort() if l: i = l[-1] + 1 else: i = 0 img = "%i.png" % i self.txt.tag_bind(img, '<Double-Button-1>', lambda e: self.add_latex(img)) self.latex[img] = latex else: img = img_name im = os.path.join(PATH_LATEX, img) try: math_to_image(latex, im, fontsize=CONFIG.getint("Font", "text_size") - 2) self.images.append(PhotoImage(file=im, master=self)) if self.txt.tag_ranges("sel"): index = self.txt.index("sel.first") self.txt.delete('sel.first', 'sel.last') else: index = self.txt.index("current") self.txt.image_create(index, image=self.images[-1], name=im) self.txt.tag_add(img, index) top.destroy() except Exception as e: showerror(_("Error"), str(e)) top = Toplevel(self) top.transient(self) top.update_idletasks() top.geometry("+%i+%i" % top.winfo_pointerxy()) top.grab_set() top.resizable(True, False) top.title("LaTex") text = Entry(top, justify='center') if img_name is not None: text.insert(0, self.latex[img_name]) else: if self.txt.tag_ranges('sel'): text.insert(0, self.txt.get('sel.first', 'sel.last')) else: text.insert(0, '$$') text.icursor(1) text.pack(fill='x', expand=True) text.bind('<Return>', ok) text.focus_set()
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 focus_set(entry: ttk.Entry): """Set initial focus for this class.""" entry.focus_set() entry.select_range(0, tk.END) entry.icursor(tk.END)
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(tkfontchooser.FontChooser): def __init__(self, master, font_dict=None, text='AaBbYyZz', title='Font', **kwargs): Toplevel.__init__(self, master, **kwargs) self.title(title) try: self.wm_iconbitmap('transparent.ico') except TclError: pass self.resizable(False, False) self.protocol('WM_DELETE_WINDOW', self.quit) self._validate_family = self.register(self.validate_font_family) self._validate_size = self.register(self.validate_font_size) # --- variable storing the chosen font self.res = '' style = Style(self) style.configure('prev.TLabel') bg = style.lookup('TLabel', 'background') self.configure(bg=bg) # --- family list self.fonts = list(set(families())) self.fonts.append('TkDefaultFont') self.fonts.sort() for i in range(len(self.fonts)): self.fonts[i] = self.fonts[i].replace(' ', '\\ ') max_length = int(2.5 * max([len(font) for font in self.fonts])) // 3 - 2 self.sizes = ['%i' % i for i in (list(range(6, 17)) + list(range(18, 32, 2)) + list(range(36, 48, 4)))] # --- font default font_dict['weight'] = font_dict.get('weight', 'normal') font_dict['slant'] = font_dict.get('slant', 'roman') font_dict['underline'] = font_dict.get('underline', False) font_dict['overstrike'] = font_dict.get('overstrike', False) font_dict['family'] = font_dict.get('family', self.fonts[0].replace('\\ ', ' ')) font_dict['size'] = font_dict.get('size', 10) # --- format list self.formats = ['Regular', 'Italic', 'Bold', 'Bold Italic'] # --- creation of the widgets self.font_family = StringVar(self, ' '.join(self.fonts)) self.font_size = StringVar(self, ' '.join(self.sizes)) self.format_type = StringVar(self, self.formats) self.var_bold = BooleanVar(self, font_dict['weight'] == 'bold') self.var_italic = BooleanVar(self, font_dict['slant'] == 'italic') # ------ Size and family self.var_size = StringVar(self) self.entry_family = Entry(self, width=max_length, validate='key', validatecommand=(self._validate_family, '%d', '%S', '%i', '%s', '%V')) self.entry_size = Entry(self, width=8, validate='key', textvariable=self.var_size, validatecommand=(self._validate_size, '%d', '%P', '%V')) self.entry_format = Entry(self) self.list_family = Listbox(self, selectmode='browse', listvariable=self.font_family, highlightthickness=0, exportselection=False, width=max_length, height=6) self.list_size = Listbox(self, selectmode='browse', listvariable=self.font_size, highlightthickness=0, exportselection=False, width=6, height=6) self.list_format = Listbox(self, selectmode='browse', listvariable=self.format_type, highlightthickness=0, exportselection=False, width=12, height=6) self.scroll_family = Scrollbar(self, orient='vertical', command=self.list_family.yview) self.scroll_size = Scrollbar(self, orient='vertical', command=self.list_size.yview) self.scroll_format = Scrollbar(self, orient='vertical', command=self.list_format.yview) self.family_label = Label(self, text='Font:') self.style_label = Label(self, text='Font style:') self.size_label = Label(self, text='Size:') self.script_label = Label(self, text='Script:') self.script_box = Combobox(self, values=['Western']) self.more_fonts_label = Lbl(self, text='Show more fonts', underline=1, fg='blue', cursor='hand2') f = Font(self.more_fonts_label, self.more_fonts_label.cget("font")) f.configure(underline=True) self.more_fonts_label.configure(font=f) self.preview_font = Font(self, **font_dict) if len(text) > 30: text = text[:30] self.preview_window = LabelFrame(self, relief='groove', text='Sample', bd=1) # --- widget configuration self.list_family.configure(yscrollcommand=self.scroll_family.set) self.list_size.configure(yscrollcommand=self.scroll_size.set) self.list_format.configure(yscrollcommand=self.scroll_format.set) self.entry_family.insert(0, font_dict['family']) self.entry_family.selection_clear() self.entry_family.icursor('end') self.entry_format.insert(0, self.formats[1]) self.entry_format.selection_clear() self.entry_format.icursor('end') self.entry_size.insert(0, font_dict['size']) try: i = self.fonts.index(self.entry_family.get().replace(' ', '\\ ')) except ValueError: # unknown font i = 0 self.list_family.selection_clear(0, 'end') self.list_family.selection_set(i) self.list_family.see(i) try: i = self.sizes.index(self.entry_size.get()) self.list_size.selection_clear(0, 'end') self.list_size.selection_set(i) self.list_size.see(i) except ValueError: # size not in listtsg pass # font family location config self.family_label.grid(row=0, column=0, sticky='nsew', pady=(10, 1), padx=(10, 1)) self.entry_family.grid(row=1, column=0, sticky='nsew', pady=(1, 1), padx=(10, 0), columnspan=2) self.list_family.grid(row=2, column=0, sticky='nsew', pady=(1, 10), padx=(10, 0)) self.scroll_family.grid(row=2, column=1, sticky='ns', pady=(1, 10)) # font style/format location config self.style_label.grid(row=0, column=2, sticky='nsew', pady=(10, 1), padx=(15, 1)) self.entry_format.grid(row=1, column=2, sticky='nsew', pady=(1, 1), padx=(15, 0), columnspan=2) self.list_format.grid(row=2, column=2, sticky='nsew', pady=(1, 10), padx=(15, 0)) self.scroll_format.grid(row=2, column=3, sticky='ns', pady=(1, 10)) # font size location config self.size_label.grid(row=0, column=4, sticky='nsew', pady=(10, 1), padx=(15, 1)) self.entry_size.grid(row=1, column=4, sticky='nsew', pady=(1, 1), padx=(15, 10), columnspan=2) self.list_size.grid(row=2, column=4, sticky='nsew', pady=(1, 10), padx=(15, 0)) self.scroll_size.grid(row=2, column=5, sticky='nsew', pady=(1, 10), padx=(0, 10)) # font preview location config self.preview_window.grid(row=4, column=2, columnspan=4, sticky='nsew', rowspan=2, padx=15, pady=(0, 10), ipadx=10, ipady=10) self.preview_window.config(height=75) preview = Label(self.preview_window, text=text, font=self.preview_font, anchor='center') preview.place(relx=0.5, rely=0.5, anchor='center') self.script_label.grid(row=6, column=2, sticky='nsw', padx=(15, 0)) self.script_box.grid(row=7, column=2, sticky='nsw', pady=(1, 30), padx=(15, 0)) self.script_box.current(0) self.more_fonts_label.grid(row=8, column=0, pady=(35, 20), padx=(15, 0), sticky='nsw') button_frame = Frame(self) button_frame.grid(row=9, column=2, columnspan=4, pady=(0, 10), padx=(10, 0)) Button(button_frame, text='Ok', command=self.ok).grid(row=0, column=0, padx=4, sticky='ew') Button(button_frame, text='Cancel', command=self.quit).grid(row=0, column=1, padx=4, sticky='ew') self.list_family.bind('<<ListboxSelect>>', self.update_entry_family) self.list_format.bind('<<ListboxSelect>>', self.update_entry_format) self.list_size.bind('<<ListboxSelect>>', self.update_entry_size, add=True) self.list_family.bind('<KeyPress>', self.keypress) self.entry_family.bind('<Return>', self.change_font_family) self.entry_family.bind('<Tab>', self.tab) self.entry_size.bind('<Return>', self.change_font_size) self.more_fonts_label.bind('<Button-1>', search_fonts) self.entry_family.bind('<Down>', self.down_family) self.entry_size.bind('<Down>', self.down_size) self.entry_family.bind('<Up>', self.up_family) self.entry_size.bind('<Up>', self.up_size) self.bind_class('TEntry', '<Control-a>', self.select_all) self.wait_visibility(self) self.grab_set() self.entry_family.focus_set() self.lift() def update_entry_format(self, _): style = self.list_format.get(self.list_format.curselection()[0]) self.entry_format.delete(0, 'end') self.entry_format.insert(0, style) self.entry_format.selection_clear() self.entry_format.icursor('end') if style == self.formats[0]: self.var_italic = FALSE self.var_bold = FALSE elif style == self.formats[1]: self.var_italic = TRUE self.var_bold = FALSE elif style == self.formats[2]: self.var_italic = FALSE self.var_bold = TRUE elif style == self.formats[3]: self.var_italic = TRUE self.var_bold = TRUE else: log.error('invalid style') self.preview_font.configure(weight=['normal', 'bold'][self.var_bold], slant=['roman', 'italic'][self.var_italic])
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()