import tkinter as tk
from tkinter import messagebox, filedialog, ttk
import subprocess
import os
import threading

# 创建主窗口
root = tk.Tk()
root.title("PyInstaller 高级打包工具")
root.geometry("800x700")

# 样式配置
style = ttk.Style()
style.configure('TButton', padding=5)
style.configure('Title.TLabel', font=('Arial', 16, 'bold'))
style.configure('Section.TLabel', font=('Arial', 12, 'bold'))

# 创建标题
title_label = ttk.Label(root, text="PyInstaller 打包工具", style='Title.TLabel')
title_label.pack(pady=10)

# 创建Notebook（选项卡）
notebook = ttk.Notebook(root)
notebook.pack(fill='both', expand=True, padx=10, pady=5)

# 选项卡1：基本设置
basic_frame = ttk.Frame(notebook)
notebook.add(basic_frame, text='基本设置')

# 源文件选择
ttk.Label(basic_frame, text="Python源文件:", style='Section.TLabel').grid(row=0, column=0, sticky='w', padx=10, pady=10)

file_frame = ttk.Frame(basic_frame)
file_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

source_entry = ttk.Entry(file_frame, width=50)
source_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

def browse_source():
    filename = filedialog.askopenfilename(
        title="选择Python文件",
        filetypes=[("Python文件", "*.py"), ("所有文件", "*.*")]
    )
    if filename:
        source_entry.delete(0, tk.END)
        source_entry.insert(0, filename)

browse_btn = ttk.Button(file_frame, text="浏览", command=browse_source)
browse_btn.pack(side='right')

# 输出设置
ttk.Label(basic_frame, text="输出设置:", style='Section.TLabel').grid(row=2, column=0, sticky='w', padx=10, pady=10)

# 输出目录
ttk.Label(basic_frame, text="输出目录:").grid(row=3, column=0, sticky='w', padx=20, pady=5)
output_dir_entry = ttk.Entry(basic_frame, width=40)
output_dir_entry.grid(row=3, column=1, sticky='w', pady=5)
output_dir_entry.insert(0, "./dist")

def browse_output_dir():
    dirname = filedialog.askdirectory(title="选择输出目录")
    if dirname:
        output_dir_entry.delete(0, tk.END)
        output_dir_entry.insert(0, dirname)

ttk.Button(basic_frame, text="浏览", command=browse_output_dir).grid(row=3, column=2, padx=5)

# 输出文件名
ttk.Label(basic_frame, text="输出文件名:").grid(row=4, column=0, sticky='w', padx=20, pady=5)
name_entry = ttk.Entry(basic_frame, width=40)
name_entry.grid(row=4, column=1, sticky='w', pady=5)

# 工作目录
ttk.Label(basic_frame, text="工作目录:").grid(row=5, column=0, sticky='w', padx=20, pady=5)
workpath_entry = ttk.Entry(basic_frame, width=40)
workpath_entry.grid(row=5, column=1, sticky='w', pady=5)

# 选项卡2：打包模式
mode_frame = ttk.Frame(notebook)
notebook.add(mode_frame, text='打包模式')

# 打包模式选择
mode_var = tk.StringVar(value="onefile")
ttk.Label(mode_frame, text="打包模式:", style='Section.TLabel').grid(row=0, column=0, sticky='w', padx=10, pady=10)

ttk.Radiobutton(mode_frame, text="单个可执行文件", variable=mode_var, value="onefile").grid(row=1, column=0, sticky='w', padx=20, pady=5)
ttk.Radiobutton(mode_frame, text="目录形式（多个文件）", variable=mode_var, value="onedir").grid(row=2, column=0, sticky='w', padx=20, pady=5)

# 控制台选项
console_var = tk.StringVar(value="noconsole")
ttk.Label(mode_frame, text="控制台窗口:", style='Section.TLabel').grid(row=3, column=0, sticky='w', padx=10, pady=10)

ttk.Radiobutton(mode_frame, text="无控制台窗口（GUI程序）", variable=console_var, value="noconsole").grid(row=4, column=0, sticky='w', padx=20, pady=5)
ttk.Radiobutton(mode_frame, text="显示控制台窗口", variable=console_var, value="console").grid(row=5, column=0, sticky='w', padx=20, pady=5)

