コード例 #1
0
class Tkui(base.BaseUI):
    """
  This is a ui class which handles the complete Tk user interface.
  """
    def __init__(self):
        """ Initializes."""
        base.BaseUI.__init__(self)

        # internal ui queue
        self._event_queue = queue.Queue()

        # map of session -> (bold, foreground, background)
        self._currcolors = {}

        # ses -> string
        self._unfinishedcolor = {}

        self._viewhistory = 0
        self._do_i_echo = 1

        # holds a map of window names -> window references
        self._windows = {}

        # instantiate all the widgets
        self._tk = Tk()
        self._tk.geometry("800x600")

        self.settitle()

        fnt = tkinter.font.Font(family="FixedSys", size=10)

        self._entry = CommandEntry(self._tk,
                                   self,
                                   fg='white',
                                   bg='black',
                                   insertbackground='yellow',
                                   font=fnt,
                                   insertwidth='2')
        self._entry.pack(side='bottom', fill='both')

        self._topframe = Frame(self._tk)
        self._topframe.pack(side='top', fill='both', expand=1)

        self._txt = ScrolledText(self._topframe,
                                 fg='white',
                                 bg='black',
                                 font=fnt,
                                 height=20)
        self._txt.pack(side='bottom', fill='both', expand=1)

        self._txt.bind("<KeyPress>", self._ignoreThis)
        self._txtbuffer = ScrolledText(self._topframe,
                                       fg='white',
                                       bg='black',
                                       font=fnt,
                                       height=20)
        self._txtbuffer.bind("<KeyPress-Escape>", self.escape)
        self._txtbuffer.bind("<KeyPress>", self._ignoreThis)

        self._entry.focus_set()
        self._initColorTags()
        self.dequeue()

        exported.hook_register("config_change_hook", self.configChangeHandler)
        exported.hook_register("to_user_hook", self.write)

        # FIXME - fix this explanation.  this is just terrible.
        tc = config.BoolConfig(
            "saveinputhighlight", 0, 1,
            "Allows you to change the behavior of the command entry.  When "
            "saveinputhighlight is off, we discard whatever is on the entry "
            "line.  When it is on, we will retain the contents allowing you "
            "to press the enter key to do whatever you typed again.")
        exported.add_config("saveinputhighlight", tc)

        self._quit = 0

    def runui(self):
        global HELP_TEXT
        exported.add_help("tkui", HELP_TEXT)
        exported.write_message("For tk help type \"#help tkui\".")
        exported.add_command("colorcheck", colorcheck_cmd)

        # run the tk mainloop here
        self._tk.mainloop()

    def wantMainThread(self):
        # The tkui needs the main thread of execution so we return
        # a 1 here.
        return 1

    def quit(self):
        if not self._quit:
            self._quit = 1
            self._topframe.quit()

    def dequeue(self):
        qsize = self._event_queue.qsize()
        if qsize > 10:
            qsize = 10

        for i in range(qsize):
            ev = self._event_queue.get_nowait()
            ev.execute(self)

        self._tk.after(25, self.dequeue)

    def settitle(self, title=""):
        """
    Sets the title bar to the Lyntin title plus the given string.

    @param title: the title to set
    @type  title: string
    """
        if title:
            title = constants.LYNTINTITLE + title
        else:
            title = constants.LYNTINTITLE
        self._event_queue.put(_TitleEvent(self._tk, title))

    def removeWindow(self, windowname):
        """
    This removes a NamedWindow from our list of NamedWindows.

    @param windowname: the name of the window to write to
    @type  windowname: string
    """
        if windowname in self._windows:
            del self._windows[windowname]

    def writeWindow(self, windowname, message):
        """
    This writes to the window named "windowname".  If the window
    does not exist, we spin one off.  It handles ansi text and
    messages just like writing to the main window.

    @param windowname: the name of the window to write to
    @type  windowname: string

    @param message: the message to write to the window
    @type  message: string or Message instance
    """
        self._event_queue.put(_WriteWindowEvent(windowname, message))

    def writeWindow_internal(self, windowname, message):
        if windowname not in self._windows:
            self._windows[windowname] = NamedWindow(windowname, self, self._tk)
        self._windows[windowname].write(message)

    def _ignoreThis(self, tkevent):
        """ This catches keypresses from the history buffer."""
        # kludge so that ctrl-c doesn't get caught allowing windows
        # users to copy the buffer....
        if tkevent.keycode == 17 or tkevent.keycode == 67:
            return

        self._entry.focus()
        if tkevent.char:
            # we do this little song and dance so as to pass events
            # we don't want to deal with to the entry widget essentially
            # by creating a new event and tossing it in the event list.
            # it only sort of works--but it's the best code we've got
            # so far.
            args = ('event', 'generate', self._entry, "<KeyPress>")
            args = args + ('-rootx', tkevent.x_root)
            args = args + ('-rooty', tkevent.y_root)
            args = args + ('-keycode', tkevent.keycode)
            args = args + ('-keysym', tkevent.keysym)

            self._tk.tk.call(args)

        return "break"

    def pageUp(self):
        """ Handles prior (Page-Up) events."""
        if self._viewhistory == 0:
            self._txtbuffer.pack(side='top', fill='both', expand=1)

            self._viewhistory = 1
            self._txtbuffer.delete("1.0", "end")
            lotofstuff = self._txt.get('1.0', 'end')
            self._txtbuffer.insert('end', lotofstuff)
            for t in self._txt.tag_names():
                taux = None
                tst = 0
                for e in self._txt.tag_ranges(t):
                    if tst == 0:
                        taux = e
                        tst = 1
                    else:
                        tst = 0
                        self._txtbuffer.tag_add(t, str(taux), str(e))

            self._txtbuffer.yview('moveto', '1')
            if os.name != 'posix':
                self._txtbuffer.yview('scroll', '20', 'units')
            self._tk.update_idletasks()
            self._txt.yview('moveto', '1.0')
            if os.name != 'posix':
                self._txt.yview('scroll', '220', 'units')

        else:
            # yscroll up stuff
            self._txtbuffer.yview('scroll', '-15', 'units')

    def pageDown(self):
        """ Handles next (Page-Down) events."""
        if self._viewhistory == 1:
            # yscroll down stuff
            self._txtbuffer.yview('scroll', '15', 'units')

    def escape(self, tkevent):
        """ Handles escape (Escape) events."""
        if self._viewhistory == 1:
            self._txtbuffer.forget()
            self._viewhistory = 0
        else:
            self._entry.clearInput()

    def configChangeHandler(self, args):
        """ This handles config changes including mudecho. """
        name = args["name"]
        newvalue = args["newvalue"]

        if name == "mudecho":
            if newvalue == 1:
                # echo on
                self._do_i_echo = 1
                self._entry.configure(show='')
            else:
                # echo off
                self._do_i_echo = 0
                self._entry.configure(show='*')

    def _yadjust(self):
        """Handles y scrolling after text insertion."""
        self._txt.yview('moveto', '1')
        # if os.name != 'posix':
        self._txt.yview('scroll', '20', 'units')

    def _clipText(self):
        """
    Scrolls the text buffer up so that the new text written at
    the bottom of the text buffer can be seen.
    """
        temp = self._txt.index("end")
        ind = temp.find(".")
        temp = temp[:ind]
        if (temp.isdigit() and int(temp) > 800):
            self._txt.delete("1.0", "100.end")

    def write(self, args):
        """
    This writes text to the text buffer for viewing by the user.

    This is overridden from the 'base.BaseUI'.
    """
        self._event_queue.put(_OutputEvent(args))

    def write_internal(self, args):
        mess = args["message"]
        if type(mess) == bytes:
            mess = message.Message(mess, message.LTDATA)
        elif "window" in mess.hints:
            self.writeWindow_internal(mess.hints["window"], mess)
            return

        line = mess.data
        ses = mess.session

        if line == '' or self.showTextForSession(ses) == 0:
            return

        color, leftover = buffer_write(mess, self._txt, self._currcolors,
                                       self._unfinishedcolor)

        if mess.type == message.MUDDATA:
            self._unfinishedcolor[ses] = leftover
            self._currcolors[ses] = color

        self._clipText()
        self._yadjust()

    def convertColor(self, name):
        """
    Tk has this really weird color palatte.  So I switched to using
    color names in most cases and rgb values in cases where I couldn't
    find a good color name.

    This method allows me to specify either an rgb or a color name
    and it converts the color names to rgb.

    @param name: either an rgb value or a name
    @type  name: string

    @returns: the rgb color value
    @rtype: string
    """
        if name.startswith("#"):
            return name

        rgb = self._tk._getints(
            self._tk.tk.call('winfo', 'rgb', self._txt, name))
        rgb = "#%02x%02x%02x" % (old_div(rgb[0], 256), old_div(
            rgb[1], 256), old_div(rgb[2], 256))
        print(name, "converted to: ", rgb)

        return rgb

    def _initColorTags(self):
        """ Sets up Tk tags for the text widget (fg/bg/u)."""
        for ck in list(fg_color_codes.keys()):
            color = self.convertColor(fg_color_codes[ck])
            self._txt.tag_config(ck, foreground=color)
            self._txtbuffer.tag_config(ck, foreground=color)

        for ck in list(bg_color_codes.keys()):
            self._txt.tag_config(ck, background=bg_color_codes[ck])
            self._txtbuffer.tag_config(ck, background=bg_color_codes[ck])

        self._txt.tag_config("u", underline=1)
        self._txtbuffer.tag_config("u", underline=1)

    def colorCheck(self):
        """
    Goes through and displays all the combinations of fg and bg
    with the text string involved.  Purely for debugging
    purposes.
    """
        fgkeys = ['30', '31', '32', '33', '34', '35', '36', '37']
        bgkeys = ['40', '41', '42', '43', '44', '45', '46', '47']

        self._txt.insert('end', 'color check:\n')
        for bg in bgkeys:
            for fg in fgkeys:
                self._txt.insert('end', str(fg), (fg, bg))
                self._txt.insert('end', str("b" + fg), ("b" + fg, bg))
            self._txt.insert('end', '\n')

            for fg in fgkeys:
                self._txt.insert('end', str(fg), (fg, "b" + bg))
                self._txt.insert('end', str("b" + fg), ("b" + fg, "b" + bg))
            self._txt.insert('end', '\n')

        self._txt.insert('end', '\n')
        self._txt.insert('end', '\n')
