566 lines
23 KiB
Python
566 lines
23 KiB
Python
# -*- coding: UTF-8 -*-
|
||
|
||
import os
|
||
import tkinter as tk
|
||
from tkinter import ttk, messagebox, simpledialog
|
||
import json
|
||
from typing import Dict, Optional
|
||
from PIL import Image
|
||
import subprocess
|
||
import shutil
|
||
from PIL import ImageTk
|
||
|
||
class ConfigEditorGUI:
|
||
def __init__(self, root):
|
||
self.root = root
|
||
self.root.title("目录图标工具 FilesTreeIco_Tool")
|
||
self.root.geometry("800x600")
|
||
|
||
# 设置基本样式
|
||
style = ttk.Style()
|
||
style.configure("Treeview", rowheight=25)
|
||
style.configure("TButton", padding=5)
|
||
|
||
# 当前编辑的配置文件名
|
||
self.current_file: Optional[str] = None
|
||
# 存储目录结构的字典
|
||
self.folder_structure: Dict = {}
|
||
|
||
# 添加图标缓存字典
|
||
self.icon_cache = {}
|
||
|
||
self._init_ui()
|
||
self._load_configs()
|
||
|
||
def _init_ui(self):
|
||
"""初始化用户界面"""
|
||
# 创建主框架
|
||
self.main_frame = ttk.Frame(self.root, padding="10")
|
||
self.main_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 顶部工具
|
||
self._create_toolbar()
|
||
|
||
# 创建左右分栏
|
||
self.paned = ttk.PanedWindow(self.main_frame, orient=tk.HORIZONTAL)
|
||
self.paned.pack(fill=tk.BOTH, expand=True, pady=(10, 0))
|
||
|
||
# 左侧配置文件列表
|
||
self._create_config_list()
|
||
|
||
# 右侧目录树编辑器
|
||
self._create_tree_editor()
|
||
|
||
def _create_toolbar(self):
|
||
"""创建顶部工具栏"""
|
||
toolbar = ttk.Frame(self.main_frame)
|
||
toolbar.pack(fill=tk.X, pady=(0, 10))
|
||
|
||
ttk.Button(toolbar, text="新建配置", command=self._new_config).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(toolbar, text="删除配置", command=self._delete_config).pack(side=tk.LEFT, padx=5)
|
||
ttk.Button(toolbar, text="创建目录", command=self._create_folders).pack(side=tk.LEFT, padx=5)
|
||
|
||
def _create_config_list(self):
|
||
"""创建左侧配置文件列表"""
|
||
list_frame = ttk.LabelFrame(self.paned, text="配置文件", padding="5")
|
||
self.paned.add(list_frame, weight=1)
|
||
|
||
# 配置文件列表
|
||
self.config_listbox = tk.Listbox(list_frame)
|
||
self.config_listbox.pack(fill=tk.BOTH, expand=True)
|
||
self.config_listbox.bind('<<ListboxSelect>>', self._on_config_select)
|
||
|
||
def _set_folder_icon(self):
|
||
"""设置文件夹图标"""
|
||
selected = self.tree.selection()
|
||
if not selected or self.tree.item(selected[0])['text'] == '根目录':
|
||
return
|
||
|
||
# 创建图标选择对话框
|
||
icon_dialog = tk.Toplevel(self.root)
|
||
icon_dialog.title("选择图标")
|
||
icon_dialog.geometry("600x400")
|
||
icon_dialog.transient(self.root) # 设置为模态对话框
|
||
icon_dialog.grab_set() # 禁止与其他窗口交互
|
||
|
||
# 设置最小窗口大小
|
||
icon_dialog.minsize(400, 300)
|
||
|
||
# 将窗口居中显示
|
||
icon_dialog.update_idletasks() # 更新窗口大小
|
||
width = icon_dialog.winfo_width()
|
||
height = icon_dialog.winfo_height()
|
||
x = (icon_dialog.winfo_screenwidth() // 2) - (width // 2)
|
||
y = (icon_dialog.winfo_screenheight() // 2) - (height // 2)
|
||
icon_dialog.geometry(f'+{x}+{y}')
|
||
|
||
# 创建主框架
|
||
main_frame = ttk.Frame(icon_dialog, padding="10")
|
||
main_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 创建标题标签
|
||
ttk.Label(
|
||
main_frame,
|
||
text="选择要应用的图标:",
|
||
font=('微软雅黑', 10)
|
||
).pack(anchor='w', pady=(0, 10))
|
||
|
||
# 创图标显示区域
|
||
icon_frame = ttk.Frame(main_frame)
|
||
icon_frame.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 创建画和滚动条
|
||
canvas = tk.Canvas(icon_frame, bg='white')
|
||
scrollbar = ttk.Scrollbar(icon_frame, orient=tk.VERTICAL, command=canvas.yview)
|
||
|
||
# 创建用于放置图标的框架
|
||
scrollable_frame = ttk.Frame(canvas)
|
||
scrollable_frame.bind(
|
||
"<Configure>",
|
||
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||
)
|
||
|
||
# 创建画布窗口
|
||
canvas_window = canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
|
||
|
||
# 配置画布随窗口调整大小
|
||
def on_canvas_configure(event):
|
||
canvas.itemconfig(canvas_window, width=event.width)
|
||
canvas.bind('<Configure>', on_canvas_configure)
|
||
|
||
# 配置画布滚动
|
||
canvas.configure(yscrollcommand=scrollbar.set)
|
||
|
||
# 打包滚动组件
|
||
canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
|
||
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
|
||
|
||
# 创建网格框架
|
||
grid_frame = ttk.Frame(scrollable_frame)
|
||
grid_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
|
||
|
||
def on_icon_click(path):
|
||
self._apply_icon(selected[0], path)
|
||
icon_dialog.destroy()
|
||
|
||
# 添加"无图标"选项
|
||
no_icon_frame = ttk.Frame(grid_frame)
|
||
no_icon_frame.grid(row=0, column=0, padx=5, pady=5, sticky='nsew')
|
||
|
||
ttk.Button(
|
||
no_icon_frame,
|
||
text="无图标",
|
||
width=10,
|
||
command=lambda: on_icon_click(None)
|
||
).pack(pady=2)
|
||
ttk.Label(no_icon_frame, text="默认").pack()
|
||
|
||
# 加载实际图标
|
||
icon_count = 1 # 从1开始计数,因为0被"无图标"占用
|
||
for file in os.listdir('图标'):
|
||
if file.lower().endswith(('.ico', '.png')):
|
||
try:
|
||
icon_path = os.path.join('图标', file)
|
||
if file.lower().endswith('.png'):
|
||
# 动态调整图标大小以适配预览
|
||
max_size = 24
|
||
preview_size = 32
|
||
original_icon = Image.open(icon_path)
|
||
# original_icon.thumbnail((max_size, max_size), Image.LANCZOS)
|
||
original_icon = original_icon.resize((preview_size, preview_size), Image.LANCZOS)
|
||
preview_icon = ImageTk.PhotoImage(original_icon)
|
||
|
||
# 创建图标按钮框架
|
||
btn_frame = ttk.Frame(grid_frame)
|
||
row = icon_count // 4 # 每行4个图标
|
||
col = icon_count % 4
|
||
btn_frame.grid(row=row, column=col, padx=10, pady=10, sticky='nsew')
|
||
|
||
# 创建图标按钮
|
||
btn = ttk.Button(
|
||
btn_frame,
|
||
image=preview_icon,
|
||
command=lambda p=icon_path: on_icon_click(p)
|
||
)
|
||
btn.image = preview_icon # 保持引用
|
||
btn.pack(pady=2)
|
||
|
||
# 显示文件名(限制长度)
|
||
name = file if len(file) <= 15 else file[:12] + '...'
|
||
ttk.Label(btn_frame, text=name).pack()
|
||
|
||
icon_count += 1
|
||
|
||
except Exception as e:
|
||
print(f"无法加载图标 {file}: {str(e)}")
|
||
|
||
# 配置网格列的权重
|
||
for i in range(4):
|
||
grid_frame.grid_columnconfigure(i, weight=1)
|
||
|
||
# 添加底部按钮框架
|
||
button_frame = ttk.Frame(main_frame)
|
||
button_frame.pack(fill=tk.X, pady=(10, 0))
|
||
|
||
ttk.Button(
|
||
button_frame,
|
||
text="取消",
|
||
command=icon_dialog.destroy
|
||
).pack(side=tk.RIGHT)
|
||
|
||
# 绑定ESC键关闭窗口
|
||
icon_dialog.bind('<Escape>', lambda e: icon_dialog.destroy())
|
||
|
||
def _apply_icon(self, item_id: str, icon_path: str):
|
||
"""应用选择的图标到文件夹"""
|
||
try:
|
||
if icon_path is None:
|
||
# 移除图标
|
||
self.tree.item(item_id, image='')
|
||
# 更新配置
|
||
path = self._get_item_path(item_id)
|
||
current = self.folder_structure.get('folders', {})
|
||
for p in path[1:]:
|
||
if p not in current:
|
||
current[p] = {}
|
||
current = current[p]
|
||
if not isinstance(current, dict):
|
||
current = {}
|
||
|
||
# 移除图标配置
|
||
if isinstance(current, dict) and '_icon' in current:
|
||
del current['_icon']
|
||
else:
|
||
# 原有的图标设置逻辑
|
||
rel_path = os.path.relpath(icon_path)
|
||
if rel_path not in self.icon_cache:
|
||
original_icon = Image.open(icon_path)
|
||
# 动态调整图标大小以适配树状图
|
||
max_size = 16
|
||
original_icon.thumbnail((max_size, max_size), Image.LANCZOS)
|
||
self.icon_cache[rel_path] = ImageTk.PhotoImage(original_icon)
|
||
self.tree.item(item_id, image=self.icon_cache[rel_path])
|
||
|
||
path = self._get_item_path(item_id)
|
||
current = self.folder_structure.get('folders', {})
|
||
for p in path[1:]:
|
||
if p not in current:
|
||
current[p] = {}
|
||
current = current[p]
|
||
if not isinstance(current, dict):
|
||
current = {}
|
||
|
||
current['_icon'] = rel_path
|
||
|
||
# 保存配置
|
||
self._auto_save()
|
||
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"设置图标失败: {str(e)}")
|
||
print(f"错误详情: {str(e)}")
|
||
|
||
def _get_item_path(self, item_id: str) -> list:
|
||
"""获取树节点的完整路径"""
|
||
path = []
|
||
while item_id:
|
||
path.insert(0, self.tree.item(item_id)['text'])
|
||
item_id = self.tree.parent(item_id)
|
||
return path
|
||
|
||
def _create_tree_editor(self):
|
||
"""创建右侧录树编辑器"""
|
||
editor_frame = ttk.LabelFrame(self.paned, text="目录结构", padding="5")
|
||
self.paned.add(editor_frame, weight=3)
|
||
|
||
self.tree = ttk.Treeview(editor_frame, selectmode='browse')
|
||
self.tree.pack(fill=tk.BOTH, expand=True)
|
||
|
||
# 右键菜单
|
||
self.context_menu = tk.Menu(self.root, tearoff=0)
|
||
self.context_menu.add_command(label="添加子目录", command=self._add_subfolder)
|
||
self.context_menu.add_command(label="设置图标", command=self._set_folder_icon)
|
||
self.context_menu.add_command(label="删除", command=self._delete_folder)
|
||
|
||
self.tree.bind("<Button-3>", self._show_context_menu)
|
||
|
||
def _load_configs(self):
|
||
"""加载所有配置文件"""
|
||
self.config_listbox.delete(0, tk.END)
|
||
for file in os.listdir('.'):
|
||
if file.endswith('.json'):
|
||
self.config_listbox.insert(tk.END, file)
|
||
|
||
def _on_config_select(self, event):
|
||
"""当选择配置文件时触发"""
|
||
selection = self.config_listbox.curselection()
|
||
if selection:
|
||
filename = self.config_listbox.get(selection[0])
|
||
self._load_config_file(filename)
|
||
|
||
def _load_config_file(self, filename: str):
|
||
"""加载指定配置文件"""
|
||
try:
|
||
with open(filename, 'r', encoding='utf-8') as f:
|
||
self.folder_structure = json.load(f)
|
||
self.current_file = filename
|
||
self._update_tree()
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"无法加载配置文件: {str(e)}")
|
||
|
||
def _update_tree(self):
|
||
"""更新目录树显示"""
|
||
self.tree.delete(*self.tree.get_children())
|
||
folders = self.folder_structure.get('folders', {})
|
||
# 添加根节点
|
||
root = self.tree.insert('', 'end', text='根目录')
|
||
self._build_tree(root, folders)
|
||
|
||
def _build_tree(self, parent: str, folders: Dict):
|
||
"""递归构建目录树"""
|
||
for folder, content in folders.items():
|
||
# 检查是否有图标设置
|
||
icon = None
|
||
if isinstance(content, dict) and '_icon' in content:
|
||
icon_path = content['_icon']
|
||
try:
|
||
if icon_path not in self.icon_cache:
|
||
original_icon = Image.open(icon_path)
|
||
# 动态调整图标大小以适配树状图
|
||
max_size = 16
|
||
original_icon.thumbnail((max_size, max_size), Image.LANCZOS)
|
||
self.icon_cache[icon_path] = ImageTk.PhotoImage(original_icon)
|
||
icon = self.icon_cache[icon_path]
|
||
except Exception as e:
|
||
print(f"无法加载图标 {icon_path}: {str(e)}")
|
||
|
||
# 根据是否有图标来决定传递的参数
|
||
if icon:
|
||
folder_id = self.tree.insert(parent, 'end', text=folder, image=icon)
|
||
else:
|
||
folder_id = self.tree.insert(parent, 'end', text=folder)
|
||
|
||
if isinstance(content, dict):
|
||
# 过滤掉元数据键
|
||
subfolders = {k: v for k, v in content.items() if not k.startswith('_')}
|
||
if subfolders:
|
||
self._build_tree(folder_id, subfolders)
|
||
|
||
def _show_context_menu(self, event):
|
||
"""显示右键菜单"""
|
||
item = self.tree.identify_row(event.y)
|
||
if item:
|
||
self.tree.selection_set(item)
|
||
self.context_menu.post(event.x_root, event.y_root)
|
||
|
||
def _add_subfolder(self):
|
||
"""添加子目录"""
|
||
selected = self.tree.selection()
|
||
if selected:
|
||
name = simpledialog.askstring("新建目录", "请输入目录名:")
|
||
if name:
|
||
# 添加新节点
|
||
new_item = self.tree.insert(selected[0], 'end', text=name)
|
||
# 展开父节点
|
||
self.tree.item(selected[0], open=True)
|
||
# 选中新节点
|
||
self.tree.selection_set(new_item)
|
||
# 确保新节点见
|
||
self.tree.see(new_item)
|
||
# 立即保存更改
|
||
self.folder_structure = {"folders": self._get_folder_structure('')}
|
||
self._auto_save()
|
||
|
||
def _delete_folder(self):
|
||
"""删除选中的目录"""
|
||
selected = self.tree.selection()
|
||
if selected and self.tree.item(selected[0])['text'] != '根目录':
|
||
self.tree.delete(selected[0])
|
||
# 立即保存更改
|
||
self.folder_structure = {"folders": self._get_folder_structure('')}
|
||
self._auto_save()
|
||
|
||
def _auto_save(self):
|
||
"""自动存当前配置"""
|
||
if not self.current_file:
|
||
return
|
||
|
||
try:
|
||
with open(self.current_file, 'w', encoding='utf-8') as f:
|
||
json.dump(self.folder_structure, f, indent=4, ensure_ascii=False)
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"自动保存失败: {str(e)}")
|
||
|
||
def _new_config(self):
|
||
"""创建新配置文件"""
|
||
filename = simpledialog.askstring("新建配置", "请输入配置文件名:")
|
||
if filename:
|
||
if not filename.endswith('.json'):
|
||
filename += '.json'
|
||
self.folder_structure = {
|
||
"folders": {
|
||
"新文件夹": {}
|
||
}
|
||
}
|
||
self.current_file = filename
|
||
self._auto_save()
|
||
self._load_configs()
|
||
self._update_tree()
|
||
|
||
def _get_folder_structure(self, node: str) -> Dict:
|
||
"""递归获取目录结构"""
|
||
structure = {}
|
||
children = self.tree.get_children(node)
|
||
for child in children:
|
||
item = self.tree.item(child)
|
||
child_text = item['text']
|
||
# 跳过根节
|
||
if child_text == '根目录':
|
||
structure = self._get_folder_structure(child)
|
||
else:
|
||
# 修改结构以匹配创建函数的预期格式
|
||
subfolders = self._get_folder_structure(child)
|
||
if subfolders:
|
||
structure[child_text] = subfolders
|
||
else:
|
||
structure[child_text] = {}
|
||
return structure
|
||
|
||
def _create_folders(self):
|
||
"""创建实际的目录结构"""
|
||
if not self.current_file:
|
||
messagebox.showwarning("警告", "请先选择或创建配置文件")
|
||
return
|
||
|
||
try:
|
||
folders = self._get_folder_structure(
|
||
next(iter(self.tree.get_children(''))) # 获取根节点
|
||
)
|
||
create_folders('.', folders, self.current_file)
|
||
messagebox.showinfo("成功", "目录结构创建完成")
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"创建目录失败: {str(e)}")
|
||
|
||
def _delete_config(self):
|
||
"""删除当前选中的配置文件"""
|
||
selection = self.config_listbox.curselection()
|
||
if not selection:
|
||
messagebox.showwarning("警告", "请先选择要删除的配置文件")
|
||
return
|
||
|
||
filename = self.config_listbox.get(selection[0])
|
||
if messagebox.askyesno("确认删除", f"确定要删除配置文件 {filename} 吗?"):
|
||
try:
|
||
os.remove(filename)
|
||
self._load_configs() # 重新加载配置文件列表
|
||
|
||
# 如果删除的是当前打开的配置文件,清空树形视图
|
||
if filename == self.current_file:
|
||
self.current_file = None
|
||
self.folder_structure = {}
|
||
self.tree.delete(*self.tree.get_children())
|
||
except Exception as e:
|
||
messagebox.showerror("错误", f"删除失败: {str(e)}")
|
||
|
||
def init_icon_dir() -> None:
|
||
"""初始化图标目录"""
|
||
icon_dir = "图标"
|
||
if not os.path.exists(icon_dir):
|
||
os.makedirs(icon_dir)
|
||
|
||
def create_folders(base_path: str, folders: Dict, current_file) -> None:
|
||
"""递归创建目录结构"""
|
||
for folder_name, content in folders.items():
|
||
# 跳过以下划线开头的元数据键
|
||
if folder_name.startswith('_'):
|
||
continue
|
||
|
||
# 构建完整路径
|
||
folder_path = os.path.join(base_path, folder_name)
|
||
|
||
# 创建目录
|
||
try:
|
||
if not os.path.exists(folder_path):
|
||
os.makedirs(folder_path)
|
||
|
||
# 调试输出
|
||
print(f"处理文件夹: {folder_name}")
|
||
print(f"内容类型: {type(content)}")
|
||
print(f"内容: {content}")
|
||
|
||
except Exception as e:
|
||
print(f"创建目录失败 {folder_path}: {str(e)}")
|
||
continue
|
||
|
||
# 重新从选中的配置文件中读取folder_name中的_icon
|
||
with open(current_file, 'r', encoding='utf-8') as f:
|
||
data = json.load(f)
|
||
icon_path = data['folders'][folder_name]['_icon']
|
||
icon_path = os.path.join(base_path, icon_path)
|
||
|
||
# 复制图标到folder_path并创建desktop.ini文件
|
||
try:
|
||
filename = os.path.basename(icon_path)
|
||
icon_name = os.path.splitext(filename)[0] + '.ico'
|
||
temp_icon_path = os.path.join('.\图标', icon_name)#临时图标
|
||
print(temp_icon_path)
|
||
img = Image.open(icon_path)
|
||
img.save(temp_icon_path, format='ICO')
|
||
|
||
shutil.copy(temp_icon_path, folder_path)
|
||
os.remove(temp_icon_path)
|
||
|
||
final_icon_path = os.path.join(folder_path, icon_name)
|
||
desktop_ini_path = os.path.join(folder_path, 'desktop.ini')
|
||
|
||
if os.path.exists(desktop_ini_path):
|
||
os.remove(desktop_ini_path)
|
||
|
||
with open(desktop_ini_path, 'w', encoding='ANSI') as f:
|
||
f.write(f"[.ShellClassInfo]\nIconResource=.\{icon_name},0")
|
||
|
||
# 设置desktop.ini为系统和隐藏文件
|
||
subprocess.run(['attrib', '+s', '+h', desktop_ini_path], shell=True)
|
||
# 设置icon文件为隐藏文件
|
||
subprocess.run(['attrib', '+s', '+h', final_icon_path], shell=True)
|
||
# 设置文件夹为读
|
||
subprocess.run(['attrib', '+r', folder_path], shell=True)
|
||
|
||
# 清除图标缓存
|
||
os.system("attrib -h -s -r \"%userprofile%\AppData\Local\IconCache.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\IconCache.db\"")
|
||
os.system("attrib /s /d -h -s -r \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\*\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_32.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_96.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_102.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_256.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_1024.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_idx.db\"")
|
||
os.system("del /f \"%userprofile%\AppData\Local\Microsoft\Windows\Explorer\thumbcache_sr.db\"")
|
||
os.system("echo y|reg delete \"HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify\" /v IconStreams")
|
||
os.system("echo y|reg delete \"HKEY_CLASSES_ROOT\Local Settings\Software\Microsoft\Windows\CurrentVersion\TrayNotify\" /v PastIconsStream")
|
||
except Exception as e:
|
||
print(f"设置图标失败: {str(e)}")
|
||
|
||
# 如果有子目录,递归创建
|
||
if isinstance(content, dict):
|
||
subfolders = {k: v for k, v in content.items() if not k.startswith('_')}
|
||
if subfolders:
|
||
create_folders(folder_path, subfolders)
|
||
|
||
def main():
|
||
root = tk.Tk()
|
||
# 设置窗口图标
|
||
try:
|
||
root.iconbitmap("图标/app.ico")
|
||
except:
|
||
pass
|
||
|
||
# 设置窗口主题色
|
||
root.configure(bg="#f0f0f0")
|
||
|
||
app = ConfigEditorGUI(root)
|
||
root.mainloop()
|
||
|
||
if __name__ == '__main__':
|
||
init_icon_dir()
|
||
main() |