# 调试选项
debug_var = tk.BooleanVar()
ttk.Checkbutton(mode_frame, text="调试模式", variable=debug_var).grid(row=6, column=0, sticky='w', padx=20, pady=10)

# 选项卡3：高级选项
advanced_frame = ttk.Frame(notebook)
notebook.add(advanced_frame, text='高级选项')

# 图标文件
ttk.Label(advanced_frame, text="图标文件:", style='Section.TLabel').grid(row=0, column=0, sticky='w', padx=10, pady=10)

icon_frame = ttk.Frame(advanced_frame)
icon_frame.grid(row=1, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

icon_entry = ttk.Entry(icon_frame, width=40)
icon_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

def browse_icon():
    filename = filedialog.askopenfilename(
        title="选择图标文件",
        filetypes=[("图标文件", "*.ico"), ("所有文件", "*.*")]
    )
    if filename:
        icon_entry.delete(0, tk.END)
        icon_entry.insert(0, filename)

ttk.Button(icon_frame, text="浏览", command=browse_icon).pack(side='right')

# 版本信息
ttk.Label(advanced_frame, text="版本信息文件:", style='Section.TLabel').grid(row=2, column=0, sticky='w', padx=10, pady=10)

version_frame = ttk.Frame(advanced_frame)
version_frame.grid(row=3, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

version_entry = ttk.Entry(version_frame, width=40)
version_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

def browse_version():
    filename = filedialog.askopenfilename(
        title="选择版本信息文件",
        filetypes=[("版本信息文件", "*.txt"), ("所有文件", "*.*")]
    )
    if filename:
        version_entry.delete(0, tk.END)
        version_entry.insert(0, filename)

ttk.Button(version_frame, text="浏览", command=browse_version).pack(side='right')

# 附加文件
ttk.Label(advanced_frame, text="附加文件/目录:", style='Section.TLabel').grid(row=4, column=0, sticky='w', padx=10, pady=10)

add_data_frame = ttk.Frame(advanced_frame)
add_data_frame.grid(row=5, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

add_data_entry = ttk.Entry(add_data_frame, width=40)
add_data_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

def browse_add_data():
    selection = messagebox.askquestion("选择", "选择文件还是目录？", icon='question')
    if selection == 'yes':
        # 文件
        filename = filedialog.askopenfilename(title="选择附加文件")
        if filename:
            add_data_entry.insert(tk.END, f'"{filename}";')
    else:
        # 目录
        dirname = filedialog.askdirectory(title="选择附加目录")
        if dirname:
            add_data_entry.insert(tk.END, f'"{dirname}";')

ttk.Button(add_data_frame, text="添加", command=browse_add_data).pack(side='right')

# 导入路径
ttk.Label(advanced_frame, text="附加导入路径:", style='Section.TLabel').grid(row=6, column=0, sticky='w', padx=10, pady=10)

paths_frame = ttk.Frame(advanced_frame)
paths_frame.grid(row=7, column=0, columnspan=3, padx=10, pady=5, sticky='ew')

paths_entry = ttk.Entry(paths_frame, width=40)
paths_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

def browse_path():
    dirname = filedialog.askdirectory(title="选择附加路径")
    if dirname:
        paths_entry.insert(tk.END, f'{dirname};')

ttk.Button(paths_frame, text="添加", command=browse_path).pack(side='right')

# 选项卡4：优化选项
optimize_frame = ttk.Frame(notebook)
notebook.add(optimize_frame, text='优化选项')

# UPX压缩
upx_var = tk.BooleanVar(value=True)
ttk.Checkbutton(optimize_frame, text="使用UPX压缩", variable=upx_var).grid(row=0, column=0, sticky='w', padx=20, pady=10)

ttk.Label(optimize_frame, text="UPX目录:").grid(row=1, column=0, sticky='w', padx=30, pady=5)
upx_dir_entry = ttk.Entry(optimize_frame, width=40)
upx_dir_entry.grid(row=1, column=1, sticky='w', pady=5)
upx_dir_entry.insert(0, "./upx")

def browse_upx():
    dirname = filedialog.askdirectory(title="选择UPX目录")
    if dirname:
        upx_dir_entry.delete(0, tk.END)
        upx_dir_entry.insert(0, dirname)

ttk.Button(optimize_frame, text="浏览", command=browse_upx).grid(row=1, column=2, padx=5)

# 优化级别
ttk.Label(optimize_frame, text="优化级别:", style='Section.TLabel').grid(row=2, column=0, sticky='w', padx=20, pady=10)

optimize_var = tk.StringVar(value="default")
ttk.Radiobutton(optimize_frame, text="默认", variable=optimize_var, value="default").grid(row=3, column=0, sticky='w', padx=30, pady=5)
ttk.Radiobutton(optimize_frame, text="优化导入", variable=optimize_var, value="optimize").grid(row=4, column=0, sticky='w', padx=30, pady=5)
ttk.Radiobutton(optimize_frame, text="排除导入", variable=optimize_var, value="exclude").grid(row=5, column=0, sticky='w', padx=30, pady=5)

# 排除模块
ttk.Label(optimize_frame, text="排除模块:", style='Section.TLabel').grid(row=6, column=0, sticky='w', padx=20, pady=10)

exclude_frame = ttk.Frame(optimize_frame)
exclude_frame.grid(row=7, column=0, columnspan=3, padx=30, pady=5, sticky='ew')

exclude_entry = ttk.Entry(exclude_frame, width=40)
exclude_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))
exclude_entry.insert(0, "tkinter;pygame;matplotlib")

# 隐藏导入
ttk.Label(optimize_frame, text="隐藏导入:", style='Section.TLabel').grid(row=8, column=0, sticky='w', padx=20, pady=10)

hidden_frame = ttk.Frame(optimize_frame)
hidden_frame.grid(row=9, column=0, columnspan=3, padx=30, pady=5, sticky='ew')

hidden_entry = ttk.Entry(hidden_frame, width=40)
hidden_entry.pack(side='left', fill='x', expand=True, padx=(0, 5))

# 日志输出区域
output_frame = ttk.LabelFrame(root, text="输出日志")
output_frame.pack(fill='both', expand=True, padx=10, pady=10)

output_text = tk.Text(output_frame, height=10, wrap='word')
output_text.pack(fill='both', expand=True, padx=5, pady=5)

scrollbar = ttk.Scrollbar(output_text)
scrollbar.pack(side='right', fill='y')
output_text.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=output_text.yview)

