Adjustable Window sizes

Changed window sizes to be controlled by config.
This commit is contained in:
2026-09-04 11:39:48 -05:00
parent 9e74032649
commit 1aaaefd08f
+379 -376
View File
@@ -1,377 +1,380 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import tkinter as tk import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext from tkinter import filedialog, messagebox, scrolledtext
import tkinter.font as tkfont import tkinter.font as tkfont
import shutil import shutil
import ctypes import ctypes
import string import string
import configparser import configparser
# ============================================================ # ============================================================
# CONFIG: # CONFIG:
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(r"C:\ProgramData\Program-Search\config.ini") config.read(r"C:\ProgramData\Program-Search\config.ini")
SEARCH_ROOT = config["Paths"]["SEARCH_ROOT"] SEARCH_WINDOW_SIZE = config["General"]["SEARCH_WINDOW_SIZE"]
TRASH_DIR = config["Paths"]["TRASH_DIR"] RESULTS_WINDOW_SIZE = config["General"]["RESULTS_WINDOW_SIZE"]
TRASH_ENABLED = config.getboolean("Features", "TRASH_ENABLED") PREVIEW_WINDOW_SIZE = config["General"]["PREVIEW_WINDOW_SIZE"]
EDITOR_ENABLED = config.getboolean("Features", "EDITOR_ENABLED") SEARCH_ROOT = config["Paths"]["SEARCH_ROOT"]
MANUEAL_REPLACE_ENABLED = config.getboolean("Features", "MANUEAL_REPLACE_ENABLED") TRASH_DIR = config["Paths"]["TRASH_DIR"]
CLOSE_ALL_ENABLED = config.getboolean("Features", "CLOSE_ALL_ENABLED") TRASH_ENABLED = config.getboolean("Features", "TRASH_ENABLED")
AUTO_CLOSE_SECONDS = config.getint("Features", "AUTO_CLOSE_SECONDS") EDITOR_ENABLED = config.getboolean("Features", "EDITOR_ENABLED")
Extension = config["Copy"]["Extension"] MANUEAL_REPLACE_ENABLED = config.getboolean("Features", "MANUEAL_REPLACE_ENABLED")
# ============================================================ CLOSE_ALL_ENABLED = config.getboolean("Features", "CLOSE_ALL_ENABLED")
AUTO_CLOSE_SECONDS = config.getint("Features", "AUTO_CLOSE_SECONDS")
Extension = config["Copy"]["Extension"]
os.makedirs(TRASH_DIR, exist_ok=True) # ============================================================
def custom_messagebox(title, message, kind="info"): os.makedirs(TRASH_DIR, exist_ok=True)
win = tk.Toplevel()
win.title(title)
win.geometry("600x300") def custom_messagebox(title, message, kind="info"):
win.grab_set() win = tk.Toplevel()
win.focus_force() win.title(title)
frame = tk.Frame(win, padx=20, pady=20) win.geometry(SEARCH_WINDOW_SIZE)
frame.pack(expand=True, fill="both") win.grab_set()
center_window(win) win.focus_force()
frame = tk.Frame(win, padx=20, pady=20)
tk.Label(frame, text=message, font=("Consolas", 24), wraplength=550).pack(pady=20) frame.pack(expand=True, fill="both")
def close(): center_window(win)
win.destroy()
tk.Label(frame, text=message, font=("Consolas", 24), wraplength=550).pack(pady=20)
if kind == "yesno": def close():
result = {"value": False} win.destroy()
IsInfo = False
def yes(): if kind == "yesno":
result["value"] = True result = {"value": False}
win.destroy() IsInfo = False
def yes():
def no(): result["value"] = True
result["value"] = False win.destroy()
win.destroy()
def no():
tk.Button(frame, text="Yes", font=("Consolas", 24), padx=10, pady=10, command=yes).pack(side="left", padx=16) result["value"] = False
tk.Button(frame, text="No", font=("Consolas", 24), padx=10, pady=10, command=no).pack(side="right", padx=16) win.destroy()
win.wait_window() tk.Button(frame, text="Yes", font=("Consolas", 24), padx=10, pady=10, command=yes).pack(side="left", padx=16)
return result["value"] tk.Button(frame, text="No", font=("Consolas", 24), padx=10, pady=10, command=no).pack(side="right", padx=16)
else: win.wait_window()
tk.Button(frame, text="OK", font=("Consolas", 24), padx=10, pady=10, command=close).pack(pady=20) return result["value"]
win.bind("<Return>", lambda event: close())
win.wait_window() else:
tk.Button(frame, text="OK", font=("Consolas", 24), padx=10, pady=10, command=close).pack(pady=20)
win.bind("<Return>", lambda event: close())
def center_window(win): win.wait_window()
win.update_idletasks()
w = win.winfo_width()
h = win.winfo_height() def center_window(win):
sw = win.winfo_screenwidth() win.update_idletasks()
sh = win.winfo_screenheight() w = win.winfo_width()
x = (sw // 2) - (w // 2) h = win.winfo_height()
y = (sh // 2) - (h // 2) sw = win.winfo_screenwidth()
win.geometry(f"{w}x{h}+{x}+{y}") sh = win.winfo_screenheight()
x = (sw // 2) - (w // 2)
y = (sh // 2) - (h // 2)
def find_external_drives(): win.geometry(f"{w}x{h}+{x}+{y}")
drives = []
DRIVE_REMOVABLE = 2
def find_external_drives():
for letter in string.ascii_uppercase: drives = []
drive = f"{letter}:\\" DRIVE_REMOVABLE = 2
if os.path.exists(drive):
dtype = ctypes.windll.kernel32.GetDriveTypeW(drive) for letter in string.ascii_uppercase:
if dtype == DRIVE_REMOVABLE: drive = f"{letter}:\\"
drives.append(drive) if os.path.exists(drive):
dtype = ctypes.windll.kernel32.GetDriveTypeW(drive)
return drives if dtype == DRIVE_REMOVABLE:
drives.append(drive)
def close_all(root): return drives
for w in root.winfo_children():
try:
w.destroy() def close_all(root):
except: for w in root.winfo_children():
pass try:
root.destroy() w.destroy()
except:
pass
def popup_search_input(): root.destroy()
win = tk.Toplevel()
win.title("Search")
win.geometry("600x300") def popup_search_input():
center_window(win) win = tk.Toplevel()
win.title("Search")
tk.Label(win, text="Enter program number, part number, or name:").pack(pady=10) win.geometry(SEARCH_WINDOW_SIZE)
entry = tk.Entry(win) center_window(win)
entry.pack(pady=5)
entry.focus_set() tk.Label(win, text="Enter program number, part number, or name:").pack(pady=10)
entry.bind("<Return>", lambda event: submit()) entry = tk.Entry(win)
win.bind("<Return>", lambda event: submit()) entry.pack(pady=5)
entry.focus_set()
result = {"value": None} entry.bind("<Return>", lambda event: submit())
win.bind("<Return>", lambda event: submit())
def submit():
result["value"] = entry.get().strip().lower() result = {"value": None}
win.destroy()
def submit():
tk.Button(win, text="Search", command=submit, padx=20, pady=20).pack(pady=60) result["value"] = entry.get().strip().lower()
win.destroy()
win.wait_window(); return result["value"]
tk.Button(win, text="Search", command=submit, padx=20, pady=20).pack(pady=60)
def search_files(term): win.wait_window(); return result["value"]
matches = []
for root, dirs, files in os.walk(SEARCH_ROOT): def search_files(term):
for fname in files: matches = []
path = os.path.join(root, fname)
fname_lower = fname.lower() for root, dirs, files in os.walk(SEARCH_ROOT):
for fname in files:
filename_match = term in fname_lower path = os.path.join(root, fname)
contents_match = False fname_lower = fname.lower()
try: filename_match = term in fname_lower
with open(path, "r", errors="ignore") as f: contents_match = False
if term in f.read().lower():
contents_match = True try:
except: with open(path, "r", errors="ignore") as f:
pass if term in f.read().lower():
contents_match = True
if filename_match or contents_match: except:
matches.append(path) pass
return matches if filename_match or contents_match:
matches.append(path)
def popup_results_list(matches): return matches
win = tk.Toplevel()
win.title("Matches Found")
win.geometry("1200x1000") def popup_results_list(matches):
center_window(win) win = tk.Toplevel()
win.title("Matches Found")
tk.Label(win, text="Select files to preview:").pack(pady=10) win.geometry(RESULTS_WINDOW_SIZE)
center_window(win)
lb = tk.Listbox(win, selectmode=tk.MULTIPLE)
lb.pack(expand=True, fill="both") tk.Label(win, text="Select files to preview:").pack(pady=10)
for m in matches: lb = tk.Listbox(win, selectmode=tk.MULTIPLE)
lb.insert(tk.END, m) lb.pack(expand=True, fill="both")
result = {"selected": []} for m in matches:
lb.insert(tk.END, m)
def select_all():
lb.select_set(0, tk.END) result = {"selected": []}
def submit():
sel = lb.curselection() def select_all():
result["selected"] = [matches[i] for i in sel] lb.select_set(0, tk.END)
win.destroy() def submit():
sel = lb.curselection()
tk.Button(win, text="Open Selected", command=submit, padx=20, pady=10).pack(side ="right", padx=20, pady=10) result["selected"] = [matches[i] for i in sel]
tk.Button(win, text="Select All", command=select_all, padx=20, pady=10).pack(side ="left", padx=20, pady=10) win.destroy()
lb.bind("<Return>", lambda event: submit()) tk.Button(win, text="Open Selected", command=submit, padx=20, pady=10).pack(side ="right", padx=20, pady=10)
win.bind("<Return>", lambda event: submit()) tk.Button(win, text="Select All", command=select_all, padx=20, pady=10).pack(side ="left", padx=20, pady=10)
win.wait_window(); return result["selected"] lb.bind("<Return>", lambda event: submit())
win.bind("<Return>", lambda event: submit())
def popup_file_preview(path, on_close_callback): win.wait_window(); return result["selected"]
win = tk.Toplevel()
win.title(path)
win.geometry("900x1000") def popup_file_preview(path, on_close_callback):
win = tk.Toplevel()
box = scrolledtext.ScrolledText(win, wrap=tk.WORD) win.title(path)
box.pack(expand=True, fill="both") win.geometry(PREVIEW_WINDOW_SIZE)
try: box = scrolledtext.ScrolledText(win, wrap=tk.WORD)
with open(path, "r", errors="ignore") as f: box.pack(expand=True, fill="both")
original_text = f.read()
box.insert(tk.END, original_text) try:
box.edit_modified(False) with open(path, "r", errors="ignore") as f:
except Exception as e: original_text = f.read()
original_text = "" box.insert(tk.END, original_text)
box.insert(tk.END, f"Error reading file:\n{e}") box.edit_modified(False)
except Exception as e:
modified = {"changed": False} original_text = ""
box.insert(tk.END, f"Error reading file:\n{e}")
def mark_modified(event=None):
if box.edit_modified(): modified = {"changed": False}
modified["changed"] = True
box.edit_modified(False) def mark_modified(event=None):
if box.edit_modified():
if EDITOR_ENABLED: modified["changed"] = True
box.bind("<<Modified>>", mark_modified) box.edit_modified(False)
else:
box.configure(state="disabled") if EDITOR_ENABLED:
box.bind("<<Modified>>", mark_modified)
frame = tk.Frame(win) else:
frame.pack(fill="x", pady=10) box.configure(state="disabled")
frame = tk.Frame(win)
def save_file(): frame.pack(fill="x", pady=10)
try:
with open(path, "w", errors="ignore") as f:
f.write(box.get("1.0", tk.END)) def save_file():
modified["changed"] = False try:
custom_messagebox("Saved", f"File saved:\n{path}") with open(path, "w", errors="ignore") as f:
except Exception as e: f.write(box.get("1.0", tk.END))
custom_messagebox("Save Error", str(e)) modified["changed"] = False
custom_messagebox("Saved", f"File saved:\n{path}")
def copy_to_external(): except Exception as e:
if EDITOR_ENABLED and modified["changed"]: custom_messagebox("Save Error", str(e))
ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before copying?", kind = "yesno")
if ask: def copy_to_external():
save_file() if EDITOR_ENABLED and modified["changed"]:
ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before copying?", kind = "yesno")
drives = find_external_drives() if ask:
if not drives: save_file()
custom_messagebox("No External Drive Found", "No external drive detected."); return
drives = find_external_drives()
base_name = os.path.splitext(os.path.basename(path))[0] if not drives:
new_name = base_name + Extension custom_messagebox("No External Drive Found", "No external drive detected."); return
copied = [] base_name = os.path.splitext(os.path.basename(path))[0]
for d in drives: new_name = base_name + Extension
try:
dest = os.path.join(d, new_name) copied = []
shutil.copy2(path, dest) for d in drives:
copied.append(dest) try:
except Exception as e: dest = os.path.join(d, new_name)
custom_messagebox("Copy Error", f"Could not copy to {d}:\n{e}") shutil.copy2(path, dest)
copied.append(dest)
if copied: except Exception as e:
custom_messagebox("Copied Successfully", "Copied to:\n\n" + "\n".join(copied)) custom_messagebox("Copy Error", f"Could not copy to {d}:\n{e}")
win.destroy()
on_close_callback() if copied:
close_all(win.master) custom_messagebox("Copied Successfully", "Copied to:\n\n" + "\n".join(copied))
win.destroy()
def move_to_trash(): on_close_callback()
confirm = custom_messagebox("Move to Trash",f"Are you sure you want to move this file to trash?\n\n{path}", kind = "yesno") close_all(win.master)
if confirm:
try: def move_to_trash():
rel_path = os.path.relpath(path, SEARCH_ROOT) confirm = custom_messagebox("Move to Trash",f"Are you sure you want to move this file to trash?\n\n{path}", kind = "yesno")
dest_path = os.path.join(TRASH_DIR, rel_path) if confirm:
os.makedirs(os.path.dirname(dest_path), exist_ok=True) try:
shutil.move(path, dest_path) rel_path = os.path.relpath(path, SEARCH_ROOT)
custom_messagebox("Trashed", f"Moved to trash:\n{dest_path}") dest_path = os.path.join(TRASH_DIR, rel_path)
win.destroy() os.makedirs(os.path.dirname(dest_path), exist_ok=True)
on_close_callback() shutil.move(path, dest_path)
custom_messagebox("Trashed", f"Moved to trash:\n{dest_path}")
except Exception as e: win.destroy()
custom_messagebox("Trash Error", str(e)) on_close_callback()
def manual_replace(): except Exception as e:
drives = find_external_drives() custom_messagebox("Trash Error", str(e))
if not drives: custom_messagebox("No External Drive Found","No external drive detected."); return
base_name = os.path.splitext(os.path.basename(path))[0] def manual_replace():
new_name = base_name + Extension drives = find_external_drives()
replacement_path = None if not drives: custom_messagebox("No External Drive Found","No external drive detected."); return
for d in drives: base_name = os.path.splitext(os.path.basename(path))[0]
candidate = os.path.join(d, new_name) new_name = base_name + Extension
if os.path.exists(candidate): replacement_path = candidate; replacement_path = None
break for d in drives:
if not replacement_path: custom_messagebox("File Not Found",f"No replacement file named:\n\n{new_name}\n\nwas found on any external drive."); return candidate = os.path.join(d, new_name)
try: if os.path.exists(candidate): replacement_path = candidate;
with open(replacement_path,"r",errors="ignore") as f: new_contents = f.read() break
except Exception as e: custom_messagebox("Read Error",f"Could not read replacement file:\n{e}"); return if not replacement_path: custom_messagebox("File Not Found",f"No replacement file named:\n\n{new_name}\n\nwas found on any external drive."); return
try: try:
with open(path,"w",errors="ignore") as f: f.write(new_contents) with open(replacement_path,"r",errors="ignore") as f: new_contents = f.read()
except Exception as e: custom_messagebox("Write Error",f"Could not overwrite original file:\n{e}"); return except Exception as e: custom_messagebox("Read Error",f"Could not read replacement file:\n{e}"); return
box.delete("1.0",tk.END); box.insert(tk.END,new_contents); box.edit_modified(False); modified["changed"] = False try:
custom_messagebox("Replaced Successfully",f"File replaced using:\n{replacement_path}") with open(path,"w",errors="ignore") as f: f.write(new_contents)
except Exception as e: custom_messagebox("Write Error",f"Could not overwrite original file:\n{e}"); return
def close(): box.delete("1.0",tk.END); box.insert(tk.END,new_contents); box.edit_modified(False); modified["changed"] = False
if EDITOR_ENABLED and modified["changed"]: custom_messagebox("Replaced Successfully",f"File replaced using:\n{replacement_path}")
ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before closing?", kind = "yesno")
if ask: def close():
save_file() if EDITOR_ENABLED and modified["changed"]:
ask = custom_messagebox("Unsaved Changes", "You have unsaved changes.\nSave before closing?", kind = "yesno")
win.destroy() if ask:
on_close_callback() save_file()
def auto_close(): win.destroy()
if win.winfo_exists(): on_close_callback()
win.destroy()
on_close_callback() def auto_close():
if win.winfo_exists():
def close_all_button(): win.destroy()
ask = custom_messagebox("WARNING!", "Are you sure you want to cloose all windows?", kind = "yesno") on_close_callback()
if ask:
close_all(win.master) def close_all_button():
ask = custom_messagebox("WARNING!", "Are you sure you want to cloose all windows?", kind = "yesno")
win.protocol("WM_DELETE_WINDOW", close) if ask:
win.after(AUTO_CLOSE_SECONDS * 1000, auto_close) close_all(win.master)
if EDITOR_ENABLED: win.protocol("WM_DELETE_WINDOW", close)
tk.Button(win, text="Save", fg="green", command=save_file, padx=15, pady=8).pack(side ="left",padx=10, pady=10) win.after(AUTO_CLOSE_SECONDS * 1000, auto_close)
if TRASH_ENABLED == True: if EDITOR_ENABLED:
tk.Button(win, text="Move to Trash", fg="red", command=move_to_trash, padx=15, pady=8).pack(side ="left",padx=10, pady=10) tk.Button(win, text="Save", fg="green", command=save_file, padx=15, pady=8).pack(side ="left",padx=10, pady=10)
tk.Button(win, text="Copy to External Drive", command=copy_to_external, padx=15, pady=8).pack(side ="left",padx=10, pady=10) if TRASH_ENABLED == True:
tk.Button(win, text="Move to Trash", fg="red", command=move_to_trash, padx=15, pady=8).pack(side ="left",padx=10, pady=10)
tk.Button(win, text="Close", command=close, padx=15, pady=8).pack(side ="right",padx=10, pady=10)
tk.Button(win, text="Copy to External Drive", command=copy_to_external, padx=15, pady=8).pack(side ="left",padx=10, pady=10)
if CLOSE_ALL_ENABLED:
tk.Button(win, text="Close All", command=lambda: close_all_button(), padx=15, pady=8).pack(side ="right",padx=10, pady=10) tk.Button(win, text="Close", command=close, padx=15, pady=8).pack(side ="right",padx=10, pady=10)
if MANUEAL_REPLACE_ENABLED == True: if CLOSE_ALL_ENABLED:
tk.Button(win, text="Replace from External Drive", command=manual_replace, padx=15, pady=8).pack(side ="right",padx=10, pady=10) tk.Button(win, text="Close All", command=lambda: close_all_button(), padx=15, pady=8).pack(side ="right",padx=10, pady=10)
if MANUEAL_REPLACE_ENABLED == True:
tk.Button(win, text="Replace from External Drive", command=manual_replace, padx=15, pady=8).pack(side ="right",padx=10, pady=10)
def main():
root = tk.Tk()
root.withdraw()
default_font = tkfont.nametofont("TkDefaultFont") def main():
default_font.configure(family="Consolas", size=18) root = tk.Tk()
root.withdraw()
text_font = tkfont.nametofont("TkTextFont") default_font = tkfont.nametofont("TkDefaultFont")
text_font.configure(family="Consolas", size=18) default_font.configure(family="Consolas", size=18)
fixed_font = tkfont.nametofont("TkFixedFont") text_font = tkfont.nametofont("TkTextFont")
fixed_font.configure(family="Consolas", size=18) text_font.configure(family="Consolas", size=18)
root.option_add("*Dialog.msg.font", "Consolas 18") fixed_font = tkfont.nametofont("TkFixedFont")
root.option_add("*Dialog.msg.width", 60) fixed_font.configure(family="Consolas", size=18)
root.option_add("*Dialog.msg.height", 10)
root.option_add("*Dialog.msg.wrapLength", 600) root.option_add("*Dialog.msg.font", "Consolas 18")
root.option_add("*Dialog.msg.width", 60)
while True: root.option_add("*Dialog.msg.height", 10)
search_term = popup_search_input() root.option_add("*Dialog.msg.wrapLength", 600)
if not search_term:
root.destroy() while True:
return search_term = popup_search_input()
if not search_term:
matches = search_files(search_term) root.destroy()
return
if matches:
break matches = search_files(search_term)
custom_messagebox("No Results", "No matching files found.") if matches:
break
selected = popup_results_list(matches)
if not selected: custom_messagebox("No Results", "No matching files found.")
root.destroy()
return selected = popup_results_list(matches)
if not selected:
root.destroy()
open_windows = {"count": len(selected)} return
def window_closed():
open_windows["count"] -= 1 open_windows = {"count": len(selected)}
if open_windows["count"] == 0:
root.destroy() def window_closed():
open_windows["count"] -= 1
if open_windows["count"] == 0:
for path in selected: root.destroy()
popup_file_preview(path, window_closed)
root.mainloop() for path in selected:
popup_file_preview(path, window_closed)
if __name__ == "__main__": root.mainloop()
if __name__ == "__main__":
main() main()