7
\$\begingroup\$

Here's what I managed to do. It is rather limited and I had to get help from the internet in some places.

Can you review it, give me some tips on making the code better, cleaner, faster etc.?

editor.py

import tkinter
import tkinter.ttk
import tkinter.filedialog
import tkinter.messagebox
import os

try:
    import config
    import menu
    import syntaxhl
    import findtool

except ImportError as e:
    import sys
    sys.exit("Import error.\nRaw: {}".format(e))

class Editor(object):

    def editor(self):
        self.root = tkinter.Tk()

        self.config = config.config()
        self.syntaxcolor = config.syntaxHighligh()

        self.root.wm_title(self.config.editor_title)
        self.root.attributes("-alpha", self.config.transparency)

        # Scrollbar
        self.scrollbar = tkinter.Scrollbar(self.root)
        self.scrollbar.pack(side = tkinter.RIGHT, fill = tkinter.Y)

        # Editor.
        self.editor_space = tkinter.Text(self.root)
        self.editor_space.config(undo = self.config.undo, width = self.config.width,
                                 height = self.config.height, fg = self.config.foreground,
                                 bg = self.config.background, insertbackground = self.config.cursorColor,
                                 yscrollcommand = self.scrollbar.set,
                                 font = (self.config.font, self.config.font_size))

        self.editor_space.pack(fill = tkinter.X)
        self.scrollbar.config(command = self.editor_space.yview)

        # Find tool
        find = findtool.Find(self.editor_space)

        # Menu.
        # Note: The order of the instances is the order in witch the menus will appear.
        self.mn = tkinter.Menu(self.root)
        self.filemenu = menu.FileMenu(self.mn, self.openFile,
                                      self.saveFile, self.destroy,
                                      self.root)

        self.toolmenu = menu.ToolsMenu(self.mn, self.clear,
                                       self.syntaxHighlight,
                                       find.find)

        self.prefmenu = menu.Settings(self.mn, self.openConfigFile)
        self.helpmenu = menu.HelpMenu(self.mn, self.about)

        # Close event.
        if self.config.askokcancel:
            self.root.protocol("WM_DELETE_WINDOW", self.destroy)

        self.root.config(menu = self.mn)

        self.root.mainloop()

    def openConfigFile(self):
        try:
            with open("config.py") as file:
                for line in file.readlines():
                    self.editor_space.insert(tkinter.END, line)

        except IOError as e:
            tkinter.messagebox.showerror("Error", "Couldn't open file.")

        except Exception as e:
            tkinter.messagebox.showerror("Error", "Error.\n{}".format(repr(e)))

    def destroy(self):
        ako = tkinter.messagebox.askokcancel("Quit", "Are you sure you want to exit?")
        if ako:
            self.root.quit()

    def clear(self):
        self.editor_space.delete("1.0", tkinter.END)

    def saveFile(self):
        path = tkinter.filedialog.asksaveasfilename(defaultextension = self.config.defext)
        filename = os.path.basename(path)

        try:
            with open(filename, "w") as wf:
                wf.write(self.editor_space.get("0.0", tkinter.END))

            tkinter.messagebox.showinfo(None, "File saved.")

        except IOError as e:
            tkinter.messagebox.showerror("Error", "Couldn't open file.")

        except Exception as e:
            tkinter.messagebox.showerror("Error", "Error.\n{}".format(repr(e)))

    def openFile(self):
        self.clear()
        file = tkinter.filedialog.askopenfile()
        try:
            for line in file.readlines():
                self.editor_space.insert(tkinter.END, line)

            self.syntaxHighlight()

        except IOError as e:
            tkinter.messagebox.showerror("Error", "Couldn't open file.")

        except Exception as e:
            tkinter.messagebox.showerror("Error", "Error.\n{}".format(repr(e)))

    def syntaxHighlight(self):
        for keyw, color in getattr(self.syntaxcolor, "colors").items():
            syntaxhl.highlight(self.editor_space, keyw, color)

    def about(self):
        pass

config.py

class config(object):
    editor_title = "TEdit"
    undo = 1 # 1 - Enable

    # The default extension used when saving a file.
    defext = ".txt"

    # Size of the editor window.
    width = 150
    height = 50

    askokcancel = False
    matchColor = "blue"

    # Theme
    transparency = 1
    background = "black"
    foreground = "white"
    font = "Arial"
    font_size = 18
    cursorColor = "green"

class syntaxHighligh(object):
    colors = {"import" : "red",
              "def" : "red",
              "format" : "red",
              "if" : "red",
              "class" : "red",
              "else" : "red",
              "elif" : "red",
              "=" : "red",
              "try" : "red",
              "except" : "red",
              "finally" : "red",
              "yield" : "red",
              "return" : "red",
              "not" : "red",
              "pass" : "red",
              "for": "red",
              "pass": "red",
              "in" : "red",
              "print" : "red",
              "lambda" : "red",
              }

findtool.py

import tkinter
import tkinter.ttk
import config

