539 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			539 lines
		
	
	
		
			21 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import tkinter as tk
 | 
						||
from tkinter import ttk, messagebox, filedialog, simpledialog
 | 
						||
from pathlib import Path
 | 
						||
from models import Project, Template
 | 
						||
from canvas_widget import CanvasWidget
 | 
						||
from template_dialog import TemplateDialog
 | 
						||
from export_dialog import ExportDialog
 | 
						||
from exporter import Exporter
 | 
						||
 | 
						||
# 确保中文显示正常
 | 
						||
import matplotlib
 | 
						||
 | 
						||
matplotlib.use('Agg')
 | 
						||
import matplotlib.pyplot as plt
 | 
						||
 | 
						||
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
 | 
						||
 | 
						||
 | 
						||
class MainWindow(tk.Tk):
 | 
						||
    """主窗口"""
 | 
						||
 | 
						||
    def __init__(self):
 | 
						||
        super().__init__()
 | 
						||
        self.title("批量图片布局工具")
 | 
						||
        self.project = Project()
 | 
						||
        self.init_ui()
 | 
						||
 | 
						||
    def init_ui(self):
 | 
						||
        """初始化UI"""
 | 
						||
        # 设置样式
 | 
						||
        style = ttk.Style()
 | 
						||
        style.configure("TButton", padding=5)
 | 
						||
        style.configure("TLabel", padding=2)
 | 
						||
 | 
						||
        # 菜单栏
 | 
						||
        self.create_menu()
 | 
						||
 | 
						||
        # 工具栏
 | 
						||
        toolbar = ttk.Frame(self, padding=5)
 | 
						||
        toolbar.pack(fill=tk.X)
 | 
						||
 | 
						||
        ttk.Button(toolbar, text="打开背景", command=self.open_background).pack(side=tk.LEFT, padx=2)
 | 
						||
        ttk.Button(toolbar, text="添加图片", command=self.add_foregrounds).pack(side=tk.LEFT, padx=2)
 | 
						||
        ttk.Button(toolbar, text="清除图片", command=self.clear_foregrounds).pack(side=tk.LEFT, padx=2)
 | 
						||
        ttk.Button(toolbar, text="模板设置", command=self.set_template).pack(side=tk.LEFT, padx=2)
 | 
						||
        ttk.Button(toolbar, text="批量导出", command=self.batch_export).pack(side=tk.LEFT, padx=2)
 | 
						||
 | 
						||
        # 主布局
 | 
						||
        main_frame = ttk.Frame(self, padding=5)
 | 
						||
        main_frame.pack(fill=tk.BOTH, expand=True)
 | 
						||
 | 
						||
        # 左侧:图片列表
 | 
						||
        left_frame = ttk.Frame(main_frame, width=200)
 | 
						||
        left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=(0, 5))
 | 
						||
 | 
						||
        ttk.Label(left_frame, text="前景图片列表", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        self.image_list_frame = ttk.Frame(left_frame)
 | 
						||
        self.image_list_frame.pack(fill=tk.BOTH, expand=True)
 | 
						||
 | 
						||
        # 滚动条
 | 
						||
        scrollbar = ttk.Scrollbar(self.image_list_frame)
 | 
						||
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
 | 
						||
 | 
						||
        self.image_listbox = tk.Listbox(
 | 
						||
            self.image_list_frame,
 | 
						||
            yscrollcommand=scrollbar.set,
 | 
						||
            selectmode=tk.SINGLE,
 | 
						||
            height=20
 | 
						||
        )
 | 
						||
        self.image_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
 | 
						||
        scrollbar.config(command=self.image_listbox.yview)
 | 
						||
 | 
						||
        # 绑定列表事件
 | 
						||
        self.image_listbox.bind('<<ListboxSelect>>', self.on_image_select)
 | 
						||
 | 
						||
        # 添加按钮
 | 
						||
        ttk.Button(left_frame, text="添加图片", command=self.add_foregrounds).pack(fill=tk.X, pady=5)
 | 
						||
        ttk.Button(left_frame, text="移除选中", command=self.remove_selected).pack(fill=tk.X)
 | 
						||
 | 
						||
        # 中间:画布区域
 | 
						||
        center_frame = ttk.Frame(main_frame)
 | 
						||
        center_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
 | 
						||
 | 
						||
        # 画布滚动条
 | 
						||
        hscroll = ttk.Scrollbar(center_frame, orient=tk.HORIZONTAL)
 | 
						||
        hscroll.pack(side=tk.BOTTOM, fill=tk.X)
 | 
						||
 | 
						||
        vscroll = ttk.Scrollbar(center_frame, orient=tk.VERTICAL)
 | 
						||
        vscroll.pack(side=tk.RIGHT, fill=tk.Y)
 | 
						||
 | 
						||
        # 创建画布
 | 
						||
        self.canvas = CanvasWidget(center_frame, self.project, main_window=self)
 | 
						||
        # self.canvas = CanvasWidget(center_frame, self.project)
 | 
						||
        self.canvas.pack(fill=tk.BOTH, expand=True)
 | 
						||
 | 
						||
        # 关联滚动条
 | 
						||
        self.canvas.config(xscrollcommand=hscroll.set, yscrollcommand=vscroll.set)
 | 
						||
        hscroll.config(command=self.canvas.xview)
 | 
						||
        vscroll.config(command=self.canvas.yview)
 | 
						||
 | 
						||
        # 右侧:属性面板
 | 
						||
        right_frame = ttk.Frame(main_frame, width=200)
 | 
						||
        right_frame.pack(side=tk.RIGHT, fill=tk.Y, padx=(5, 0))
 | 
						||
 | 
						||
        ttk.Label(right_frame, text="属性设置", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        self.property_frame = ttk.Frame(right_frame)
 | 
						||
        self.property_frame.pack(fill=tk.BOTH, expand=True, padx=5)
 | 
						||
 | 
						||
        # 初始化属性面板内容
 | 
						||
        self.clear_property_panel()
 | 
						||
 | 
						||
        # 设置窗口大小
 | 
						||
        self.geometry("1200x700")
 | 
						||
 | 
						||
    def create_menu(self):
 | 
						||
        """创建菜单栏"""
 | 
						||
        menubar = tk.Menu(self)
 | 
						||
 | 
						||
        # 文件菜单
 | 
						||
        file_menu = tk.Menu(menubar, tearoff=0)
 | 
						||
        file_menu.add_command(label="新建项目", command=self.new_project)
 | 
						||
        file_menu.add_command(label="打开项目", command=self.open_project)
 | 
						||
        file_menu.add_command(label="保存项目", command=self.save_project)
 | 
						||
        file_menu.add_command(label="另存为", command=self.save_project_as)
 | 
						||
        file_menu.add_separator()
 | 
						||
        file_menu.add_command(label="退出", command=self.quit)
 | 
						||
        menubar.add_cascade(label="文件", menu=file_menu)
 | 
						||
 | 
						||
        # 编辑菜单
 | 
						||
        edit_menu = tk.Menu(menubar, tearoff=0)
 | 
						||
        edit_menu.add_command(label="清除所有图片", command=self.clear_foregrounds)
 | 
						||
        edit_menu.add_separator()
 | 
						||
        edit_menu.add_command(label="上移图层", command=lambda: self.move_layer(1))
 | 
						||
        edit_menu.add_command(label="下移图层", command=lambda: self.move_layer(-1))
 | 
						||
        menubar.add_cascade(label="编辑", menu=edit_menu)
 | 
						||
 | 
						||
        # 视图菜单
 | 
						||
        view_menu = tk.Menu(menubar, tearoff=0)
 | 
						||
        view_menu.add_command(label="缩放重置", command=self.reset_zoom)
 | 
						||
        menubar.add_cascade(label="视图", menu=view_menu)
 | 
						||
 | 
						||
        # 帮助菜单
 | 
						||
        help_menu = tk.Menu(menubar, tearoff=0)
 | 
						||
        help_menu.add_command(label="关于", command=self.show_about)
 | 
						||
        menubar.add_cascade(label="帮助", menu=help_menu)
 | 
						||
 | 
						||
        # 设置菜单栏
 | 
						||
        self.config(menu=menubar)
 | 
						||
 | 
						||
    def open_background(self):
 | 
						||
        """打开背景图"""
 | 
						||
        file_path = filedialog.askopenfilename(
 | 
						||
            filetypes=[
 | 
						||
                ("图像文件", "*.jpg;*.jpeg;*.png;*.webp;*.bmp"),
 | 
						||
                ("所有文件", "*.*")
 | 
						||
            ]
 | 
						||
        )
 | 
						||
        if file_path:
 | 
						||
            if self.project.template.load_background(file_path):
 | 
						||
                self.canvas.draw_preview()
 | 
						||
 | 
						||
    def add_foregrounds(self):
 | 
						||
        """添加前景图"""
 | 
						||
        file_paths = filedialog.askopenfilenames(
 | 
						||
            filetypes=[
 | 
						||
                ("图像文件", "*.jpg;*.jpeg;*.png;*.webp;*.bmp"),
 | 
						||
                ("所有文件", "*.*")
 | 
						||
            ]
 | 
						||
        )
 | 
						||
 | 
						||
        if file_paths:
 | 
						||
            # 检查是否是第一次添加图片
 | 
						||
            is_first_batch = len(self.project.foregrounds) == 0
 | 
						||
            
 | 
						||
            # 添加所有选择的图片
 | 
						||
            added_foregrounds = []
 | 
						||
            for file_path in file_paths:
 | 
						||
                # 检查是否已添加
 | 
						||
                if not any(fg.file_path == file_path for fg in self.project.foregrounds):
 | 
						||
                    fg = self.project.add_foreground(file_path)
 | 
						||
                    added_foregrounds.append(fg)
 | 
						||
            
 | 
						||
            # 如果是第一次批量添加,则设置批量处理模式
 | 
						||
            if is_first_batch and len(added_foregrounds) > 1:
 | 
						||
                # 第一张图片保持可见和可编辑
 | 
						||
                first_fg = added_foregrounds[0]
 | 
						||
                first_fg.hidden = False
 | 
						||
                first_fg.visible = True
 | 
						||
                
 | 
						||
                # 其他图片设置为隐藏
 | 
						||
                for fg in added_foregrounds[1:]:
 | 
						||
                    fg.hidden = True
 | 
						||
                    fg.visible = False
 | 
						||
                    
 | 
						||
                # 选中第一张图片
 | 
						||
                self.canvas.selected_item = first_fg
 | 
						||
                self.update_property_panel(first_fg)
 | 
						||
            elif not is_first_batch and self.project.foregrounds:
 | 
						||
                # 如果不是第一次添加,且已存在图片,则复制第一张图片的配置
 | 
						||
                reference_fg = self.project.foregrounds[0]
 | 
						||
                for fg in added_foregrounds:
 | 
						||
                    fg.vertices = [tuple(v) for v in reference_fg.vertices]
 | 
						||
                    fg.angle = reference_fg.angle
 | 
						||
                    fg.mask_shape = reference_fg.mask_shape
 | 
						||
                    fg.locked = reference_fg.locked
 | 
						||
                    fg.visible = reference_fg.visible
 | 
						||
                    fg.hidden = reference_fg.hidden
 | 
						||
 | 
						||
            # 更新列表
 | 
						||
            self.update_image_list()
 | 
						||
            # 重绘画布
 | 
						||
            self.canvas.draw_preview()
 | 
						||
 | 
						||
    def update_image_list(self):
 | 
						||
        """更新图片列表"""
 | 
						||
        self.image_listbox.delete(0, tk.END)
 | 
						||
        for fg in self.project.foregrounds:
 | 
						||
            name = Path(fg.file_path).name
 | 
						||
            self.image_listbox.insert(tk.END, name)
 | 
						||
 | 
						||
    def on_image_select(self, event):
 | 
						||
        """处理列表选择事件"""
 | 
						||
        selection = self.image_listbox.curselection()
 | 
						||
        if selection:
 | 
						||
            index = selection[0]
 | 
						||
            if 0 <= index < len(self.project.foregrounds):
 | 
						||
                fg = self.project.foregrounds[index]
 | 
						||
                self.canvas.selected_item = fg
 | 
						||
                self.update_property_panel(fg)
 | 
						||
                self.canvas.draw_preview()
 | 
						||
        else:
 | 
						||
            # 如果没有选择任何项目,显示默认面板
 | 
						||
            self.clear_property_panel()
 | 
						||
 | 
						||
    def remove_selected(self):
 | 
						||
        """移除选中的图片"""
 | 
						||
        selection = self.image_listbox.curselection()
 | 
						||
        if selection:
 | 
						||
            index = selection[0]
 | 
						||
            if 0 <= index < len(self.project.foregrounds):
 | 
						||
                fg = self.project.foregrounds[index]
 | 
						||
                self.project.remove_foreground(fg)
 | 
						||
                self.update_image_list()
 | 
						||
                self.clear_property_panel()
 | 
						||
                self.canvas.selected_item = None
 | 
						||
                self.canvas.draw_preview()
 | 
						||
 | 
						||
    def clear_foregrounds(self):
 | 
						||
        """清除所有前景图"""
 | 
						||
        if messagebox.askyesno("确认", "确定要清除所有前景图片吗?"):
 | 
						||
            self.project.foregrounds.clear()
 | 
						||
            self.update_image_list()
 | 
						||
            self.clear_property_panel()
 | 
						||
            self.canvas.selected_item = None
 | 
						||
            self.canvas.draw_preview()
 | 
						||
 | 
						||
    def get_canvas(self):
 | 
						||
        """获取画布组件"""
 | 
						||
        return self.canvas
 | 
						||
 | 
						||
    def set_template(self):
 | 
						||
        """设置模板"""
 | 
						||
        dialog = TemplateDialog(self, self.project.template)
 | 
						||
        if dialog.result:
 | 
						||
            if dialog.result["type"] == "preset_loaded":
 | 
						||
                # 直接使用已加载的模板设置,不需要重新创建Template对象
 | 
						||
                # 模板已经在对话框中加载完成,只需确保画布刷新
 | 
						||
                pass
 | 
						||
            elif dialog.result["type"] == "preset":
 | 
						||
                # 确保传递所有参数并创建模板
 | 
						||
                self.project.template = Template(
 | 
						||
                    ratio=tuple(dialog.result["ratio"]),
 | 
						||
                    width_px=dialog.result["width"],
 | 
						||
                    height_px=dialog.result.get("height")
 | 
						||
                )
 | 
						||
                # 设置背景颜色
 | 
						||
                self.project.template.bg_color = dialog.result["bg_color"]
 | 
						||
                # 设置背景图
 | 
						||
                if "bg_image" in dialog.result:
 | 
						||
                    self.project.template.bg_image = dialog.result["bg_image"]
 | 
						||
            else:
 | 
						||
                self.project.template = Template()
 | 
						||
                self.project.template.set_custom_size(
 | 
						||
                    dialog.result["width"],
 | 
						||
                    dialog.result["height"]
 | 
						||
                )
 | 
						||
                # 设置背景颜色
 | 
						||
                self.project.template.bg_color = dialog.result["bg_color"]
 | 
						||
                # 设置背景图
 | 
						||
                if "bg_image" in dialog.result:
 | 
						||
                    self.project.template.bg_image = dialog.result["bg_image"]
 | 
						||
 | 
						||
            # 确保更新画布显示
 | 
						||
            self.canvas.draw_preview()
 | 
						||
 | 
						||
    def batch_export(self):
 | 
						||
        """批量导出"""
 | 
						||
        if not self.project.foregrounds:
 | 
						||
            messagebox.showinfo("提示", "请先添加前景图片")
 | 
						||
            return
 | 
						||
 | 
						||
        # 在导出前,将第一张图片的配置应用到所有隐藏的图片
 | 
						||
        if self.project.foregrounds:
 | 
						||
            first_fg = self.project.foregrounds[0]
 | 
						||
            # 将第一张图片的配置应用到所有隐藏的图片
 | 
						||
            for fg in self.project.foregrounds[1:]:
 | 
						||
                if fg.hidden:
 | 
						||
                    fg.vertices = [tuple(v) for v in first_fg.vertices]
 | 
						||
                    fg.angle = first_fg.angle
 | 
						||
                    fg.mask_shape = first_fg.mask_shape
 | 
						||
 | 
						||
        # 设置默认导出路径为第一个图片所在目录下的output文件夹
 | 
						||
        default_output_dir = None
 | 
						||
        if self.project.foregrounds:
 | 
						||
            first_image_dir = Path(self.project.foregrounds[0].file_path).parent
 | 
						||
            default_output_dir = first_image_dir / "output"
 | 
						||
        
 | 
						||
        dialog = ExportDialog(self, len(self.project.foregrounds))
 | 
						||
        if dialog.result:
 | 
						||
            # 禁用主窗口防止重复操作
 | 
						||
            self.grab_set()
 | 
						||
            try:
 | 
						||
                # 执行导出
 | 
						||
                success = Exporter.export_batch(
 | 
						||
                    self.project,
 | 
						||
                    dialog.output_dir_var.get(),
 | 
						||
                    dialog.filename_var.get(),
 | 
						||
                    dialog.format_var.get(),
 | 
						||
                    dialog.quality_var.get(),
 | 
						||
                    progress_callback=self.update_export_progress
 | 
						||
                )
 | 
						||
 | 
						||
                # 导出完成
 | 
						||
                if success:
 | 
						||
                    messagebox.showinfo("成功", f"已成功导出 {len(self.project.foregrounds)} 张图片")
 | 
						||
                else:
 | 
						||
                    messagebox.showerror("错误", "导出过程中发生错误")
 | 
						||
            finally:
 | 
						||
                # 重新启用主窗口
 | 
						||
                self.grab_release()
 | 
						||
 | 
						||
    def update_export_progress(self, current, total):
 | 
						||
        """更新导出进度(这个方法需要在ExportDialog中实现)"""
 | 
						||
        # 注意:这个方法需要在ExportDialog中调用,而不是直接在这里实现
 | 
						||
        pass
 | 
						||
 | 
						||
    def update_property_panel(self, fg):
 | 
						||
        """更新属性面板"""
 | 
						||
        # 清除现有内容
 | 
						||
        for widget in self.property_frame.winfo_children():
 | 
						||
            widget.destroy()
 | 
						||
 | 
						||
        # 位置设置
 | 
						||
        ttk.Label(self.property_frame, text="位置", font=("Arial", 9, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        pos_frame = ttk.Frame(self.property_frame)
 | 
						||
        pos_frame.pack(fill=tk.X, pady=2)
 | 
						||
 | 
						||
        ttk.Label(pos_frame, text="X:").grid(row=0, column=0, sticky=tk.W)
 | 
						||
        x_var = tk.DoubleVar(value=fg.x)
 | 
						||
        x_entry = ttk.Entry(pos_frame, textvariable=x_var, width=10)
 | 
						||
        x_entry.grid(row=0, column=1, padx=2)
 | 
						||
        x_entry.bind("<FocusOut>", lambda e: self.update_property(fg, "x", x_var.get()))
 | 
						||
 | 
						||
        ttk.Label(pos_frame, text="Y:").grid(row=0, column=2, sticky=tk.W, padx=5)
 | 
						||
        y_var = tk.DoubleVar(value=fg.y)
 | 
						||
        y_entry = ttk.Entry(pos_frame, textvariable=y_var, width=10)
 | 
						||
        y_entry.grid(row=0, column=3, padx=2)
 | 
						||
        y_entry.bind("<FocusOut>", lambda e: self.update_property(fg, "y", y_var.get()))
 | 
						||
 | 
						||
        # 尺寸设置
 | 
						||
        ttk.Label(self.property_frame, text="尺寸", font=("Arial", 9, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        size_frame = ttk.Frame(self.property_frame)
 | 
						||
        size_frame.pack(fill=tk.X, pady=2)
 | 
						||
 | 
						||
        ttk.Label(size_frame, text="宽:").grid(row=0, column=0, sticky=tk.W)
 | 
						||
        w_var = tk.DoubleVar(value=fg.w)
 | 
						||
        w_entry = ttk.Entry(size_frame, textvariable=w_var, width=10)
 | 
						||
        w_entry.grid(row=0, column=1, padx=2)
 | 
						||
        w_entry.bind("<FocusOut>", lambda e: self.update_property(fg, "w", w_var.get()))
 | 
						||
 | 
						||
        ttk.Label(size_frame, text="高:").grid(row=0, column=2, sticky=tk.W, padx=5)
 | 
						||
        h_var = tk.DoubleVar(value=fg.h)
 | 
						||
        h_entry = ttk.Entry(size_frame, textvariable=h_var, width=10)
 | 
						||
        h_entry.grid(row=0, column=3, padx=2)
 | 
						||
        h_entry.bind("<FocusOut>", lambda e: self.update_property(fg, "h", h_var.get()))
 | 
						||
 | 
						||
        # 旋转设置
 | 
						||
        ttk.Label(self.property_frame, text="旋转", font=("Arial", 9, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        rot_frame = ttk.Frame(self.property_frame)
 | 
						||
        rot_frame.pack(fill=tk.X, pady=2)
 | 
						||
 | 
						||
        angle_var = tk.DoubleVar(value=fg.angle)
 | 
						||
        angle_entry = ttk.Entry(rot_frame, textvariable=angle_var, width=10)
 | 
						||
        angle_entry.grid(row=0, column=0, padx=2)
 | 
						||
        ttk.Label(rot_frame, text="度").grid(row=0, column=1, sticky=tk.W)
 | 
						||
        angle_entry.bind("<FocusOut>", lambda e: self.update_property(fg, "angle", angle_var.get()))
 | 
						||
 | 
						||
        # 遮罩设置
 | 
						||
        ttk.Label(self.property_frame, text="遮罩", font=("Arial", 9, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        mask_frame = ttk.Frame(self.property_frame)
 | 
						||
        mask_frame.pack(fill=tk.X, pady=2)
 | 
						||
 | 
						||
        mask_var = tk.StringVar(value=fg.mask_shape)
 | 
						||
        masks = [("矩形", "rect"), ("圆形", "round"), ("心形", "heart"), ("星形", "star")]
 | 
						||
        for i, (text, value) in enumerate(masks):
 | 
						||
            ttk.Radiobutton(
 | 
						||
                mask_frame,
 | 
						||
                text=text,
 | 
						||
                variable=mask_var,
 | 
						||
                value=value,
 | 
						||
                command=lambda: self.update_property(fg, "mask_shape", mask_var.get())
 | 
						||
            ).grid(row=i // 2, column=i % 2, sticky=tk.W, padx=2, pady=2)
 | 
						||
 | 
						||
        # 其他设置
 | 
						||
        ttk.Label(self.property_frame, text="其他", font=("Arial", 9, "bold")).pack(anchor=tk.W, pady=5)
 | 
						||
 | 
						||
        lock_var = tk.BooleanVar(value=fg.locked)
 | 
						||
        ttk.Checkbutton(
 | 
						||
            self.property_frame,
 | 
						||
            text="锁定位置",
 | 
						||
            variable=lock_var,
 | 
						||
            command=lambda: self.update_property(fg, "locked", lock_var.get())
 | 
						||
        ).pack(anchor=tk.W, pady=2)
 | 
						||
 | 
						||
        visible_var = tk.BooleanVar(value=fg.visible)
 | 
						||
        ttk.Checkbutton(
 | 
						||
            self.property_frame,
 | 
						||
            text="显示图片",
 | 
						||
            variable=visible_var,
 | 
						||
            command=lambda: self.update_property(fg, "visible", visible_var.get())
 | 
						||
        ).pack(anchor=tk.W, pady=2)
 | 
						||
 | 
						||
    def update_property(self, fg, prop, value):
 | 
						||
        """更新属性值"""
 | 
						||
        if hasattr(fg, prop):
 | 
						||
            setattr(fg, prop, value)
 | 
						||
            self.canvas.draw_preview()
 | 
						||
 | 
						||
    def clear_property_panel(self):
 | 
						||
        """清除属性面板"""
 | 
						||
        for widget in self.property_frame.winfo_children():
 | 
						||
            widget.destroy()
 | 
						||
 | 
						||
        ttk.Label(self.property_frame, text="未选择图片", foreground="#999").pack(pady=20)
 | 
						||
 | 
						||
    def move_layer(self, direction):
 | 
						||
        """移动图层顺序"""
 | 
						||
        if not self.canvas.selected_item:
 | 
						||
            return
 | 
						||
 | 
						||
        fg = self.canvas.selected_item
 | 
						||
        current_idx = fg.z_index
 | 
						||
        new_idx = current_idx + direction
 | 
						||
 | 
						||
        # 检查边界
 | 
						||
        if new_idx < 0 or new_idx >= len(self.project.foregrounds):
 | 
						||
            return
 | 
						||
 | 
						||
        # 交换z_index
 | 
						||
        for other in self.project.foregrounds:
 | 
						||
            if other.z_index == new_idx:
 | 
						||
                other.z_index = current_idx
 | 
						||
                break
 | 
						||
 | 
						||
        fg.z_index = new_idx
 | 
						||
        self.canvas.draw_preview()
 | 
						||
 | 
						||
    def reset_zoom(self):
 | 
						||
        """重置缩放"""
 | 
						||
        self.project.canvas_zoom = 1.0
 | 
						||
        self.canvas.zoom = 1.0
 | 
						||
        self.canvas.draw_preview()
 | 
						||
 | 
						||
    def new_project(self):
 | 
						||
        """新建项目"""
 | 
						||
        if messagebox.askyesno("确认", "确定要创建新项目吗?当前项目将被清空。"):
 | 
						||
            self.project = Project()
 | 
						||
            self.canvas.project = self.project
 | 
						||
            self.update_image_list()
 | 
						||
            self.clear_property_panel()
 | 
						||
            self.canvas.selected_item = None
 | 
						||
            self.canvas.draw_preview()
 | 
						||
 | 
						||
    def open_project(self):
 | 
						||
        """打开项目"""
 | 
						||
        file_path = filedialog.askopenfilename(
 | 
						||
            filetypes=[("项目文件", "*.json"), ("所有文件", "*.*")]
 | 
						||
        )
 | 
						||
 | 
						||
        if file_path:
 | 
						||
            new_project = Project()
 | 
						||
            if new_project.load(file_path):
 | 
						||
                self.project = new_project
 | 
						||
                self.canvas.project = self.project
 | 
						||
                self.canvas.zoom = self.project.canvas_zoom
 | 
						||
                self.update_image_list()
 | 
						||
                self.clear_property_panel()
 | 
						||
                self.canvas.selected_item = None
 | 
						||
                self.canvas.draw_preview()
 | 
						||
            else:
 | 
						||
                messagebox.showerror("错误", "打开项目失败")
 | 
						||
 | 
						||
    def save_project(self):
 | 
						||
        """保存项目"""
 | 
						||
        if self.project.file_path:
 | 
						||
            if self.project.save():
 | 
						||
                messagebox.showinfo("成功", "项目保存成功")
 | 
						||
            else:
 | 
						||
                messagebox.showerror("错误", "项目保存失败")
 | 
						||
        else:
 | 
						||
            self.save_project_as()
 | 
						||
 | 
						||
    def save_project_as(self):
 | 
						||
        """另存为项目"""
 | 
						||
        file_path = filedialog.asksaveasfilename(
 | 
						||
            defaultextension=".json",
 | 
						||
            filetypes=[("项目文件", "*.json"), ("所有文件", "*.*")]
 | 
						||
        )
 | 
						||
 | 
						||
        if file_path:
 | 
						||
            if self.project.save(file_path):
 | 
						||
                messagebox.showinfo("成功", "项目保存成功")
 | 
						||
            else:
 | 
						||
                messagebox.showerror("错误", "项目保存失败")
 | 
						||
 | 
						||
    def show_about(self):
 | 
						||
        """显示关于对话框"""
 | 
						||
        messagebox.showinfo(
 | 
						||
            "关于",
 | 
						||
            "批量图片布局工具\n"
 | 
						||
            "版本: 1.0\n"
 | 
						||
            "用于批量处理图片布局的工具,可以将多张图片按照相同的布局应用到指定模板中。"
 | 
						||
        ) |