# 底部按钮框架
button_frame = ttk.Frame(root)
button_frame.pack(pady=10)

# 执行命令的函数
def execute_command_thread(command):
    """在新线程中执行命令"""
    try:
        # 清空输出
        output_text.delete('1.0', tk.END)
        output_text.insert('1.0', f"执行命令: {command}\n")
        output_text.insert('end', "="*50 + "\n")
        
        # 执行命令
        process = subprocess.Popen(
            command,
            shell=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
            text=True,
            encoding='gbk',
            bufsize=1,
            universal_newlines=True
        )
        
        # 实时读取输出
        for line in process.stdout:
            output_text.insert('end', line)
            output_text.see('end')  # 滚动到底部
            root.update()
        
        process.wait()
        
        if process.returncode == 0:
            output_text.insert('end', "\n✅ 打包完成！\n")
        else:
            output_text.insert('end', f"\n❌ 打包失败，返回码: {process.returncode}\n")
            
    except Exception as e:
        output_text.insert('end', f"\n❌ 错误: {str(e)}\n")
    
    # 启用按钮
    for btn in [generate_btn, execute_btn, clear_btn]:
        btn.config(state='normal')

def execute_command():
    """执行打包命令"""
    command = generate_command()
    
    # 禁用按钮防止重复点击
    for btn in [generate_btn, execute_btn, clear_btn]:
        btn.config(state='disabled')
    
    # 在新线程中执行
    thread = threading.Thread(target=execute_command_thread, args=(command,))
    thread.daemon = True
    thread.start()

