2
\$\begingroup\$

I am making a simple file explorer with tkinter Python.
This is the file repository on github.
I know that this is pretty much long code and it took that much time to write but i have somehow added the comments.
And I want someone to test my code.
Also the additional mechanisms are not here and this is only for show purpose. Secondly can you suggest that how can I show all the drives instead of F: drive (as I was using it for testing purpose. Thank you in advance for any help.

import tkinter as tk
from os import listdir
from os.path import isdir
from tkinter import messagebox

from PIL import ImageTk, Image

# globals
h = []
value = 0
m = 0
b, a, f, q, type, r= "", "", "", "", "", ""
g = 1
l = 1
photo = ""
new_name = 0
row = ""
image = ""
text = ""
# modifying button for files and folders
class modified_button(tk.Button):
    def __init__(self, root, type=None, *args, **kw):
        global h, value, m, b
        tk.Button.__init__(self, root, *args, **kw)
        self.type = type
        self.root = root  # root of the given button widget
        if value == 0:
            for i in range(len(x.lst)):
                h.append(tk.Button(bg="#ffffff"))  # creating a list of buttons when the first button is created
        h.insert(value, self)  # updating the list with newly created buttons
        value += 1  # increasing the value so that list is not created again
        self.x = ""
        self.bind("<Enter>", lambda event, a="enter": self.changebg(a,
                                                                    event))  # changing the color of the button when it is under cursor
        self.bind("<Leave>", lambda event, a="leave": self.changebg(a,
                                                                    event))  # changing the color of the button when the cursor leaves it
        if type == "folder" or type == "file":  # if the button is of a file or a folder
            self.bind("<Button 1>",
                      lambda event, a="click": self.changebg(a, event))  # binding the click and rightclick
            self.bind("<Button-3>", lambda event, a="right-click": self.changebg(a, event))

    def changebg(self, a, event):
        global g, l, q,type
        if self["bg"] != "#80bfff" and a == "leave" and l != 0:  # if the bg of the color is not dark blue change the bg to white
            self.config(bg="#ffffff")
        if self["bg"] != "#80bfff" and a == "enter" and g != 0 and l != 0:  # same as above
            self.config(bg="#cce6ff")
        if self["bg"] != "#80bfff" and a == "click" and l != 0:  # if the user has clicked then changing the bg
            for ech in h:
                if type == "renaming":
                    self.rename()
                if ech["bg"] == "#80bfff":  # checking if any other button has dark blue bg
                    ech.config(bg="#ffffff")  # if it has then change it to light blue
            self.config(bg="#80bfff")  # lastly changing the bg to dark blue
        if self.type != "other":
            if self["bg"] == "#80bfff" and a == "right-click" and l != 0:
                # checking if the button is clicked before or not
                # creating menu for rightclick
                self.m = tk.Menu(self.root, tearoff=0)
                self.m.add_command(label="Cut  ", command=lambda a=self, b="cut": self.copy_paste(b, a))
                self.m.add_command(label="Copy ", command=lambda a=self, b="copy": self.copy_paste(b, a))
                if self.type == "folder":  # if the button is of a folder then only add the option to paste in it
                    self.m.add_command(label="Paste ", command=lambda a=self, b="paste": self.copy_paste(b, a))
                else:
                    pass
                self.m.add_separator()
                self.m.add_command(label="Delete")
                self.m.add_command(label="Rename", command=lambda a=self, b="rename": self.copy_paste(b, a))
                try:
                    self.m.tk_popup(event.x_root, event.y_root)
                finally:
                    self.m.grab_release()

    def copy_paste(self, command, file):
        global b, a, g, l, q,type,r,photo,row,image,text,new_name
        if self.type != "other":
            if command == "cut":  # if the user has selected to cut the file
                self.u = "{0}/{1}".format(x.z, file["text"])
                a = "cut"
                b = self.u

            if command == "copy":  # if the user has selected to copy the file
                self.u = "{0}/{1}".format(x.z, file["text"])
                a = "copy"
                b = self.u
            if command == "paste":  # if the user has selected to paste the file
                try:
                    file[0].isalpha()
                    if file == x.z:
                        self.x = file
                    else:
                        self.x = "{0}/{1}".format(x.z, file)  # creating the path to paste
                except:
                    self.x = "{0}/{1}".format(x.z, file["text"])
                if a == "copy":
                    # sh.copy(b,self.x)
                    pass
                if a == "cut":
                    # sh.move(b,self.x)
                    pass
            if command == "rename":  # renaming file
                row = self.grid_info()["row"]
                image = self["image"]
                text = self["text"]
                type = "renaming"
                self.grid_forget()
                r = tk.Frame(master=self.root, width=1750, height=10, bg="#ffffff")
                photo = tk.Label(master=r, image=x.p1)
                photo.grid(row=0, column=0)
                new_name = tk.Entry(master=r, width=30)
                new_name.grid(row=0, column=1)
                r.grid(column=0, row=row, sticky="w")
                new_name.bind("<Return>", lambda event: self.rename())
            if command == "delete":
                # remove(b)
                pass

    def rename(self):
        # exact renaming method is not here
        global r,new_name,text,row,type
        print(r,2,new_name,3,text,4,row)
        r.grid_forget()
        button = modified_button(self.root, type=type, image=image, bg="#ffffff", text=new_name.get(), width=750,
                                 height=13,
                                 compound="left",
                                 anchor="w",
                                 borderwidth=0)
        button.bind("<Double 1>", lambda event, a=text: x.create_dirlst(a))
        if button["text"] == "":
            button["text"] = text
        button.grid(column=0, row=row)
        type = ""


class explorer(tk.Frame):
    def __init__(self, root, *args, **kwargs):
        global f
        tk.Frame.__init__(self, *args, **kwargs)
        # creating the basics and handlers
        self.window = root
        self.lst = []
        self.starting_path = "F:"
        self.z = " "
        self.last_search = ""
        self.val = [0, 0, 0, 1, 0, 1, 0, 0, 1, 0]
        self.p1 = ImageTk.PhotoImage(file="Folder.png")
        self.p2 = ImageTk.PhotoImage(file="file.png")
        self.p3 = ImageTk.PhotoImage(Image.open("Untitled-1.png").resize((20, 20), Image.ANTIALIAS))
        self.p4 = ImageTk.PhotoImage(Image.open("Untitled-2.png").resize((20, 20), Image.ANTIALIAS))
        self.no_of_files = 0
        self.no_of_folders = 0
        self.last_opened_folder = ""

    def create_basics(self, a):
        if self.val[0] == 0:  # if this is first time then pass
            pass
        else:  # if this not the first time then delete all the old widgets
            self.left.pack_forget()
            self.up.pack_forget()
            self.parent_frame.pack_forget()
            self.down.pack_forget()
        # after deleting the old widgets creating new one
        self.left = tk.Frame(self.window, bg="#ffffff", width=10)
        self.left.pack(side="left", fill="y")
        self.up = tk.Frame(master=self.window, height=10, width=10)
        self.navigation_buttons = tk.Frame(self.up)
        self.back_button = modified_button(self.navigation_buttons, type="other", image=self.p3, borderwidth=0,
                                           bg="#ffffff")
        self.forward_button = modified_button(self.navigation_buttons, type="other", image=self.p4, borderwidth=0,
                                              bg="#ffffff")
        self.back_button.grid(row=0, column=0)
        self.back_button.bind("<Button-1>", lambda event, command="back": self.navigation(event, command))
        self.forward_button.grid(row=0, column=1)
        self.forward_button.bind("<Button-1>", lambda event, command="next": self.navigation(event, command))
        self.navigation_buttons.pack(side="left", fill="y")
        self.search = tk.Entry(master=self.up)
        if self.last_search == "":
            self.search.insert(tk.END, "Search")
        else:
            self.search.insert(tk.END, self.last_search)
        self.search.bind("<Button-1>", lambda event, a="click": explorer.modify_search(self, a, event))
        self.search.bind("<Leave>", lambda event, a="leave": explorer.modify_search(self, a, event))
        self.search.bind("<Return>", lambda event, a="enter": explorer.modify_search(self, a, event))
        self.search.bind("<BackSpace>", lambda event, a="Backspace": explorer.modify_search(self, a, event))
        self.search.pack(side="right", fill="y")
        self.path = tk.Frame(self.up, bg="#ffffff")
        self.path.pack(fill="both", expand=1)
        self.up.pack(fill="x")
        self.parent_frame = tk.Frame(self.window)
        self.canvas = tk.Canvas(self.parent_frame, bg="#ffffff")
        self.canvas.pack(side="left", fill="both", expand=1)
        self.scrollbar = tk.Scrollbar(self.parent_frame, orient="vertical", command=self.canvas.yview)
        self.scrollbar.pack(side="right", fill="y")
        self.canvas.configure(yscrollcommand=self.scrollbar.set)
        self.canvas.bind("<Configure>", lambda event: self.canvas.configure(scrollregion=self.canvas.bbox("all")))
        self.frame = tk.Frame(self.canvas)
        self.canvas.create_window((0, 0), window=self.frame)
        self.parent_frame.pack(fill="both", expand=1)
        self.down = tk.Frame(self.window, width=100, borderwidth=1, bg="#444444")
        self.down.pack(anchor="w")
        self.canvas.bind("<Button-3>", lambda event: self.mod_canvas(event))
        if a == "R":
            self.val[5] = 0
            explorer.create_folder(self)

    def modify_search(self, a, event):  # modifying the search widget
        if self.search.get() == "" and a == "leave" and self.val[3] != 0 and self.val[
            6] == 0:  # if the method is called for first time or after changing the directory
            self.search.insert(tk.END, "Search")

        if a == "click" and self.search.get() == "Search" and self.val[
            3] != 0:  # if the user has clicked the widget to type
            self.search.delete(0, tk.END)
            self.val[6] = 1
        if a == "enter" and self.search.get() != "" and self.search.get() != "Search":  # if the user has pressed enter to search
            m = self.lst  # first saving the list
            self.lst = []  # emptying the list
            for x in m:
                if self.search.get().lower() == x[0:len(
                        self.search.get())].lower():  # sortin all the matches of the search

                    self.lst.append(x)  # updating self.lst
                    self.val[4] += 1  # updating self.val[4] to identify is any file matches or not
            print(self.val[4])
            if self.val[4] == 0:  # if no file matches raise error
                messagebox.showwarning('Error', "Can't find the specified file.")
            else:  # if the files are found creating the new sest of buttons with modified self.lst
                self.last_search = self.search.get()
                self.no_of_files = 0
                self.no_of_folders = 0
                self.create_basics("")
                self.create_folder()
                self.val[4] = 0

        if a == "Backspace":
            if self.search.get() == "Search":
                self.search.delete(0, tk.END)
                self.val[3] = 0
            else:
                pass

    def navigation(self, event, command):
        global f
        if command == "back":  # if the user has pressed back
            try:
                self.last_opened_folder = f  # updating the last_opened_folder for later use
                f = self.z[:len(f) - f[::-1].index("/") - 1]  # obtaining the upper directory
            except:
                pass
            # doing basic things as before to create new
            self.lst = list(f for f in listdir(f) if f[0].isalpha())
            self.starting_path = f
            self.no_of_files = 0
            self.no_of_folders = 0
            self.create_basics("")
            self.create_folder()
        if command == "next":
            try:  # if the user has clicked next button
                self.lst = list(f for f in listdir(self.last_opened_folder) if
                                f[0].isalpha())  # using the last_opened_folder from before
            except:
                pass
            f = self.last_opened_folder
            self.starting_path = self.last_opened_folder
            self.no_of_files = 0
            self.no_of_folders = 0
            self.create_basics("")
            self.create_folder()

    def show_no_of_files(self):
        # creating widgets to show no. of files and folders
        self.files = tk.Label(master=self.down,
                              text=f"No. of files: {self.no_of_files}, No. of folders: {self.no_of_folders}",
                              borderwidth=1)
        self.files.pack(fill="x", expand=1)

    def create_dirlst(self, a):
        global f
        self.lst = []
        # here self.z is the clicked icon or the file/folder user wants to open
        if self.val[0] == 0:  # if this method is called first time then initialise self.z
            self.z = self.starting_path
            self.val[2] += 1
        if self.val[0] != 0 and self.val[
            2] == 1:  # if this method is not called for the first time then modifying self.z
            self.z = "{0}/{1}".format(self.starting_path, a)
        if isdir(self.z) == False and self.val[0] != 0:  # if self.z is not a folder then raise error
            #exact opening file method is not here
            messagebox.showwarning('Open Error', "Can't open this file.")
            self.val[1] += 1
        if self.val[0] != 0 and isdir(self.z):  # if the clicked icon is folder
            self.starting_path = "{0}/{1}".format(self.starting_path, a)
            self.lst = list(f for f in listdir(self.starting_path) if f[0].isalpha())
            self.no_of_files = 0
            self.no_of_folders = 0
            self.create_basics("")
            self.create_folder()
            self.val[5] = 1

            if self.val[1] != 0:  # setting val[1] to zero so that the show_no_of_files method can be called later
                self.val[1] = 0
        f = self.z
        if self.val[0] == 0:  # creating the list if the method is called for first time
            self.lst = list(f for f in listdir(self.starting_path) if f[0].isalpha() and f[0] != "$")

    def create_folder(self):
        for ech in self.lst:  # iterating through the list of dir and files
            if isdir("{0}/{1}".format(self.starting_path, ech)):  # if ech is a folder
                button = modified_button(self.frame, type="folder", image=self.p1, bg="#ffffff", text=ech, width=750,
                                         height=13,
                                         compound="left",
                                         anchor="w",
                                         borderwidth=0)
                button.bind("<Double 1>", lambda event, a=ech: self.create_dirlst(a))
                button.grid(column=0, row=self.lst.index(ech))
                if self.val[5] != 0:
                    self.no_of_folders += 1  # incrementing self.no_of_folders

            else:
                button = modified_button(self.frame, type="file", image=self.p2, bg="#ffffff", text=ech, width=750,
                                         height=13,
                                         compound="left",
                                         anchor="w",
                                         borderwidth=0)
                button.bind("<Double 1>", lambda event, a=ech: self.create_dirlst(a))
                button.grid(column=0, row=self.lst.index(ech))
                if self.val[5] != 0:
                    self.no_of_files += 1
        self.val[0] += 1
        if self.val[8] != 0:
            q = self.starting_path.split("/")
            for g in q:
                self.path_buttons = modified_button(self.path, type="other", text=g, bg="#ffffff", borderwidth=0)
                self.path_buttons.grid(row=0, column=q.index(g), padx=4)
                self.path_buttons.bind("<Button-1>", lambda event, r=g: self.updating_path(r))
        if self.val[1] == 0:
            self.show_no_of_files()

    def updating_path(self, k):
        if self.starting_path == k:
            pass
        else:
            self.starting_path = self.starting_path[:self.starting_path.index(k) + len(k)]
        self.lst = list(f for f in listdir(self.starting_path) if f[0].isalpha())
        self.create_basics("")
        self.create_folder()

    def mod_canvas(self,
                   event):  # modifying the canvas at the right side to paste in the current directory and also for refreshing
        self.menu = tk.Menu(self.canvas, tearoff=0)
        self.menu.add_command(label="Refresh            ", command=lambda a="R": explorer.create_basics(self, a))
        self.menu.add_command(label="Paste                ",
                              command=lambda a="paste", b=self.z: modified_button.copy_paste(self, a, b))
        try:
            self.menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.menu.grab_release()


if __name__ == "__main__":  # creating basic window
    window = tk.Tk()
    window.geometry("900x600")
    window.title("Door Explorer 1.0")
    window.iconbitmap("icon1.ico")
    x = explorer(window)
    x.create_basics("")
    x.create_dirlst(x.starting_path)
    x.create_folder()
    window.mainloop()
\$\endgroup\$
9
  • 5
    \$\begingroup\$ Welcome to Code Review. Unfortunately your question is off-topic as of now, as the code to be reviewed must be present in the question. Please add the code you want reviewed in your question. Thanks! \$\endgroup\$
    – Zeta
    Apr 22, 2021 at 15:06
  • \$\begingroup\$ I want the full code to be reviewed as all the classes and methods in it are interlinked. And when I add all the code to the question it shows your question contains mostly code. \$\endgroup\$ Apr 22, 2021 at 15:33
  • \$\begingroup\$ And if it is not possible then you can just test the code by compiling the file and checking if it performs all the necessary functions of a simple file explorer. \$\endgroup\$ Apr 22, 2021 at 15:35
  • 1
    \$\begingroup\$ I want the full code to be reviewed - Great; then copy-and-paste it. A link is not enough; links die. \$\endgroup\$
    – Reinderien
    Apr 22, 2021 at 15:45
  • 1
    \$\begingroup\$ Welcome to the Code Review Community. We can use repositories as references, but we can't review code in repositories, only the code embedded in the question post. Please add the code from the repository. \$\endgroup\$
    – pacmaninbw
    Apr 22, 2021 at 15:45

1 Answer 1

1
\$\begingroup\$

Note: I don't have experience with tkinter, so this will be a review for general code style. I welcome anyone who has experience with tkinter to add their review as well!


Class Naming

Class names should be in PascalCase, not snake_case.

modified_button -> ModifiedButton
explorer -> Explorer

Constants

Littered through your code are strings of hex values and specific strings representing events. You should define these at the top of your file, and just reference them throughout your code. One mistype, e.g "#80bfff" to "#08bfff", will cause you to search through your code to where you might have mistyped that.

DARK_BLUE = "#80bfff"
WHITE = "#ffffff"
...

Comments

To me, your comments make this code 10x harder to read.

if self.type == "folder":  # if the button is of a folder

Do you really need this comment? Comments should tell the user how a specific part of the code works, your intentions with that part, and to explain an algorithmic piece of code that could be confusing.

Reserved Names

type is a reserved name (function) in python, so you should rename it to something like file_type, or along those lines.

While we're talking about variables names, what are these?

# globals
h = []
value = 0
m = 0
b, a, f, q, type, r= "", "", "", "", "", ""
g = 1
l = 1

While you say they're globals, I don't have the faintest clue about what they represent. And looking through your code, I'm still confused. Variables should be representative of the data they store.

Cleaner Logic

Instead of

if self.val[0] == 0:  # if this is first time then pass
    pass
else:  # if this not the first time then delete all the old widgets
    self.left.pack_forget()
    self.up.pack_forget()
    self.parent_frame.pack_forget()
    self.down.pack_forget()

How about checking if the value isn't 0?

if self.val[0] != 0:
    self.left.pack_forget()
    self.up.pack_forget()
    self.parent_frame.pack_forget()
    self.down.pack_forget()

The same concept can be applied to multiple other places in your code.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks for your answer.I will try to avoid these cases in my next project which is possibly not of tkinter. \$\endgroup\$ Apr 23, 2021 at 12:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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