コード例 #2
0
class Editor(Toplevel):
    @property
    def text(self):
        return self.sc_text.get('1.0', END)

    @text.setter
    def text(self, value):
        pos = self.sc_text.yview()
        self.sc_text.delete('1.0', END)
        self.sc_text.insert(END, value)
        self.sc_text.yview_moveto(pos[0])

    def __init__(self, master):
        super().__init__(master)

        self.index_matches = None
        self.current_index = None

        self.search_content = StringVar()
        self.sc_text = ScrolledText(self, wrap=NONE)

        self.x_scrollbar_content_text = Scrollbar(self,
                                                  orient=HORIZONTAL,
                                                  command=self.sc_text.xview)
        self.sc_text.configure(
            xscrollcommand=self.x_scrollbar_content_text.set)
        self.wrap_text = IntVar()
        self.wrap_text.set(False)

        Button(self, text='<-', command=self.click_prev_button).grid(row=0,
                                                                     column=0)
        self.search_content_entry = Entry(self,
                                          textvariable=self.search_content)
        self.search_content_entry.bind(
            '<KeyRelease>',
            lambda event: self.highlight_pattern(event.widget.get()))
        self.search_content_entry.grid(row=0, column=1, sticky=W + E)
        Button(self, text='->', command=self.click_next_button).grid(row=0,
                                                                     column=2)
        Checkbutton(self,
                    text="Wrap Text",
                    variable=self.wrap_text,
                    command=lambda: self.sc_text.config(
                        wrap=NONE
                        if not self.wrap_text.get() else WORD)).grid(row=1,
                                                                     column=0)
        self.sc_text.grid(row=2, column=0, columnspan=3, sticky=N + S + E + W)
        self.x_scrollbar_content_text.grid(row=3,
                                           column=0,
                                           columnspan=3,
                                           sticky=W + E)

        self.columnconfigure(1, weight=1)
        self.rowconfigure(2, weight=1)
        self.withdraw()
        self.protocol('WM_DELETE_WINDOW', lambda t=self: t.withdraw())

    def click_next_button(self):
        if not self.index_matches:
            return
        if self.current_index is None:
            self.current_index = 0
        else:
            self.current_index += 1
        self.current_index %= len(self.index_matches)
        self.sc_text.see(self.index_matches[self.current_index])

    def click_prev_button(self):
        if not self.index_matches:
            return
        if self.current_index is None:
            self.current_index = len(self.index_matches) - 1
        else:
            self.current_index -= 1
        self.current_index %= len(self.index_matches)
        self.sc_text.see(self.index_matches[self.current_index])

    def highlight_pattern(self, pattern, start="1.0", end="end"):
        for t in self.sc_text.tag_names():
            self.sc_text.tag_delete(t)

        if not pattern:
            return
        self.index_matches = []
        self.current_index = None

        self.sc_text.tag_config('found', background="green")
        start = self.sc_text.index(start)
        end = self.sc_text.index(end)
        self.sc_text.mark_set("matchStart", start)
        self.sc_text.mark_set("matchEnd", start)
        self.sc_text.mark_set("searchLimit", end)
        count = IntVar()
        while True:
            index = self.sc_text.search(pattern,
                                        "matchEnd",
                                        "searchLimit",
                                        count=count)
            if index == "":
                break
            self.sc_text.mark_set("matchStart", index)
            match_index = "%s+%sc" % (index, count.get())
            self.index_matches.append(match_index)
            self.sc_text.mark_set("matchEnd", match_index)
            self.sc_text.tag_add('found', "matchStart", "matchEnd")
