DEV Community

Insidious
Insidious

Posted on

Make a simple text editor using Python

Hey everyone!

Today we'll be making a simple text editor using Python and Tkinter.

What is tkinter?

  • It is a standard Python interface to the Tk GUI toolkit shipped with Python.

How do I know if I have installed tkinter?

  • Most probably, you would have it installed. Try running python -m tkinter in your console. If it doesn't give an error, it means you have tkinter installed and ready to use!

I don't have tkinter installed, what should I do?

  • Just make sure you have Python and pip working, then run pip install tk in the console.

Prerequisites

  • Python 3.9.x
  • Code editor (VSCode, PyCharm, etc)

Now that that's out of the way, let's get started!!!

The tutorial starts here...

First, we'll make a file structure sort of thing.
We'll need these files (they can be in the root directory):

  • main.py
  • file_menu.py
  • edit_menu.py
  • format_menu.py
  • help_menu.py

main.py

For the main.py file, we'll start by importing the tkinter library and the menu files we made just now:

# import statements
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
from tkinter.font import Font
from tkinter.scrolledtext import *
import file_menu
import edit_menu
import format_menu
import help_menu
Enter fullscreen mode Exit fullscreen mode

Then, we'll make a tkinter window like this:

# creating a tkinter window
root = Tk()
Enter fullscreen mode Exit fullscreen mode

Then, we'll give our text editor some dimensions and a title like this:

# gives the window a title and dimensions
root.title("TextEditor-newfile")
root.geometry("300x250+300+300")
root.minsize(width=400, height=400)
Enter fullscreen mode Exit fullscreen mode

Then, we'll add this code:

# i don't really know how to explain what this code does, but yeah it's important
text = ScrolledText(root, state='normal', height=400, width=400, wrap='word', pady=2, padx=3, undo=True)
text.pack(fill=Y, expand=1)
text.focus_set()
Enter fullscreen mode Exit fullscreen mode

Then, we'll make a menubar:

# creating a menubar
menubar = Menu(root)
Enter fullscreen mode Exit fullscreen mode

Then, we'll add our menus to the menubar:

# adding our menus to the menubar
file_menu.main(root, text, menubar)
edit_menu.main(root, text, menubar)
format_menu.main(root, text, menubar)
help_menu.main(root, text, menubar)
Enter fullscreen mode Exit fullscreen mode

Finally, we'll add this code so this editor actually functions:

# running the whole program
root.mainloop()
Enter fullscreen mode Exit fullscreen mode

Let's move on to our menu files, shall we?

file_menu.py

First, we'll import stuff:

# import statements
from tkinter import *
from tkinter.filedialog import *
from tkinter.messagebox import *
Enter fullscreen mode Exit fullscreen mode

Then, we'll make a class File:

# creating File
class File():
Enter fullscreen mode Exit fullscreen mode

Then we'll add all the functions we need to our File class:

    def newFile(self):
        self.filename = "Untitled"
        self.text.delete(0.0, END)

    def saveFile(self):
        try:
            t = self.text.get(0.0, END)
            f = open(self.filename, 'w')
            f.write(t)
            f.close()
        except:
            self.saveAs()

    def saveAs(self):
        f = asksaveasfile(mode='w', defaultextension='.txt')
        t = self.text.get(0.0, END)
        try:
            f.write(t.rstrip())
        except:
            showerror(title="Oops!", message="Unable to save file...")

    def openFile(self):
        f = askopenfile(mode='r')
        self.filename = f.name
        t = f.read()
        self.text.delete(0.0, END)
        self.text.insert(0.0, t)

    def quit(self):
        entry = askyesno(title="Quit", message="Are you sure you want to quit?")
        if entry == True:
            self.root.destroy()

    def __init__(self, text, root):
        self.filename = None
        self.text = text
        self.root = root
Enter fullscreen mode Exit fullscreen mode

In the above code, we've added some functions:

  • New File
  • Save File
  • Save File As
  • Open File
  • Quit Editor

Then, we'll make a main function (note that this function should not be under the File class):

def main(root, text, menubar):
    filemenu = Menu(menubar)
    objFile = File(text, root)
    filemenu.add_command(label="New", command=objFile.newFile)
    filemenu.add_command(label="Open", command=objFile.openFile)
    filemenu.add_command(label="Save", command=objFile.saveFile)
    filemenu.add_command(label="Save As...", command=objFile.saveAs)
    filemenu.add_separator()
    filemenu.add_command(label="Quit", command=objFile.quit)
    menubar.add_cascade(label="File", menu=filemenu)
    root.config(menu=menubar)
