class Editor(Component): def __init__(self, page, cid): super().__init__(page, cid) self._box = None self._strvar_title = tk.StringVar() self._text_widget = None @property def parts(self): """Dictionary that contains parts of this component. Exposed parts are: "text_widget" and "strvar_title" """ return self._parts def build(self, title=None, text=None, readonly=False, width=45, height=10, new_col=True, side=tk.BOTTOM, anchor="nw", padx=5, pady=5, expand=False, fill=None): # get new box self._box = self._page.new_box(new_col, side, anchor, padx, pady, expand, fill) # set title if title: self._strvar_title.set(title) title_label = tk.Label(self._box, textvariable=self._strvar_title) title_label.pack(anchor="nw") self._parts["strvar_title"] = self._strvar_title # create text widget from tkinter.scrolledtext import ScrolledText self._text_widget = ScrolledText(self._box, width=width, height=height) self._text_widget.pack(fill=fill, expand=expand) self._parts["text"] = self._text_widget # fill text widget if text is not None: self._text_widget.insert("1.0", text) if readonly: self._text_widget.config(state=tk.DISABLED) def read(self): if self._text_widget: return self._text_widget.get("1.0", tk.END) return None def update(self, title=None, text=None, readonly=None): if title is not None: self._strvar_title.set(title) if text is not None: readonly_cache = self._text_widget.cget("state") readonly_cache = True if readonly_cache == tk.DISABLED else False if readonly_cache: self._text_widget.config(state=tk.NORMAL) self._text_widget.delete("1.0", tk.END) self._text_widget.insert("1.0", text) if readonly_cache: self._text_widget.config(state=tk.DISABLED) if readonly is not None: state = tk.DISABLED if readonly else tk.NORMAL self._text_widget.config(state=state) def _gen_info(self): Info = namedtuple("Info", ("page", "cid", "component")) info = Info(self._page, self._cid, self) return info
class EditorTab(tk.Frame): def __init__(self, master, filepath: str, new_file=False): tk.Frame.__init__(self, master) self.new_file = new_file self.filepath = filepath self.filename = get_filename(filepath) if not new_file else filepath self.master = master self.modified = False self.text_editor = ScrolledText(self, font=("", 15), undo=True, maxundo=-1, wrap="none") self.text_editor.config(highlightthickness=0, bd=0) self.text_editor.grid(row=0, column=1, sticky=tk.NSEW) self.scrollbar_x = tk.Scrollbar(self, orient=tk.HORIZONTAL, command=self.text_editor.xview) self.scrollbar_x.grid(row=1, column=0, columnspan=2, stick=tk.EW) self.text_editor.configure(xscrollcommand=self.scrollbar_x.set) self.line_nb_canvas = tk.Canvas(self, bg=self.text_editor.cget("bg"), bd=0, highlightthickness=0) self.line_nb_canvas.grid_propagate(False) self.line_nb_canvas.grid(row=0, column=0, sticky=tk.NS) self.grid_rowconfigure(0, weight=1) self.grid_columnconfigure(1, weight=1) self.default_file_content = str() @property def id(self) -> str: return str(self) def get(self) -> str: return self.text_editor.get("1.0", "end-1c") @property def lines(self): return self.get().splitlines() def update_lines(self): self.line_nb_canvas.delete("all") font = self.text_editor.cget("font") i = 1 y = 0 while self.text_editor.compare(f"{i}.0", "<", tk.END): dline = self.text_editor.dlineinfo(f"{i}.0") if dline: y = dline[1] else: y = -20 self.line_nb_canvas.create_text(1, y, anchor="ne", text=str(i), font=font, fill=Color(175, 175, 175).hex) i += 1 all_boxes = [ self.line_nb_canvas.bbox(item) for item in self.line_nb_canvas.find_all() ] max_width = (min(box[2] - box[0] for box in all_boxes)) * 4 self.line_nb_canvas.configure(width=max_width + 20) for item in self.line_nb_canvas.find_all(): self.line_nb_canvas.move(item, max_width, 0) def get_filename_from_champion_name(self) -> (str, None): content = self.lines for line in content: if line.startswith(".name"): first_quote = line.find('"') if first_quote == -1: break second_quote = line.find('"', first_quote + 1) if second_quote == -1: break name = line[first_quote + 1:second_quote] if len(name) == 0: break return name.replace(" ", "_").lower() + ".s" return None def open(self) -> bool: if not os.path.isfile(self.filepath): showwarning("An error occurs...", f"Can't open '{self.filepath}'") return False with open(self.filepath, "r") as file: self.default_file_content = file.read() self.text_editor.insert("1.0", self.default_file_content) self.text_editor.edit_reset() self.text_editor.edit_modified(False) self.set_modified_status(False, on_opening=True) return True def save(self) -> bool: if self.new_file: return self.master.save_file_as() self.default_file_content = self.text_editor.get("1.0", "end-1c") with open(self.filepath, "w") as file: file.write(self.default_file_content) self.set_modified_status(False) self.text_editor.edit_modified(False) return True def save_as(self, filepath: str) -> bool: self.master.files_opened[filepath] = self.master.files_opened[ self.filepath] self.master.files_opened.pop(self.filepath) self.filepath = filepath self.filename = get_filename(filepath) self.new_file = False return self.save() def close(self) -> bool: if self.modified: save_file = askyesnocancel( f"{self.filename} - Modifications not saved", "This file was modified and was not saved.\nDo you want to save this file ?" ) if save_file is None: return False if save_file and not self.save(): return False self.master.forget(self.id) self.master.files_opened.pop(self.filepath) return True def undo(self) -> None: try: self.text_editor.edit_undo() except tk.TclError: pass def redo(self) -> None: try: self.text_editor.edit_redo() except tk.TclError: pass def copy_to_clipboard(self, remove_from_editor=False) -> bool: try: txt = self.text_editor.get("sel.first", "sel.last") self.clipboard_clear() self.clipboard_append(txt) if remove_from_editor: self.text_editor.delete("sel.first", "sel.last") except tk.TclError: return False return True def paste_from_clipboard(self) -> None: try: self.text_editor.get("sel.first", "sel.last") except tk.TclError: pass else: self.text_editor.mark_set("insert", "sel.first") self.text_editor.delete("sel.first", "sel.last") self.text_editor.insert("insert", self.clipboard_get()) def check_file_status(self) -> None: actual = self.text_editor.get("1.0", "end-1c") if self.text_editor.edit_modified(): self.text_editor.edit_separator() self.text_editor.edit_modified(False) self.set_modified_status(actual != self.default_file_content) def set_modified_status(self, status: bool, on_opening=False) -> None: self.modified = status if not on_opening: if self.modified and not self.new_file: self.master.tab(self.id, text=self.filename + " - Modified") else: self.master.tab(self.id, text=self.filename) def add(self) -> None: self.master.add(self, text=self.filename) def select(self) -> None: self.master.select(self.id) self.text_editor.focus_set() def set_template(self, name: str, comment: str, author: str) -> None: content = [ line for line in self.lines if not line.startswith(".name") and not line.startswith(".comment") ] header = [ "#", "# {name} champion for CoreWar".format(name=name), "#", "# By {author}".format(author=author), "#", "# {date}".format(date=date.today().strftime("%c")), "#", "" ".name \"{name}\"".format(name=name), ".comment \"{comment}\"".format(comment=comment), "" ] content = header + content self.text_editor.delete("1.0", "end") self.text_editor.insert("1.0", "\n".join(content)) def insert_command(self, cmd: str) -> None: insert = self.text_editor.index(tk.INSERT).split(".") end_of_line = insert[0] + "." + tk.END self.text_editor.insert(end_of_line, "\n" + cmd)
class Application(tkinter.Tk): def __init__(self): """Initialize widgets, methods.""" tkinter.Tk.__init__(self) self.grid() fontoptions = families(self) font = Font(family="Verdana", size=10) menubar = tkinter.Menu(self) fileMenu = tkinter.Menu(menubar, tearoff=0) editMenu = tkinter.Menu(menubar, tearoff=0) fsubmenu = tkinter.Menu(editMenu, tearoff=0) ssubmenu = tkinter.Menu(editMenu, tearoff=0) # adds fonts to the font submenu and associates lambda functions for option in fontoptions: fsubmenu.add_command(label=option, command = lambda: font.configure(family=option)) # adds values to the size submenu and associates lambda functions for value in range(1,31): ssubmenu.add_command(label=str(value), command = lambda: font.configure(size=value)) # adds commands to the menus menubar.add_cascade(label="File",underline=0, menu=fileMenu) menubar.add_cascade(label="Edit",underline=0, menu=editMenu) fileMenu.add_command(label="New", underline=1,command=self.new, accelerator="Ctrl+N") fileMenu.add_command(label="Open", command=self.open, accelerator="Ctrl+O") fileMenu.add_command(label="Save", command=self.save, accelerator="Ctrl+S") fileMenu.add_command(label="Exit", underline=1,command=exit, accelerator="Ctrl+Q") editMenu.add_command(label="Copy", command=self.copy, accelerator="Ctrl+C") editMenu.add_command(label="Cut", command=self.cut, accelerator="Ctrl+X") editMenu.add_command(label="Paste", command=self.paste, accelerator="Ctrl+V") editMenu.add_cascade(label="Font", underline=0, menu=fsubmenu) editMenu.add_cascade(label="Size", underline=0, menu=ssubmenu) editMenu.add_command(label="Color", command=self.color) editMenu.add_command(label="Bold", command=self.bold, accelerator="Ctrl+B") editMenu.add_command(label="Italic", command=self.italic, accelerator="Ctrl+I") editMenu.add_command(label="Underline", command=self.underline, accelerator="Ctrl+U") editMenu.add_command(label="Overstrike", command=self.overstrike, accelerator="Ctrl+T") editMenu.add_command(label="Undo", command=self.undo, accelerator="Ctrl+Z") editMenu.add_command(label="Redo", command=self.redo, accelerator="Ctrl+Y") self.config(menu=menubar) self.bind_all("<Control-n>", self.new) self.bind_all("<Control-o>", self.open) self.bind_all("<Control-s>", self.save) self.bind_all("<Control-q>", self.exit) self.bind_all("<Control-b>", self.bold) self.bind_all("<Control-i>", self.italic) self.bind_all("<Control-u>", self.underline) self.bind_all("<Control-T>", self.overstrike) self.bind_all("<Control-z>", self.undo) self.bind_all("<Control-y>", self.redo) self.text = ScrolledText(self, state='normal', height=80, wrap='word', font = font, pady=2, padx=3, undo=True) self.text.grid(column=0, row=0, sticky='NSEW') # Frame configuration self.grid_columnconfigure(0, weight=1) self.resizable(True, True) def new(self, *args): """Creates a new window.""" app = Application() app.title('Python Text Editor') app.option_add('*tearOff', False) app.mainloop() def color(self): """Changes selected text color.""" try: (rgb, hx) = tkinter.colorchooser.askcolor() self.text.tag_add('color', 'sel.first', 'sel.last') self.text.tag_configure('color', foreground=hx) except TclError: pass def bold(self, *args): """Toggles bold for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "bold" in current_tags: self.text.tag_remove("bold", "sel.first", "sel.last") else: self.text.tag_add("bold", "sel.first", "sel.last") bold_font = Font(self.text, self.text.cget("font")) bold_font.configure(weight="bold") self.text.tag_configure("bold", font=bold_font) except TclError: pass def italic(self, *args): """Toggles italic for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "italic" in current_tags: self.text.tag_remove("italic", "sel.first", "sel.last") else: self.text.tag_add("italic", "sel.first", "sel.last") italic_font = Font(self.text, self.text.cget("font")) italic_font.configure(slant="italic") self.text.tag_configure("italic", font=italic_font) except TclError: pass def underline(self, *args): """Toggles underline for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "underline" in current_tags: self.text.tag_remove("underline", "sel.first", "sel.last") else: self.text.tag_add("underline", "sel.first", "sel.last") underline_font = Font(self.text, self.text.cget("font")) underline_font.configure(underline=1) self.text.tag_configure("underline", font=underline_font) except TclError: pass def overstrike(self, *args): """Toggles overstrike for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "overstrike" in current_tags: self.text.tag_remove("overstrike", "sel.first", "sel.last") else: self.text.tag_add("overstrike", "sel.first", "sel.last") overstrike_font = Font(self.text, self.text.cget("font")) overstrike_font.configure(overstrike=1) self.text.tag_configure("overstrike", font=overstrike_font) except TclError: pass def undo(self, *args): """Undo function""" try: self.text.edit_undo() except TclError: pass def redo(self, *args): """Redo function""" try: self.text.edit_redo() except TclError: pass def copy(self, *args): """Copy text""" self.clipboard_clear() self.clipboard_append(self.text.selection_get()) def cut(self, *args): """Cut text""" self.copy self.text.delete("sel.first", "sel.last") def paste(self, *args): """Paste text""" insertion = self.selection_get(selection = "CLIPBOARD") self.text.insert(0.0, insertion) def open(self, *args): """Opens a file dialog to open a plain text file.""" filename = tkinter.filedialog.askopenfilename() with open(filename) as f: text = f.read() self.text.delete("1.0", "end") self.text.insert('insert', text) def save(self, *args): try: """Opens a file dialog to save the text in plain text format.""" text = self.text.get("1.0", "end") filename = tkinter.filedialog.asksaveasfilename() with open(filename, 'w') as f: f.write(text) except FileNotFoundError: pass def exit(self, *args): """Exits the program.""" self.quit()
class Application(tkinter.Tk): def __init__(self): """Initialize widgets, methods.""" tkinter.Tk.__init__(self) self.grid() fontoptions = families(self) font = Font(family="Verdana", size=10) menubar = tkinter.Menu(self) fileMenu = tkinter.Menu(menubar, tearoff=0) editMenu = tkinter.Menu(menubar, tearoff=0) fsubmenu = tkinter.Menu(editMenu, tearoff=0) ssubmenu = tkinter.Menu(editMenu, tearoff=0) # adds fonts to the font submenu and associates lambda functions for option in fontoptions: fsubmenu.add_command(label=option, command = lambda: font.configure(family=option)) # adds values to the size submenu and associates lambda functions for value in range(1,31): ssubmenu.add_command(label=str(value), command = lambda: font.configure(size=value)) # adds commands to the menus menubar.add_cascade(label="File",underline=0, menu=fileMenu) menubar.add_cascade(label="Edit",underline=0, menu=editMenu) fileMenu.add_command(label="New", underline=1, command=self.new, accelerator="Ctrl+N") fileMenu.add_command(label="Open", command=self.open, accelerator="Ctrl+O") fileMenu.add_command(label="Save", command=self.save, accelerator="Ctrl+S") fileMenu.add_command(label="Exit", underline=1, command=exit, accelerator="Ctrl+Q") editMenu.add_command(label="Copy", command=self.copy, accelerator="Ctrl+C") editMenu.add_command(label="Cut", command=self.cut, accelerator="Ctrl+X") editMenu.add_command(label="Paste", command=self.paste, accelerator="Ctrl+V") editMenu.add_cascade(label="Font", underline=0, menu=fsubmenu) editMenu.add_cascade(label="Size", underline=0, menu=ssubmenu) editMenu.add_command(label="Color", command=self.color) editMenu.add_command(label="Bold", command=self.bold, accelerator="Ctrl+B") editMenu.add_command(label="Italic", command=self.italic, accelerator="Ctrl+I") editMenu.add_command(label="Underline", command=self.underline, accelerator="Ctrl+U") editMenu.add_command(label="Overstrike", command=self.overstrike, accelerator="Ctrl+T") editMenu.add_command(label="Undo", command=self.undo, accelerator="Ctrl+Z") editMenu.add_command(label="Redo", command=self.redo, accelerator="Ctrl+Y") self.config(menu=menubar) """Accelerator bindings. The cut, copy, and paste functions are not bound to keyboard shortcuts because Windows already binds them, so if Tkinter bound them as well whenever you typed ctrl+v the text would be pasted twice.""" self.bind_all("<Control-n>", self.new) self.bind_all("<Control-o>", self.open) self.bind_all("<Control-s>", self.save) self.bind_all("<Control-q>", self.exit) self.bind_all("<Control-b>", self.bold) self.bind_all("<Control-i>", self.italic) self.bind_all("<Control-u>", self.underline) self.bind_all("<Control-T>", self.overstrike) self.bind_all("<Control-z>", self.undo) self.bind_all("<Control-y>", self.redo) self.text = ScrolledText(self, state='normal', height=30, wrap='word', font = font, pady=2, padx=3, undo=True) self.text.grid(column=0, row=0, sticky='NSEW') # Frame configuration self.grid_columnconfigure(0, weight=1) self.resizable(True, True) """Command functions. *args is included because the keyboard bindings pass two arguments to the functions, while clicking in the menu passes only 1.""" def new(self, *args): """Creates a new window.""" app = Application() app.title('Python Text Editor') app.option_add('*tearOff', False) app.mainloop() def color(self): """Changes selected text color.""" try: (rgb, hx) = tkinter.colorchooser.askcolor() self.text.tag_add('color', 'sel.first', 'sel.last') self.text.tag_configure('color', foreground=hx) except TclError: pass def bold(self, *args): """Toggles bold for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "bold" in current_tags: self.text.tag_remove("bold", "sel.first", "sel.last") else: self.text.tag_add("bold", "sel.first", "sel.last") bold_font = Font(self.text, self.text.cget("font")) bold_font.configure(weight="bold") self.text.tag_configure("bold", font=bold_font) except TclError: pass def italic(self, *args): """Toggles italic for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "italic" in current_tags: self.text.tag_remove("italic", "sel.first", "sel.last") else: self.text.tag_add("italic", "sel.first", "sel.last") italic_font = Font(self.text, self.text.cget("font")) italic_font.configure(slant="italic") self.text.tag_configure("italic", font=italic_font) except TclError: pass def underline(self, *args): """Toggles underline for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "underline" in current_tags: self.text.tag_remove("underline", "sel.first", "sel.last") else: self.text.tag_add("underline", "sel.first", "sel.last") underline_font = Font(self.text, self.text.cget("font")) underline_font.configure(underline=1) self.text.tag_configure("underline", font=underline_font) except TclError: pass def overstrike(self, *args): """Toggles overstrike for selected text.""" try: current_tags = self.text.tag_names("sel.first") if "overstrike" in current_tags: self.text.tag_remove("overstrike", "sel.first", "sel.last") else: self.text.tag_add("overstrike", "sel.first", "sel.last") overstrike_font = Font(self.text, self.text.cget("font")) overstrike_font.configure(overstrike=1) self.text.tag_configure("overstrike", font=overstrike_font) except TclError: pass def undo(self, *args): """Undo function""" try: self.text.edit_undo() except TclError: pass def redo(self, *args): """Redo function""" try: self.text.edit_redo() except TclError: pass def copy(self, *args): """Copy text""" self.clipboard_clear() self.clipboard_append(self.text.selection_get()) def cut(self, *args): """Cut text""" self.copy self.text.delete("sel.first", "sel.last") def paste(self, *args): """Paste text""" insertion = self.selection_get(selection = "CLIPBOARD") self.text.insert(0.0, insertion) def open(self, *args): """Opens a file dialog to open a plain text file.""" filename = tkinter.filedialog.askopenfilename() with open(filename) as f: text = f.read() self.text.delete("1.0", "end") self.text.insert('insert', text) def save(self, *args): try: """Opens a file dialog to save the text in plain text format.""" text = self.text.get("1.0", "end") filename = tkinter.filedialog.asksaveasfilename() with open(filename, 'w') as f: f.write(text) except FileNotFoundError: pass def exit(self, *args): """Exits the program.""" self.quit()