-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfolder_explorer.py
More file actions
207 lines (183 loc) · 7.38 KB
/
folder_explorer.py
File metadata and controls
207 lines (183 loc) · 7.38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import os
import tkinter as tk
from tkinter import ttk, filedialog, messagebox
import json
import ttkbootstrap as ttk # For styling
from ttkbootstrap.constants import *
from pathlib import Path
# Configuration file to save last folder
CONFIG_FILE = "folder_explorer_config.json"
def save_last_folder(folder):
"""Save the last selected folder to a config file."""
try:
with open(CONFIG_FILE, 'w') as f:
json.dump({'last_folder': folder}, f)
except Exception as e:
print(f"Failed to save last folder: {e}")
def load_last_folder():
"""Load the last selected folder from the config file."""
try:
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r') as f:
config = json.load(f)
last_folder = config.get('last_folder', '')
if os.path.isdir(last_folder):
return last_folder
except Exception as e:
print(f"Failed to load last folder: {e}")
return ""
def list_directory(path, parent="", lazy_load=True):
"""List directory contents with sorting and lazy loading."""
tree.delete(*tree.get_children(parent)) # Clear previous entries under parent
try:
items = os.listdir(path)
# Sort directories first, then files, alphabetically
dirs = sorted([item for item in items if os.path.isdir(os.path.join(path, item)) and item != ".git"])
files = sorted([item for item in items if os.path.isfile(os.path.join(path, item)) and item != ".git"])
items = dirs + files
except PermissionError:
tree.insert(parent, "end", text="❌ Permission Denied: " + path, values=(path,))
return
except FileNotFoundError:
tree.insert(parent, "end", text="❌ Path Not Found: " + path, values=(path,))
return
except Exception as e:
tree.insert(parent, "end", text=f"❌ Error: {e}", values=(path,))
return
for item in items:
full_path = os.path.join(path, item)
icon = "📁" if os.path.isdir(full_path) else "📄"
item_id = tree.insert(parent, "end", text=f"{icon} {item}", values=(full_path, "loaded" if not os.path.isdir(full_path) else "unloaded"))
if os.path.isdir(full_path) and lazy_load:
# Add a dummy child to show expand arrow
tree.insert(item_id, "end", text="Loading...", values=("",))
def expand_directory(event):
"""Load subdirectories on demand when a directory is expanded."""
item = tree.selection()
if not item:
return
item_id = item[0]
item_values = tree.item(item_id, "values")
if len(item_values) < 2 or item_values[1] != "unloaded":
return
path = item_values[0]
if os.path.isdir(path):
# Remove dummy child
tree.delete(*tree.get_children(item_id))
list_directory(path, item_id, lazy_load=True)
tree.item(item_id, values=(path, "loaded"))
def select_folder():
"""Open folder selection dialog and list contents."""
folder = filedialog.askdirectory(initialdir=load_last_folder())
if folder:
try:
entry.delete(0, tk.END)
entry.insert(0, folder)
list_directory(folder, lazy_load=True)
save_last_folder(folder)
except Exception as e:
messagebox.showerror("Error", f"Failed to load folder {folder}: {e}")
def reset_view():
"""Clear the entry field and tree view."""
entry.delete(0, tk.END)
tree.delete(*tree.get_children())
save_last_folder("") # Clear saved folder
def open_item(event=None):
"""Open selected file or folder."""
selected = tree.selection()
if not selected:
return
item = tree.item(selected[0])
path = item["values"][0]
if not os.path.exists(path):
messagebox.showerror("Error", f"Path does not exist: {path}")
return
try:
if os.name == "nt":
os.startfile(path)
else:
os.system(f"open '{path}'" if os.name == "posix" else f"xdg-open '{path}'")
except FileNotFoundError:
messagebox.showerror("Error", f"Cannot open {path}: File not found")
except PermissionError:
messagebox.showerror("Error", f"Permission denied: {path}")
except Exception as e:
messagebox.showerror("Error", f"Failed to open {path}: {e}")
def copy_path():
"""Copy the path of the selected item to clipboard."""
selected = tree.selection()
if not selected:
return
path = tree.item(selected[0], "values")[0]
root.clipboard_clear()
root.clipboard_append(path)
messagebox.showinfo("Success", "Path copied to clipboard")
def delete_item():
"""Delete the selected file or folder."""
selected = tree.selection()
if not selected:
return
path = tree.item(selected[0], "values")[0]
if not os.path.exists(path):
messagebox.showerror("Error", f"Path does not exist: {path}")
return
if messagebox.askyesno("Confirm Delete", f"Are you sure you want to delete {path}?"):
try:
if os.path.isfile(path):
os.remove(path)
elif os.path.isdir(path):
import shutil
shutil.rmtree(path)
tree.delete(selected[0])
messagebox.showinfo("Success", f"Deleted {path}")
except PermissionError:
messagebox.showerror("Error", f"Permission denied: {path}")
except Exception as e:
messagebox.showerror("Error", f"Failed to delete {path}: {e}")
def show_context_menu(event):
"""Show right-click context menu."""
selected = tree.selection()
if selected:
context_menu.post(event.x_root, event.y_root)
# GUI Setup
root = ttk.Window(themename="flatly") # Use ttkbootstrap for styling
root.title("Folder Explorer")
root.geometry("800x800")
# Load last folder
last_folder = load_last_folder()
# Entry for folder path
entry = ttk.Entry(root, width=50)
entry.insert(0, last_folder)
entry.pack(pady=5)
# Buttons in a frame
button_frame = ttk.Frame(root)
button_frame.pack(pady=5)
btn_select = ttk.Button(button_frame, text="Select Folder", command=select_folder, bootstyle=PRIMARY)
btn_select.pack(side="left", padx=5)
btn_reset = ttk.Button(button_frame, text="Reset", command=reset_view, bootstyle=SECONDARY)
btn_reset.pack(side="left", padx=5)
# Treeview with scrollbar
style = ttk.Style()
style.configure("Treeview", rowheight=25, font=("Helvetica", 10))
style.configure("Treeview.Heading", font=("Helvetica", 10, "bold"))
tree = ttk.Treeview(root, columns=("Path", "Status"), show="tree", selectmode="browse")
scrollbar = ttk.Scrollbar(root, orient="vertical", command=tree.yview, bootstyle=SECONDARY)
tree.configure(yscrollcommand=scrollbar.set)
scrollbar.pack(side="right", fill="y")
tree.pack(side="left", expand=True, fill="both", padx=5, pady=5)
# Bind events
tree.bind("<Double-1>", open_item) # Double-click to open
tree.bind("<Button-3>", show_context_menu) # Right-click for context menu
tree.bind("<<TreeviewOpen>>", expand_directory) # Lazy load on expand
# Context menu
context_menu = tk.Menu(root, tearoff=0)
context_menu.add_command(label="Open", command=open_item)
context_menu.add_command(label="Copy Path", command=copy_path)
context_menu.add_command(label="Delete", command=delete_item)
# Load initial folder if exists
if last_folder:
try:
list_directory(last_folder, lazy_load=True)
except Exception as e:
print(f"Failed to load last folder {last_folder}: {e}")
root.mainloop()