コード例 #3
0
class ScrolledTextInfoFrame(Frame):

    def __init__(self, master, *args, **kwargs):
        self.master = master
        super(ScrolledTextInfoFrame, self).__init__(self.master, *args,
                                                    **kwargs)

        self.saved_time = StringVar()

        self.highlighter = Highlighter()

        self._create_widgets()

        # the associated file
        self._file = None

    # TODO: rename the textentry
    def _create_widgets(self):
        # create a Text widget and read in the file
        self.textentry = ScrolledText(self, wrap=WORD)
        self.textentry.grid(column=0, row=0, columnspan=2, sticky='nsew')
        for key, value in self.highlighter.style:
            self.textentry.tag_configure(key, foreground=value)
        self.save_label = Label(self, textvar=self.saved_time)
        self.save_label.grid(column=0, row=1, sticky='es')
        self.save_btn = Button(self, text="Save", command=self.save_file)
        self.save_btn.grid(column=1, row=1, sticky='es')
        self.grid_columnconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=0)
        self.grid_rowconfigure(0, weight=1)
        self.grid_rowconfigure(1, weight=0)

    def update(self):
        self._update_savetime()
        self.textentry.delete(1.0, END)
        with open(self.file.file, 'r') as file:
            self.textentry.insert(END, file.read())
        if HAS_PYGMENTS:
            if self.highlighter.change_type(self.file.dtype):
                # remove current tags
                for tag in self.textentry.tag_names():
                    self.textentry.tag_configure(tag, foreground="#000000")
                for key, value in self.highlighter.style.items():
                    self.textentry.tag_configure(key, foreground=value)
            self.syn()

    def _update_savetime(self):
        self.saved_time.set("Last saved:\t{0}\t".format(self.file.saved_time))

    @threaded
    def syn(self, event=None):
        """
        Allow for syntax highlighting.
        Source: https://stackoverflow.com/a/30199105
        This will highlight the entire document once. Dynamic highlighting not
        yet supported.
        #TODO: (maybe?): https://stackoverflow.com/questions/32058760/improve-pygments-syntax-highlighting-speed-for-tkinter-text/32064481  # noqa

        This is threaded to hopefully stop it blocking the view from displaying
        and causing a race condition.
        """
        self.textentry.mark_set("range_start", "1.0")
        data = self.textentry.get("1.0", "end-1c")
        lexer = self.highlighter.lexer
        if lexer is not None:
            for token, content in lex(data, lexer()):
                self.textentry.mark_set("range_end",
                                        "range_start + %dc" % len(content))
                self.textentry.tag_add(str(token), "range_start", "range_end")
                self.textentry.mark_set("range_start", "range_end")

    def save_file(self):
        """ Write the current data in the text widget back to the file """
        file_contents = self.textentry.get("1.0", "end-1c")
        with open(self.file.file, 'w') as file:
            file.write(file_contents)
        savetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        self.file.saved_time = savetime
        self._update_savetime()
        # also re-apply the syntax highlighting
        self.syn()

    @property
    def file(self):
        return self._file

    @file.setter
    def file(self, other):
        """
        Set the file property to whatever the new file is.
        When this happens the update command will be called which will redraw
        the channel info list
        """
        # if the file is being set as a con_file continue
        if other != self._file:
            self._file = other
            self.update()
コード例 #4
0
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()
コード例 #5
0
ファイル: TextEditor.py プロジェクト: Drax-il/TextEditor
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()