无忧启动论坛

 找回密码
 注册
搜索
系统gho:最纯净好用系统下载站投放广告、加入VIP会员,请联系 微信:wuyouceo
查看: 2591|回复: 48
打印 上一主题 下一主题

[原创] 2025/6/4更新 {源代码公开} Python我的工具箱3.0

    [复制链接]
跳转到指定楼层
1#
发表于 2025-6-1 11:37:11 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
来自 47#
 楼主| 发表于 6 天前 | 只看该作者

0608修改 优化代码

本帖最后由 piaomusic 于 2025-6-8 22:51 编辑

import tkinter as tk
from tkinter import ttk, messagebox, filedialog, simpledialog
import configparser
import os
import sys
import ctypes
import socket
import logging
import webbrowser
import subprocess
from threading import Thread, Lock
from ttkbootstrap import Style
from PIL import Image, ImageTk

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    filename='tool.log',
    encoding='utf-8'
)
logger = logging.getLogger(__name__)

def is_admin():
    try:
        return ctypes.windll.shell32.IsUserAnAdmin()
    except Exception:
        return False

def get_local_ip():
    try:
        hostname = socket.gethostname()
        ip_list = socket.gethostbyname_ex(hostname)[2]
        valid_ips = [ip for ip in ip_list if not ip.startswith('127.') and not ip.startswith('169.254.')]
        return valid_ips[0] if valid_ips else (ip_list[0] if ip_list else "127.0.0.1")
    except Exception as e:
        logger.error(f"获取内网IP失败: {str(e)}")
        return "127.0.0.1"

def center_window(window, width, height):
    window.update_idletasks()
    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    x = (screen_width - width) // 2
    y = (screen_height - height) // 2
    window.geometry(f'{width}x{height}+{x}+{y}')