Enter fullscreen mode Exit fullscreen mode

In the above code, we made a main function which basically executes all functions under the File class.

Finally, we add some error-handling sort of thingy:

if __name__ == "__main__":
    print("Please run 'main.py'")
Enter fullscreen mode Exit fullscreen mode

edit_menu.py

We'll start by importing stuff:

# import statements
from tkinter import *
from tkinter.simpledialog import *
from tkinter.filedialog import *
from tkinter.messagebox import *
Enter fullscreen mode Exit fullscreen mode

Then, we'll make a class Edit:

# creating Edit
class Edit():
Enter fullscreen mode Exit fullscreen mode

Then we'll add all the necessary functions under this Edit class:

    def popup(self, event):
        self.rightClick.post(event.x_root, event.y_root)

    def copy(self, *args):
        sel = self.text.selection_get()
        self.clipboard = sel

    def cut(self, *args):
        sel = self.text.selection_get()
        self.clipboard = sel
        self.text.delete(SEL_FIRST, SEL_LAST)

    def paste(self, *args):
        self.text.insert(INSERT, self.clipboard)

    def selectAll(self, *args):
        self.text.tag_add(SEL, "1.0", END)
        self.text.mark_set(0.0, END)
        self.text.see(INSERT)

    def undo(self, *args):
        self.text.edit_undo()

    def redo(self, *args):
        self.text.edit_redo()

    def find(self, *args):
        self.text.tag_remove('found', '1.0', END)
        target = askstring('Find', 'Search String:')

        if target:
            idx = '1.0'
            while 1:
                idx = self.text.search(target, idx, nocase=1, stopindex=END)
                if not idx: break
                lastidx = '%s+%dc' % (idx, len(target))
                self.text.tag_add('found', idx, lastidx)
                idx = lastidx
            self.text.tag_config('found', foreground='white', background='blue')

    def __init__(self, text, root):
        self.clipboard = None
        self.text = text
        self.rightClick = Menu(root)
Enter fullscreen mode Exit fullscreen mode

In the above code, we make some functions:

  • Copy
  • Cut
  • Paste
  • Select All
  • Undo
  • Redo
  • Find

Then, we make a main function outside the Edit class:

def main(root, text, menubar):

    objEdit = Edit(text, root)

    editmenu = Menu(menubar)
    editmenu.add_command(label="Copy", command=objEdit.copy, accelerator="Ctrl+C")
    editmenu.add_command(label="Cut", command=objEdit.cut, accelerator="Ctrl+X")
    editmenu.add_command(label="Paste", command=objEdit.paste, accelerator="Ctrl+V")
    editmenu.add_command(label="Undo", command=objEdit.undo, accelerator="Ctrl+Z")
    editmenu.add_command(label="Redo", command=objEdit.redo, accelerator="Ctrl+Y")
    editmenu.add_command(label="Find", command=objEdit.find, accelerator="Ctrl+F")
    editmenu.add_separator()
    editmenu.add_command(label="Select All", command=objEdit.selectAll, accelerator="Ctrl+A")
    menubar.add_cascade(label="Edit", menu=editmenu)

    root.bind_all("<Control-z>", objEdit.undo)
    root.bind_all("<Control-y>", objEdit.redo)
    root.bind_all("<Control-f>", objEdit.find)
    root.bind_all("Control-a", objEdit.selectAll)

    objEdit.rightClick.add_command(label="Copy", command=objEdit.copy)
    objEdit.rightClick.add_command(label="Cut", command=objEdit.cut)
    objEdit.rightClick.add_command(label="Paste", command=objEdit.paste)
    objEdit.rightClick.add_separator()
    objEdit.rightClick.add_command(label="Select All", command=objEdit.selectAll)
    objEdit.rightClick.bind("<Control-q>", objEdit.selectAll)

    text.bind("<Button-3>", objEdit.popup)

    root.config(menu=menubar)
Enter fullscreen mode Exit fullscreen mode

Finally, we'll do some error-handling thingy:

if __name__ == "__main__":
    print("Please run 'main.py'")
Enter fullscreen mode Exit fullscreen mode

format_menu.py

We'll start by importing stuff:

# import statements
from tkinter import *
from tkinter.colorchooser import askcolor
from tkinter.font import Font, families
from tkinter.scrolledtext import *
import time
Enter fullscreen mode Exit fullscreen mode