class Find(object):

    def __init__(self, es):
        self.editor_space = es
        self.config = config.config()

    def find(self):
        # Find window and stuff.
        root = tkinter.Tk()
        tkinter.Label(root, text = "Seach for: ").grid(row = 1, column = 0)

        input_ = tkinter.ttk.Entry(root, width = 20)
        input_.grid(row = 1, column = 1)

        self.editor_space.tag_configure("search", background = self.config.matchColor)

        # Search button.
        tkinter.ttk.Button(root, text = "Search", command=lambda: self.search_editor_space(input_.get())).grid(row = 2, column = 1,
                                                                                                           columnspan = 2)

        # On close.
        root.protocol("WM_DELETE_WINDOW", lambda: self.destroy_find_window(root))


    def destroy_find_window(self, root):
        self.editor_space.tag_remove("found", "1.0", tkinter.END)
        root.destroy()

    def search_editor_space(self, searchfor):
        # Actual search.
        # Got from(with small modifications): http://www.java2s.com/Code/Python/GUI-Tk/SearchstringinText.html
        # Thanks.

        self.editor_space.tag_config("found", foreground = self.config.matchColor)
        countVar = tkinter.IntVar()

        item = "1.0"

        while True:
            item = self.editor_space.search(searchfor, item, tkinter.END, count = countVar)

            if not item:
                break

            lastindex = "{} + {}c".format(item, countVar.get())

            self.editor_space.tag_add("found", item, lastindex)

            item = lastindex

menu.py

import tkinter
import tkinter.ttk

class FileMenu(object):

    def __init__(self, menubar, openFunc, saveFunc, closeFunc, root):
        self.of = openFunc
        self.sf = saveFunc
        self.cf = closeFunc

        self.menu = tkinter.Menu(menubar)

        filemenu = tkinter.Menu(self.menu)
        filemenu.add_command(label = "Open", command = self.of)
        filemenu.add_command(label = "Save", command = self.sf)
        filemenu.add_separator()
        filemenu.add_command(label = "Exit", command = self.cf)

        menubar.add_cascade(label = "File", menu = filemenu)

class HelpMenu(object):

    def __init__(self, menubar, aboutFunc):
        self.helpmenu = tkinter.Menu(menubar)

        self.helpmenu.add_command(label = "About", command = aboutFunc)

        menubar.add_cascade(label = "Help", menu = self.helpmenu)

class ToolsMenu(object):

    def __init__(self, menubar, clearFunc, syntaxHighL, find):
        self.cf = clearFunc
        self.fd = find
        self.sh = syntaxHighL
        self.toolsmenu = tkinter.Menu(menubar)

        self.toolsmenu.add_command(label = "Clear", command = self.cf)
        self.toolsmenu.add_command(label = "Find", command = self.fd)
        self.toolsmenu.add_command(label = "Syntax highlighting", command = self.sh)

        menubar.add_cascade(label = "Tools", menu = self.toolsmenu)

class Settings(object):

    def __init__(self, menubar, configFile):
        self.prefmenu = tkinter.Menu(menubar)

        self.prefmenu.add_command(label = "Settings", command = configFile)

        menubar.add_cascade(label = "Preferences", menu = self.prefmenu)

syntaxhl.py

import tkinter

def highlight(editor_space, searchfor, color):
    countVar = tkinter.StringVar()

    pos = "1.0"

    while True:
        pos = editor_space.search(searchfor, pos, tkinter.END, count = countVar)

        if not pos:
            break

        lastindex = "{} + {}c".format(pos, countVar.get())

        editor_space.tag_add("keyword", pos, lastindex)

        pos = lastindex

    editor_space.tag_configure("keyword", foreground = color)

main.py

try:
    import editor

except ImportError as e:
    import sys
    sys.exit("editor.py module missing.")

if __name__ == '__main__':
    edit = editor.Editor()
    edit.editor()
\$\endgroup\$
  • 5
    \$\begingroup\$ Neither config nor syntaxHighligh should be classes. Both look to me like a dictionary, the second one doesn't even try to hide it. \$\endgroup\$ – L3viathan Jun 20 '15 at 21:57
6
\$\begingroup\$
  1. As mentioned by @L3viathan, the config class should be a dictionary, not a static class of attributes. When you ever need to store values in such a way, that a grouped together under a category, use a dictionary, like this:

    CONFIG = {
        "config-attribute": "config-attribute-value"
        ...
    }
    
  2. Secondly, your naming is off. Functions and variables should be in lower_snake_case, and classes should be in PascalCase. If the variable is constant, it should be in UPPERCASE_SNAKE_CASE.

  3. I'm not sure if you're using Python 3.x, or Python 2.x, but if you're using Python 3.x, you don't need to explicitly inherit all your classes from object. You can just write them like this: class MyClass:. If you're using Python 2.x though, you should continue explicitly inheriting from object.
  4. You're mixing use of single quotes, '', and double quotes, "". Preferably, you should choose one, and stick with that.
  5. You need two blank spaces in between your top level functions/classes. This is part of the official style guide.
  6. Finally, add some comments. Your code is fairly readable, but it could definitely use some comments. First off, add some docstrings, """ ... """, to describe what your functions and classes do. To describe blocks of code, just use online comments, # ....
\$\endgroup\$
  • \$\begingroup\$ You said: "you don't need to explicitly inherit all your classes from object", but what harm does it make? \$\endgroup\$ – highlycorrosive Jun 22 '15 at 18:09
  • \$\begingroup\$ @highlycorrosive There's no harm in doing it. It's just not necessary in Python 3.x. It also looks clearer as well. \$\endgroup\$ – Ethan Bierlein Jun 22 '15 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.