class ToolboxApp:
    DEFAULT_CONFIG = {
        'title_font': 'Microsoft YaHei', 'nav_font': 'Microsoft YaHei', 'button_font': 'Microsoft YaHei',
        'header_bg': '#2c3e50', 'header_fg': '#ecf0f1', 'nav_bg': '#f8f9fa', 'content_bg': '#ffffff',
        'button_bg': '#ecf0f1', 'button_fg': '#000000', 'active_button_bg': '#d1d8e0',
        'icon_text_padding': 5, 'icon_size': 32, 'window_width': 800, 'window_height': 600,
        'tools_per_row': 3, 'title': '高级工具箱', 'title_font_size': 14, 'nav_font_size': 12,
        'button_font_size': 10, 'ip_font_size': 10, 'title_bar_height': 50, 'nav_bar_width': 150,
        'nav_button_spacing': 5, 'nav_button_top_margin': 10, 'button_width': 20,
        'button_padx': 5, 'button_pady': 5
    }
   
    CONFIG_MAPPING = {
        'window_width': '窗口宽度', 'window_height': '窗口高度', 'tools_per_row': '每行工具数',
        'title': '窗口标题', 'title_font_size': '主标题字号', 'nav_font_size': '导航栏字号',
        'button_font_size': '按钮字号', 'ip_font_size': 'IP字号', 'title_bar_height': '标题栏高度',
        'nav_bar_width': '导航栏宽度', 'nav_button_spacing': '导航按钮间距',
        'nav_button_top_margin': '导航栏顶部间距', 'icon_size': '图标大小', 'button_width': '按钮宽度',
        'button_padx': '水平间距', 'button_pady': '垂直间距'
    }

    PROTECTED_METADATA = {
        '作者': '缘起性空', '版本': '3.0', '联系': '32897251@qq.com', '说明': '本工具仅供技术交流使用'
    }

    def __init__(self, master):
        self.master = master
        self.base_path = os.path.dirname(sys.executable) if getattr(sys, 'frozen', False) else os.path.dirname(os.path.abspath(__file__))
        self.config = self.DEFAULT_CONFIG.copy()
        self.tools = {}
        self.icons = {}
        self.running_tools = {}
        self.tool_lock = Lock()
        self.current_section = None
        self.selected_button = None
        self.selected_button_name = None
        self._last_config_mtime = None
        self._initialize_app()

    def _initialize_app(self):
        try:
            self.master.withdraw()
            self._ensure_metadata()
            self._load_config()
            self._setup_ui()
            self.master.after(100, self._finalize_ui)
        except Exception as e:
            logger.critical(f"应用程序初始化失败: {str(e)}")
            messagebox.showerror("错误", f"程序初始化失败: {str(e)}")
            sys.exit(1)

    def _ensure_metadata(self):
        config_path = os.path.join(self.base_path, 'config.ini')
        if not os.path.exists(config_path):
            self._write_protected_metadata(config_path)
            return
            
        parser = configparser.ConfigParser()
        parser.read(config_path, encoding='utf-8')
        
        needs_update = any(
            not parser.has_option('程序信息', key) or parser.get('程序信息', key) != value
            for key, value in self.PROTECTED_METADATA.items()
        )
        
        if needs_update:
            self._write_protected_metadata(config_path, parser)

    def _write_protected_metadata(self, config_path, old_parser=None):
        try:
            parser = configparser.ConfigParser()
            if old_parser:
                for section in old_parser.sections():
                    if section != '程序信息':
                        parser.add_section(section)
                        for key, value in old_parser.items(section):
                            parser.set(section, key, value)
            
            with open(config_path, 'w', encoding='utf-8') as f:
                f.write("[程序信息]\n")
                for key, value in self.PROTECTED_METADATA.items():
                    f.write(f"{key} = {value}\n")
                f.write("\n")
               
                if '界面设置' in parser:
                    f.write("[界面设置]\n")
                    for key, value in parser.items('界面设置'):
                        f.write(f"{key} = {value}\n")
                    f.write("\n")
               
                for section in parser.sections():
                    if section not in {'程序信息', '界面设置'}:
                        f.write(f"[{section}]\n")
                        for key, value in parser.items(section):
                            f.write(f"{key} = {value}\n")
                        f.write("\n")
               
            logger.info("配置文件已更新")
        except Exception as e:
            logger.error(f"写入配置文件失败: {str(e)}")
            raise RuntimeError(f"无法写入配置文件: {str(e)}")

    def _finalize_ui(self):
        center_window(self.master, self.config['window_width'], self.config['window_height'])
        self._update_ip_display()
        self.master.deiconify()
        if self.tools:
            self.master.after(50, lambda: self.show_tools(next(iter(self.tools)), force=True))

    def _load_config(self):
        config_path = os.path.join(self.base_path, 'config.ini')
        if not os.path.exists(config_path):
            logger.warning(f"配置文件不存在: {config_path}, 使用默认配置")
            return
            
        current_mtime = os.path.getmtime(config_path)
        if self._last_config_mtime == current_mtime:
            return
               
        self._last_config_mtime = current_mtime
        parser = configparser.ConfigParser()
        parser.read(config_path, encoding='utf-8')
        
        if '界面设置' in parser:
            for internal_key, chinese_key in self.CONFIG_MAPPING.items():
                if parser.has_option('界面设置', chinese_key):
                    try:
                        value = parser.get('界面设置', chinese_key)
                        if internal_key in ['window_width', 'window_height', 'tools_per_row',
                                          'title_font_size', 'nav_font_size', 'button_font_size',
                                          'ip_font_size', 'title_bar_height', 'nav_bar_width',
                                          'nav_button_spacing', 'nav_button_top_margin',
                                          'icon_size', 'button_width',
                                          'button_padx', 'button_pady']:
                            self.config[internal_key] = int(value)
                        else:
                            self.config[internal_key] = value
                    except ValueError:
                        logger.warning(f"配置项 {chinese_key} 格式错误, 使用默认值")
        
        self._load_tools_config(parser)

    def _load_tools_config(self, parser):
        self.tools.clear()
        for section in parser.sections():
            if section in {'程序信息', '界面设置'}:
                continue
               
            self.tools[section] = []
            for name, cmd in parser.items(section):
                parts = cmd.split('|')
                tool_config = (
                    name.strip(),
                    parts[0].strip(),
                    parts[1].strip() if len(parts) > 1 else "运行",
                    parts[2].strip() if len(parts) > 2 and parts[2].strip() else None
                )
                self.tools[section].append(tool_config)
        
        if not self.tools:
            logger.warning("配置文件中没有定义任何工具分类")

    def _save_config(self):
        config_path = os.path.join(self.base_path, 'config.ini')
        try:
            old_parser = configparser.ConfigParser()
            if os.path.exists(config_path):
                old_parser.read(config_path, encoding='utf-8')
            
            with open(config_path, 'w', encoding='utf-8') as f:
                f.write("[程序信息]\n")
                for key, value in self.PROTECTED_METADATA.items():
                    f.write(f"{key} = {value}\n")
                f.write("\n[界面设置]\n")
                for internal_key, chinese_key in self.CONFIG_MAPPING.items():
                    if internal_key in self.config:
                        f.write(f"{chinese_key} = {self.config[internal_key]}\n")
                f.write("\n")
               
                for section, tools in self.tools.items():
                    f.write(f"[{section}]\n")
                    for name, cmd, btn_text, icon_path in tools:
                        value = f"{cmd}|{btn_text}"
                        if icon_path:
                            value += f"|{icon_path}"
                        f.write(f"{name} = {value}\n")
                    f.write("\n")
               
            self._last_config_mtime = os.path.getmtime(config_path)
            logger.info("配置文件已保存")
        except Exception as e:
            logger.error(f"保存配置文件失败: {str(e)}")
            messagebox.showerror("错误", f"保存配置文件失败: {str(e)}")

    def _setup_ui(self):
        self.style = Style(theme="minty")
        self._configure_styles()
        self.master.title(self.config['title'])
        
        main_frame = ttk.Frame(self.master)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        self._setup_header(main_frame)
        self._setup_body(main_frame)
        self._setup_button_context_menu()
        
        if sys.platform == 'win32':
            self.master.tk.call('tk', 'scaling', 1.5)
            self.master.option_add('*tearOff', False)

    def _setup_header(self, parent):
        header_frame = ttk.Frame(parent, height=self.config['title_bar_height'], style='Header.TFrame')
        header_frame.pack(fill=tk.X)
        header_frame.pack_propagate(False)
        
        ttk.Label(header_frame, text=self.config['title'], style='Header.TLabel').pack(side=tk.LEFT, padx=20)
        
        info_frame = ttk.Frame(header_frame, style='Header.TFrame')
        info_frame.pack(side=tk.RIGHT, padx=10)
        
        self.ip_label = ttk.Label(info_frame, text="IP: 获取中...", style='IP.TLabel')
        self.ip_label.pack(side=tk.LEFT, padx=5)
        
        ttk.Button(info_frame, text="⚙", command=self._show_toolbox_manager, style='NoHover.TButton').pack(side=tk.LEFT, padx=5)
        ttk.Button(info_frame, text="⭐", command=self.show_about, style='NoHover.TButton').pack(side=tk.LEFT, padx=5)

    def _show_toolbox_manager(self):
        ToolboxManager(self.master, os.path.join(self.base_path, 'config.ini'), self._reload_config)

    def _reload_config(self):
        current_section = self.current_section
        self._load_config()
        
        for widget in self.nav_frame.winfo_children():
            widget.destroy()
        
        ttk.Frame(self.nav_frame, height=self.config['nav_button_top_margin'], style='Nav.TFrame').pack()
        
        for section in self.tools.keys():
            btn = ttk.Button(self.nav_frame, text=section, command=lambda s=section: self.show_tools(s, force=True), style='Nav.TButton')
            btn.pack(fill=tk.X, padx=5, pady=self.config['nav_button_spacing'])
        
        if current_section in self.tools:
            self.show_tools(current_section, force=True)
        elif self.tools:
            self.show_tools(next(iter(self.tools)), force=True)
        else:
            self._clear_content_frame()
            self.current_section = None

    def _setup_body(self, parent):
        body_frame = ttk.Frame(parent)
        body_frame.pack(fill=tk.BOTH, expand=True)
        
        self._setup_navigation(body_frame)
        
        self.content_frame = ttk.Frame(body_frame, style='Content.TFrame')
        self.content_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)

    def _setup_navigation(self, parent):
        self.nav_frame = ttk.Frame(parent, width=self.config['nav_bar_width'], style='Nav.TFrame')
        self.nav_frame.pack(side=tk.LEFT, fill=tk.Y)
        self.nav_frame.pack_propagate(False)
        
        ttk.Frame(self.nav_frame, height=self.config['nav_button_top_margin'], style='Nav.TFrame').pack()
        
        for section in self.tools.keys():
            btn = ttk.Button(self.nav_frame, text=section, command=lambda s=section: self.show_tools(s, force=True), style='Nav.TButton')
            btn.pack(fill=tk.X, padx=5, pady=self.config['nav_button_spacing'])

    def _setup_button_context_menu(self):
        self.button_menu = tk.Menu(self.master, tearoff=0)
        self.button_menu.add_command(label="运行", command=self._run_selected_tool)
        self.button_menu.add_command(label="重命名", command=self._rename_tool)
        self.button_menu.add_command(label="设置图标", command=self._set_tool_icon)
        self.button_menu.add_command(label="删除图标", command=self._remove_tool_icon)
        self.button_menu.add_separator()
        self.button_menu.add_command(label="删除", command=self._delete_tool)
        self.master.bind("<Button-1>", self._clear_selection)

    def _configure_styles(self):
        self.style.configure('Header.TFrame', background=self.config['header_bg'])
        self.style.configure('Header.TLabel', background=self.config['header_bg'],
                           foreground=self.config['header_fg'],
                           font=(self.config['title_font'], self.config['title_font_size'], 'bold'))
        self.style.configure('IP.TLabel', background=self.config['header_bg'],
                           foreground=self.config['header_fg'],
                           font=(self.config['button_font'], self.config['ip_font_size']))
        self.style.configure('Nav.TFrame', background=self.config['nav_bg'])
        self.style.configure('Content.TFrame', background=self.config['content_bg'])
        self.style.configure('Tool.TFrame', background=self.config['content_bg'])
        self.style.configure('Nav.TButton', font=(self.config['nav_font'], self.config['nav_font_size']),
                           background=self.config['nav_bg'], foreground='#333333',
                           borderwidth=0, padding=6)
        self.style.configure('Primary.TButton', font=(self.config['button_font'], self.config['button_font_size']),
                           background=self.config['button_bg'], foreground=self.config['button_fg'],
                           borderwidth=1, relief="solid",
                           padding=(self.config['icon_text_padding'], self.config['button_pady']),
                           anchor='w', width=self.config['button_width'])
        self.style.map('Primary.TButton',
                      background=[('active', self.config['active_button_bg'])],
                      foreground=[('active', '#000000')])
        self.style.configure('NoHover.TButton', font=(self.config['button_font'], self.config['button_font_size']),
                           foreground=self.config['header_fg'], background=self.config['header_bg'],
                           borderwidth=0, padding=2)
        self.style.map('NoHover.TButton', foreground=[], background=[])

    def show_tools(self, section, force=False):
        if not force and self.current_section == section:
            return
            
        self._clear_content_frame()
        self.current_section = section
        
        if section not in self.tools:
            return
        
        grid_frame = ttk.Frame(self.content_frame, style='Content.TFrame')
        grid_frame.pack(fill=tk.BOTH, expand=True)
        
        self._preload_icons(section)
        
        row, col = 0, 0
        for name, cmd, btn_text, icon_path in self.tools[section]:
            if col == 0:
                row_frame = ttk.Frame(grid_frame, style='Tool.TFrame')
                row_frame.grid(row=row, column=0, sticky="ew", pady=self.config['button_pady'])
            
            btn = ttk.Button(row_frame, text=f"    {name}", command=lambda c=cmd, n=name: self.run_tool(c, n), style='Primary.TButton')
            
            if icon_path:
                try:
                    icon = self._load_icon(icon_path)
                    btn.config(image=icon, compound=tk.LEFT, padding=(self.config['icon_text_padding'], 0, 0, 0))
                    btn.image = icon
                except Exception as e:
                    logger.error(f"设置按钮图标失败: {name} - {str(e)}")
            
            btn.grid(row=0, column=col, padx=self.config['button_padx'], pady=self.config['button_pady'], sticky="nsew")
            btn.bind("<Button-3>", lambda e, b=btn, n=name: self._show_button_context_menu(e, b, n))
            
            col += 1
            if col >= self.config['tools_per_row']:
                col = 0
                row += 1
        
        for i in range(self.config['tools_per_row']):
            grid_frame.columnconfigure(i, weight=1)

    def _preload_icons(self, section):
        for _, _, _, icon_path in self.tools[section]:
            if icon_path:
                try:
                    self._load_icon(icon_path)
                except:
                    pass

    def _clear_content_frame(self):
        for widget in self.content_frame.winfo_children():
            if isinstance(widget, ttk.Button) and hasattr(widget, 'image'):
                widget.image = None
            widget.destroy()
        self.master.update_idletasks()

    def _load_icon(self, icon_path):
        if icon_path in self.icons:
            return self.icons[icon_path]
            
        try:
            if not os.path.isabs(icon_path):
                icon_path = os.path.join(self.base_path, icon_path)
            
            img = Image.open(icon_path).convert('RGBA')
            img = img.resize((self.config['icon_size'], self.config['icon_size']), Image.LANCZOS)
            photo = ImageTk.PhotoImage(img)
            self.icons[icon_path] = photo
            return photo
            
        except Exception as e:
            logger.error(f"加载图标失败: {os.path.abspath(icon_path)} - {str(e)}")
            empty_img = Image.new('RGBA', (self.config['icon_size'], self.config['icon_size']), (0, 0, 0, 0))
            empty_photo = ImageTk.PhotoImage(empty_img)
            self.icons[icon_path] = empty_photo
            return empty_photo

    def run_tool(self, command, name):
        if "禁用驱动签名" in name or "启用驱动签名" in name:
            if not self._show_driver_signature_warning(name):
                return
            
        if command.startswith(('http://', 'https://')):
            webbrowser.open(command)
        else:
            if command.startswith('tools/'):
                command = os.path.join(self.base_path, command)
                if not os.path.exists(command):
                    logger.error(f"工具不存在: {command}")
                    return
            
            Thread(target=self._execute_command, args=(command, name), daemon=True).start()

    def _execute_command(self, command, name):
        try:
            with self.tool_lock:
                if name in self.running_tools:
                    logger.warning(f"工具已在运行: {name}")
                    return
                self.running_tools[name] = True
               
            startupinfo = subprocess.STARTUPINFO()
            startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW
            startupinfo.wShowWindow = subprocess.SW_HIDE
            
            process = subprocess.Popen(command, shell=True, creationflags=subprocess.CREATE_NEW_CONSOLE, startupinfo=startupinfo)
            
            def monitor_process():
                try:
                    process.wait(timeout=30)
                except subprocess.TimeoutExpired:
                    logger.warning(f"工具执行超时:{name}")
                    process.kill()
                finally:
                    with self.tool_lock:
                        self.running_tools.pop(name, None)
            
            Thread(target=monitor_process, daemon=True).start()
                    
        except Exception as e:
            logger.error(f"执行失败: {name} - {str(e)}")
            with self.tool_lock:
                self.running_tools.pop(name, None)

    def _show_button_context_menu(self, event, button, name):
        self.selected_button = button
        self.selected_button_name = name
        try:
            self.button_menu.tk_popup(event.x_root, event.y_root)
        finally:
            self.button_menu.grab_release()

    def _clear_selection(self, event):
        if not isinstance(event.widget, ttk.Button):
            self.selected_button = None
        self.selected_button_name = None

    def _run_selected_tool(self):
        if not self.selected_button_name or not self.current_section:
            return
            
        for name, cmd, _, _ in self.tools[self.current_section]:
            if name == self.selected_button_name:
                self.run_tool(cmd, name)
                break

    def _rename_tool(self):
        if not self.selected_button_name or not self.current_section:
            return
            
        new_name = simpledialog.askstring("重命名工具", "输入新名称:", initialvalue=self.selected_button_name, parent=self.master)
        
        if not new_name or new_name == self.selected_button_name:
            return
            
        for i, (name, cmd, btn_text, icon_path) in enumerate(self.tools[self.current_section]):
            if name == self.selected_button_name:
                self.tools[self.current_section] = (new_name, cmd, btn_text, icon_path)
                break
               
        self._save_config()
        self.show_tools(self.current_section, force=True)

    def _set_tool_icon(self):
        if not self.selected_button_name or not self.current_section:
            return
            
        icon_path = filedialog.askopenfilename(
            title="选择图标文件",
            filetypes=[("图标文件", "*.ico *.png *.jpg *.jpeg *.bmp"), ("所有文件", "*.*")]
        )
        
        if not icon_path:
            return
            
        try:
            self._load_icon(icon_path)
            
            for i, (name, cmd, btn_text, _) in enumerate(self.tools[self.current_section]):
                if name == self.selected_button_name:
                    self.tools[self.current_section] = (name, cmd, btn_text, icon_path)
                    break
                    
            self._save_config()
            self.show_tools(self.current_section, force=True)
        except Exception as e:
            logger.error(f"设置图标失败: {str(e)}")

    def _remove_tool_icon(self):
        if not self.selected_button_name or not self.current_section:
            return
            
        for i, (name, cmd, btn_text, _) in enumerate(self.tools[self.current_section]):
            if name == self.selected_button_name:
                self.tools[self.current_section] = (name, cmd, btn_text, None)
                break
               
        self._save_config()
        self.show_tools(self.current_section, force=True)

    def _delete_tool(self):
        if not self.selected_button_name or not self.current_section:
            return
            
        if not messagebox.askyesno("确认删除", f"确定要删除工具 '{self.selected_button_name}' 吗?"):
            return
            
        self.tools[self.current_section] = [
            (name, cmd, btn_text, icon_path)
            for name, cmd, btn_text, icon_path in self.tools[self.current_section]
            if name != self.selected_button_name
        ]
        
        self._save_config()
        self.show_tools(self.current_section, force=True)

    def _show_driver_signature_warning(self, operation_name):
        warning_dialog = tk.Toplevel(self.master)
        warning_dialog.title("安全警告")
        warning_dialog.resizable(False, False)
        warning_dialog.transient(self.master)
        warning_dialog.grab_set()
        warning_dialog.configure(bg='#f8f9fa')
        
        dialog_width, dialog_height = 400, 250
        warning_dialog.geometry(f"{dialog_width}x{dialog_height}")
        warning_dialog.update_idletasks()
        
        screen_width = warning_dialog.winfo_screenwidth()
        screen_height = warning_dialog.winfo_screenheight()
        x = (screen_width - dialog_width) // 2
        y = (screen_height - dialog_height) // 2
        warning_dialog.geometry(f"+{x}+{y}")
        
        content_frame = tk.Frame(warning_dialog, bg='#f8f9fa', padx=15, pady=15)
        content_frame.pack(fill=tk.BOTH, expand=True)
        
        title_frame = tk.Frame(content_frame, bg='#f8f9fa')
        title_frame.pack(fill=tk.X, pady=(0, 10))
        
        tk.Label(title_frame, text="⚠", font=('Arial', 20), fg='orange', bg='#f8f9fa').pack(side=tk.LEFT, padx=(0, 10))
        tk.Label(title_frame, text="即将执行高风险操作!", font=(self.config['title_font'], self.config['title_font_size'], 'bold'),
                justify=tk.LEFT, bg='#f8f9fa').pack(side=tk.LEFT)

        text_frame = tk.Frame(content_frame, bg='#f8f9fa')
        text_frame.pack(fill=tk.X, padx=(30, 0), pady=(0, 15))
        
        for point in ["系统将重启进入高级启动模式", "临时禁用驱动签名验证", "确定要继续吗?"]:
            point_frame = tk.Frame(text_frame, bg='#f8f9fa')
            point_frame.pack(fill=tk.X, pady=2)
            tk.Label(point_frame, text="•", font=('Arial', 10), bg='#f8f9fa').pack(side=tk.LEFT, padx=(0, 5))
            tk.Label(point_frame, text=point, font=(self.config['button_font'], self.config['button_font_size']),
                    bg='#f8f9fa').pack(side=tk.LEFT)

        result = [False]
        button_frame = tk.Frame(content_frame, bg='#f8f9fa')
        button_frame.pack(fill=tk.X, padx=10, pady=10)
        
        ttk.Button(button_frame, text="是(Y)", command=lambda: [result.__setitem__(0, True), warning_dialog.destroy()],
                  style='Primary.TButton').pack(side=tk.RIGHT, padx=5)
        ttk.Button(button_frame, text="否(N)", command=lambda: [result.__setitem__(0, False), warning_dialog.destroy()],
                  style='Primary.TButton').pack(side=tk.RIGHT, padx=5)

        self.master.wait_window(warning_dialog)
        return result[0]

    def show_about(self):
        about_text = f"""{self.config['title']}

版本: {self.PROTECTED_METADATA['版本']}
作者: {self.PROTECTED_METADATA['作者']}
联系: {self.PROTECTED_METADATA['联系']}

{self.PROTECTED_METADATA['说明']}"""
        messagebox.showinfo("关于", about_text)

    def _update_ip_display(self):
        def update_ip():
            try:
                ip = get_local_ip()
                self.ip_label.config(text=f"IP: {ip}")
            except Exception as e:
                logger.error(f"获取IP失败: {str(e)}")
                self.ip_label.config(text="IP: 获取失败")
        
        Thread(target=update_ip, daemon=True).start()