def generate_command():
    """生成PyInstaller命令"""
    # 基本参数
    source_file = source_entry.get()
    if not source_file:
        messagebox.showwarning("警告", "请选择Python源文件！")
        return ""
    
    # 构建命令
    cmd = "pyinstaller"
    
    # 打包模式
    if mode_var.get() == "onefile":
        cmd += " --onefile"
    else:
        cmd += " --onedir"
    
    # 控制台选项
    if console_var.get() == "noconsole":
        cmd += " --noconsole"
    elif console_var.get() == "console":
        cmd += " --console"
    
    # 调试模式
    if debug_var.get():
        cmd += " --debug"
    
    # 输出目录
    output_dir = output_dir_entry.get()
    if output_dir:
        cmd += f' --distpath "{output_dir}"'
    
    # 工作目录
    workpath = workpath_entry.get()
    if workpath:
        cmd += f' --workpath "{workpath}"'
    
    # 输出文件名
    name = name_entry.get()
    if name:
        cmd += f' --name "{name}"'
    
    # 图标文件
    icon = icon_entry.get()
    if icon:
        cmd += f' --icon "{icon}"'
    
    # 版本信息
    version = version_entry.get()
    if version:
        cmd += f' --version-file "{version}"'
    
    # 附加文件/目录
    add_data = add_data_entry.get()
    if add_data:
        for item in add_data.split(';'):
            if item.strip():
                cmd += f' --add-data "{item.strip()}"'
    
    # 附加导入路径
    paths = paths_entry.get()
    if paths:
        for path in paths.split(';'):
            if path.strip():
                cmd += f' --paths "{path.strip()}"'
    
    # UPX选项
    if upx_var.get():
        cmd += " --upx-exclude"
        upx_dir = upx_dir_entry.get()
        if upx_dir:
            cmd += f' --upx-dir "{upx_dir}"'
    
    # 优化选项
    if optimize_var.get() == "optimize":
        cmd += " --optimize"
    elif optimize_var.get() == "exclude":
        cmd += " --exclude"
    
    # 排除模块
    exclude = exclude_entry.get()
    if exclude:
        for module in exclude.split(';'):
            if module.strip():
                cmd += f' --exclude-module "{module.strip()}"'
    
    # 隐藏导入
    hidden = hidden_entry.get()
    if hidden:
        for module in hidden.split(';'):
            if module.strip():
                cmd += f' --hidden-import "{module.strip()}"'
    
    # 添加源文件
    cmd += f' "{source_file}"'
    
    # 显示命令
    output_text.delete('1.0', tk.END)
    output_text.insert('1.0', f"生成的命令:\n{cmd}\n")
    
    return cmd

def show_command():
    """显示生成的命令"""
    command = generate_command()
    if command:
        messagebox.showinfo("PyInstaller命令", f"生成的命令:\n\n{command}")

def clear_output():
    """清空输出"""
    output_text.delete('1.0', tk.END)

# 创建按钮
generate_btn = ttk.Button(button_frame, text="生成命令", command=show_command)
generate_btn.pack(side='left', padx=5)

execute_btn = ttk.Button(button_frame, text="执行打包", command=execute_command)
execute_btn.pack(side='left', padx=5)

clear_btn = ttk.Button(button_frame, text="清空输出", command=clear_output)
clear_btn.pack(side='left', padx=5)

# 添加说明
info_label = ttk.Label(root, text="注意：请确保已安装 pyinstaller (pip install pyinstaller)", 
                       foreground="blue")
info_label.pack(pady=5)

# 预设命令按钮框架
preset_frame = ttk.Frame(root)
preset_frame.pack(pady=5)

def set_preset(preset_name):
    """设置预设配置"""
    if preset_name == "gui_app":
        # GUI应用预设
        mode_var.set("onefile")
        console_var.set("noconsole")
        debug_var.set(False)
        output_dir_entry.delete(0, tk.END)
        output_dir_entry.insert(0, "./dist")
        
    elif preset_name == "console_app":
        # 控制台应用预设
        mode_var.set("onefile")
        console_var.set("console")
        debug_var.set(False)
        
    elif preset_name == "debug_mode":
        # 调试模式预设
        mode_var.set("onedir")
        console_var.set("console")
        debug_var.set(True)

ttk.Label(preset_frame, text="预设配置:").pack(side='left', padx=5)
ttk.Button(preset_frame, text="GUI应用", command=lambda: set_preset("gui_app")).pack(side='left', padx=2)
ttk.Button(preset_frame, text="控制台应用", command=lambda: set_preset("console_app")).pack(side='left', padx=2)
ttk.Button(preset_frame, text="调试模式", command=lambda: set_preset("debug_mode")).pack(side='left', padx=2)

root.mainloop()