Then, we make a class Format:

class Format():
Enter fullscreen mode Exit fullscreen mode

Then, we add the necessary functions:

    def __init__(self, text):
        self.text = text

    def changeBg(self):
        (triple, hexstr) = askcolor()
        if hexstr:
            self.text.config(bg=hexstr)

    def changeFg(self):
        (triple, hexstr) = askcolor()
        if hexstr:
            self.text.config(fg=hexstr)

    def bold(self, *args):  # Works only if text is selected
        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:
            pass

    def italic(self, *args):  # Works only if text is selected
        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:
            pass

    def underline(self, *args):  # Works only if text is selected
        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:
            pass

    def overstrike(self, *args):  # Works only if text is selected
        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:
            pass

    def addDate(self):
        full_date = time.localtime()
        day = str(full_date.tm_mday)
        month = str(full_date.tm_mon)
        year = str(full_date.tm_year)
        date = day + '/' + month + '/' + year
        self.text.insert(INSERT, date, "a")
Enter fullscreen mode Exit fullscreen mode

In the above code, we added some functions:

  • Change Background
  • Change Font Group
  • Bold Text
  • Italic Text
  • Underline Text
  • Over Strike Text
  • Add Date

Then, we make a main function:

def main(root, text, menubar):
    objFormat = Format(text)

    fontoptions = families(root)
    font = Font(family="Arial", size=10)
    text.configure(font=font)

    formatMenu = Menu(menubar)

    fsubmenu = Menu(formatMenu, tearoff=0)
    ssubmenu = Menu(formatMenu, tearoff=0)

    for option in fontoptions:
        fsubmenu.add_command(label=option, command=lambda option=option: font.configure(family=option))
    for value in range(1, 31):
        ssubmenu.add_command(label=str(value), command=lambda value=value: font.configure(size=value))

    formatMenu.add_command(label="Change Background", command=objFormat.changeBg)
    formatMenu.add_command(label="Change Font Color", command=objFormat.changeFg)
    formatMenu.add_cascade(label="Font", underline=0, menu=fsubmenu)
    formatMenu.add_cascade(label="Size", underline=0, menu=ssubmenu)
    formatMenu.add_command(label="Bold", command=objFormat.bold, accelerator="Ctrl+B")
    formatMenu.add_command(label="Italic", command=objFormat.italic, accelerator="Ctrl+I")
    formatMenu.add_command(label="Underline", command=objFormat.underline, accelerator="Ctrl+U")
    formatMenu.add_command(label="Overstrike", command=objFormat.overstrike, accelerator="Ctrl+T")
    formatMenu.add_command(label="Add Date", command=objFormat.addDate)
    menubar.add_cascade(label="Format", menu=formatMenu)

    root.bind_all("<Control-b>", objFormat.bold)
    root.bind_all("<Control-i>", objFormat.italic)
    root.bind_all("<Control-u>", objFormat.underline)
    root.bind_all("<Control-T>", objFormat.overstrike)

    root.grid_columnconfigure(0, weight=1)
    root.resizable(True, True)

    root.config(menu=menubar)
Enter fullscreen mode Exit fullscreen mode

Finally, we add some error-handling stuff:

if __name__ == "__main":
    print("Please run 'main.py'")
Enter fullscreen mode Exit fullscreen mode

help_menu.py

We'll start by importing stuff

# import statements
from tkinter import *
from tkinter.messagebox import *
Enter fullscreen mode Exit fullscreen mode

Then, we'll make a Help class and add an About function to it:

class Help():
    def about(root):
        showinfo(title="About", message="Hello, this is a text editor made by Insidious using Python")
Enter fullscreen mode Exit fullscreen mode

We then make a main function:

def main(root, text, menubar):

    help = Help()

    helpMenu = Menu(menubar)
    helpMenu.add_command(label="About", command=help.about)
    menubar.add_cascade(label="Help", menu=helpMenu)

    root.config(menu=menubar)
Enter fullscreen mode Exit fullscreen mode

Finally, we add some error-handling sort of thing:

if __name__ == "__main":
    print("Please run 'main.py'")
Enter fullscreen mode Exit fullscreen mode

And that should be it!

Project on GitHub


Thanks for reading this post till the end. Hopefully it helped you!!!

If there is anything missing, or I did something wrong, kindly point it out in the comments :D

Socials

Discord
GitHub
Instagram

Thanks!!!

Discussion (0)