class ToolboxManager:
    def __init__(self, master, config_path='config.ini', refresh_callback=None):
        self.master = master
        self.config_path = config_path
        self.refresh_callback = refresh_callback
        self.tools = {}
        self._load_config()
        
        self.window = tk.Toplevel(master)
        self.window.title("工具箱管理")
        self.window.geometry("450x300")
        self.window.resizable(False, False)
        self.window.protocol("WM_DELETE_WINDOW", self._on_close)
        self._center_window()
        self._create_ui()
        self._refresh_data()
   
    def _on_close(self):
        if self.refresh_callback:
            self.refresh_callback()
        self.window.destroy()
   
    def _center_window(self):
        self.window.update_idletasks()
        width, height = self.window.winfo_width(), self.window.winfo_height()
        x = (self.window.winfo_screenwidth() // 2) - (width // 2)
        y = (self.window.winfo_screenheight() // 2) - (height // 2)
        self.window.geometry(f'+{x}+{y}')
   
    def _load_config(self):
        self.config = configparser.ConfigParser()
        if os.path.exists(self.config_path):
            self.config.read(self.config_path, encoding='utf-8')
            
            for section in self.config.sections():
                if section in {'程序信息', '界面设置'}:
                    continue
                self.tools[section] = []
                for name, value in self.config.items(section):
                    parts = value.split('|')
                    self.tools[section].append({
                        'name': name,
                        'command': parts[0].strip(),
                        'btn_text': parts[1].strip() if len(parts) > 1 else "运行",
                        'icon_path': parts[2].strip() if len(parts) > 2 and parts[2].strip() else None
                    })

    def _save_config(self):
        try:
            with open(self.config_path, 'w', encoding='utf-8') as f:
                self.config.write(f)
            if self.refresh_callback:
                self.refresh_callback()
        except Exception as e:
            messagebox.showerror("错误", f"保存配置文件失败: {str(e)}", parent=self.window)
   
    def _create_ui(self):
        top_frame = ttk.Frame(self.window, padding=10)
        top_frame.pack(fill=tk.X)
        top_frame.columnconfigure(0, weight=1)
        top_frame.columnconfigure(1, weight=1)
        
        ttk.Button(top_frame, text="添加工具", command=self._show_add_tool_panel, style='primary.TButton').grid(row=0, column=0, padx=5, sticky='ew')
        ttk.Button(top_frame, text="管理分类", command=self._show_manage_category_panel, style='primary.TButton').grid(row=0, column=1, padx=5, sticky='ew')
        
        self.main_panel = ttk.Frame(self.window, padding=(10, 10, 10, 10))
        self.main_panel.pack(fill=tk.BOTH, expand=True)
        self._show_add_tool_panel()

    def _show_add_tool_panel(self):
        self._clear_main_panel()
        main_frame = ttk.Frame(self.main_panel)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        ttk.Label(main_frame, text="选择分类:").grid(row=0, column=0, sticky='w', pady=5)
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        self.cat_combobox = ttk.Combobox(main_frame, values=categories, state="readonly")
        self.cat_combobox.grid(row=0, column=1, padx=5, pady=5, sticky='ew')
        if categories:
            self.cat_combobox.current(0)
        
        ttk.Label(main_frame, text="工具名称:").grid(row=1, column=0, sticky='w', pady=5)
        self.tool_name_entry = ttk.Entry(main_frame)
        self.tool_name_entry.grid(row=1, column=1, padx=5, pady=5, sticky='ew')
        
        ttk.Label(main_frame, text="命令/URL:").grid(row=2, column=0, sticky='w', pady=5)
        self.cmd_entry = ttk.Entry(main_frame)
        self.cmd_entry.grid(row=2, column=1, padx=5, pady=5, sticky='ew')
        
        btn_frame = ttk.Frame(main_frame)
        btn_frame.grid(row=3, column=0, columnspan=2, pady=15, sticky='ew')
        btn_frame.columnconfigure(0, weight=1)
        btn_frame.columnconfigure(1, weight=1)
        
        ttk.Button(btn_frame, text="选择文件", command=self._browse_file).grid(row=0, column=0, padx=5, sticky='ew')
        ttk.Button(btn_frame, text="确认", command=self._add_tool, style='danger.TButton').grid(row=0, column=1, padx=5, sticky='ew')
        main_frame.columnconfigure(1, weight=1)

    def _show_manage_category_panel(self):
        self._clear_main_panel()
        main_frame = ttk.Frame(self.main_panel)
        main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
        
        add_frame = ttk.LabelFrame(main_frame, text="添加新分类", padding=10)
        add_frame.grid(row=0, column=0, padx=5, pady=5, sticky='ew')
        ttk.Label(add_frame, text="新分类名称:").pack(side=tk.LEFT)
        self.new_cat_entry = ttk.Entry(add_frame)
        self.new_cat_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        ttk.Button(add_frame, text="添加分类", command=self._add_category, style='success.TButton').pack(side=tk.LEFT, padx=5)
        
        del_frame = ttk.LabelFrame(main_frame, text="删除分类", padding=10)
        del_frame.grid(row=1, column=0, padx=5, pady=5, sticky='ew')
        ttk.Label(del_frame, text="选择分类:").pack(side=tk.LEFT)
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        self.del_category_combobox = ttk.Combobox(del_frame, values=categories, state="readonly")
        self.del_category_combobox.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True)
        if categories:
            self.del_category_combobox.current(0)
        ttk.Button(del_frame, text="删除分类", command=self._delete_category, style='danger.TButton').pack(side=tk.LEFT, padx=5)
        
        main_frame.columnconfigure(0, weight=1)
   
    def _clear_main_panel(self):
        for widget in self.main_panel.winfo_children():
            widget.destroy()
   
    def _refresh_data(self):
        categories = [cat for cat in self.tools.keys() if cat not in ['程序信息', '界面设置']]
        if hasattr(self, 'cat_combobox') and self.cat_combobox.winfo_exists():
            self.cat_combobox['values'] = categories
            if categories:
                self.cat_combobox.current(0)
        
        if hasattr(self, 'del_category_combobox') and self.del_category_combobox.winfo_exists():
            self.del_category_combobox['values'] = categories
            if categories:
                self.del_category_combobox.current(0)
   
    def _browse_file(self):
        self.window.attributes('-disabled', True)
        file_path = filedialog.askopenfilename(
            title="选择可执行文件",
            filetypes=[("可执行文件", "*.exe"), ("批处理文件", "*.bat *.cmd"), ("所有文件", "*.*")]
        )
        self.window.attributes('-disabled', False)
        self.window.focus_force()
        self.window.lift()
        
        if file_path:
            self.cmd_entry.delete(0, tk.END)
            self.cmd_entry.insert(0, file_path)
            file_name = os.path.basename(file_path)
            tool_name = os.path.splitext(file_name)[0]
            self.tool_name_entry.delete(0, tk.END)
            self.tool_name_entry.insert(0, tool_name)
   
    def _add_tool(self):
        category = self.cat_combobox.get()
        name = self.tool_name_entry.get().strip()
        cmd = self.cmd_entry.get().strip()
        
        if not category or not name or not cmd:
            messagebox.showwarning("警告", "请填写完整信息", parent=self.window)
            return
        
        if category not in self.tools:
            self.tools[category] = []
        
        self.tools[category].append({'name': name, 'command': cmd, 'btn_text': "运行", 'icon_path': None})
        
        if not self.config.has_section(category):
            self.config.add_section(category)
        
        self.config.set(category, name, f"{cmd}|运行")
        self._save_config()
        self.tool_name_entry.delete(0, tk.END)
        self.cmd_entry.delete(0, tk.END)
        messagebox.showinfo("成功", "工具添加成功", parent=self.window)
   
    def _add_category(self):
        new_cat = self.new_cat_entry.get().strip()
        
        if not new_cat or new_cat in ['程序信息', '界面设置']:
            messagebox.showwarning("警告", "无效的分类名称", parent=self.window)
            return
            
        if new_cat in self.tools:
            messagebox.showwarning("警告", "分类已存在", parent=self.window)
            return
        
        self.tools[new_cat] = []
        self.config.add_section(new_cat)
        self._save_config()
        self.new_cat_entry.delete(0, tk.END)
        self._refresh_data()
        messagebox.showinfo("成功", "分类添加成功", parent=self.window)
   
    def _delete_category(self):
        category = self.del_category_combobox.get()
        
        if not category or category in ['程序信息', '界面设置']:
            messagebox.showwarning("警告", "无效的分类", parent=self.window)
            return
            
        if not messagebox.askyesno("确认删除", f"确定要删除分类 '{category}' 及其所有工具吗?", parent=self.window):
            return
        
        if category in self.tools:
            del self.tools[category]
        
        if self.config.has_section(category):
            self.config.remove_section(category)
        
        self._save_config()
        self._clear_main_panel()
        self._show_manage_category_panel()
        messagebox.showinfo("成功", "分类删除成功", parent=self.window)

def main():
    if not is_admin():
        ctypes.windll.shell32.ShellExecuteW(None, "runas", sys.executable, " ".join(sys.argv), None, 1)
        sys.exit()
   
    root = tk.Tk()
   
    if sys.platform == 'win32':
        ctypes.windll.shcore.SetProcessDpiAwareness(1)
   
    try:
        root.attributes('-alpha', 0.0)
        app = ToolboxApp(root)
        root.after(100, lambda: root.attributes('-alpha', 1.0))
        root.mainloop()
    except Exception as e:
        logger.critical(f"程序崩溃: {str(e)}")

if __name__ == "__main__":
    main()
回复

使用道具 举报

2#
发表于 2025-6-1 11:40:29 | 只看该作者
感谢分享
回复

使用道具 举报

3#
发表于 2025-6-1 11:50:36 | 只看该作者
谢谢分享!!!
回复

使用道具 举报

4#
发表于 2025-6-1 12:13:17 | 只看该作者
谢谢分享。
回复

使用道具 举报

5#
发表于 2025-6-1 12:25:30 | 只看该作者
谢谢分享
回复

使用道具 举报

6#
发表于 2025-6-1 13:10:24 | 只看该作者

谢谢分享
回复

使用道具 举报

7#
发表于 2025-6-1 13:23:33 | 只看该作者
谢谢分享 链接呢

点评

http://bbs.wuyou.net/forum.php?mod=viewthread&tid=446192&extra=  详情 回复 发表于 2025-6-1 13:37
回复

使用道具 举报

8#
发表于 2025-6-1 13:35:14 | 只看该作者
感谢分享!
回复

使用道具 举报

9#
 楼主| 发表于 2025-6-1 13:37:13 | 只看该作者
回复

使用道具 举报

10#
发表于 2025-6-1 13:43:36 | 只看该作者
支持支持
回复

使用道具 举报

11#
发表于 2025-6-1 13:49:23 | 只看该作者
谢谢分享
回复

使用道具 举报

12#
发表于 2025-6-1 14:50:42 | 只看该作者
谢谢分享。
回复

使用道具 举报

13#
发表于 2025-6-1 15:11:17 | 只看该作者
多谢分享!!!
回复

使用道具 举报

14#
发表于 2025-6-1 15:18:39 | 只看该作者

谢谢分享
回复

使用道具 举报

15#
发表于 2025-6-1 15:30:24 | 只看该作者
感谢分享
回复

使用道具 举报

16#
发表于 2025-6-1 15:48:19 | 只看该作者
谢谢分享!!!
回复

使用道具 举报

17#
发表于 2025-6-1 15:50:47 | 只看该作者
谢谢分享,下载试用
回复

使用道具 举报

18#
发表于 2025-6-1 15:59:20 | 只看该作者
下载试试。
回复

使用道具 举报

19#
发表于 2025-6-1 16:09:07 | 只看该作者
感谢楼主分享。
回复

使用道具 举报

20#
发表于 2025-6-1 16:18:41 | 只看该作者
感谢分享
回复

使用道具 举报

21#
发表于 2025-6-1 16:27:31 | 只看该作者
很实用 收藏了
回复

使用道具 举报

22#
发表于 2025-6-1 16:47:52 | 只看该作者
感谢分享
回复

使用道具 举报

23#
发表于 2025-6-1 16:57:25 | 只看该作者
感谢分享
回复

使用道具 举报

24#
发表于 2025-6-1 17:17:58 | 只看该作者

感谢分享!
回复

使用道具 举报

25#
发表于 2025-6-1 17:25:26 | 只看该作者
谢谢楼主分享
回复

使用道具 举报

26#
发表于 2025-6-1 17:32:25 | 只看该作者

谢谢分享
回复

使用道具 举报

27#
 楼主| 发表于 2025-6-1 17:35:01 | 只看该作者
都没人测试吗
回复

使用道具 举报

28#
发表于 2025-6-1 18:05:25 | 只看该作者
我个人觉得原来那个界面好看

点评

两个版本冲突啊。  详情 回复 发表于 2025-6-1 18:51
回复

使用道具 举报

29#
 楼主| 发表于 2025-6-1 18:51:01 | 只看该作者
印第安老斑鸠 发表于 2025-6-1 18:05
我个人觉得原来那个界面好看

两个版本不冲突啊。
回复

使用道具 举报

30#
发表于 2025-6-1 19:03:25 | 只看该作者
厉害的工具箱
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|手机版|Archiver|捐助支持|无忧启动 ( 闽ICP备05002490号-1 )

闽公网安备 35020302032614号

GMT+8, 2025-6-10 10:59

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表