#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ GUI主界面模块 FreeNote应用的图形用户界面 """ import tkinter as tk from tkinter import ttk, messagebox, filedialog import datetime from mod_conf import get_config_manager, get_setting, set_setting from mod_tpl import get_template_help_text from mod_file import save_note_content, FileSaveError class FreeNoteApp: def __init__(self, root): self.root = root self.root.title("FreeNote-随手记") self.root.geometry("1000x800") # 文本修改状态标志 self.text_modified = False # 获取配置管理器 self.config_manager = get_config_manager() # 创建界面 self.create_widgets() # 绑定快捷键 self.bind_shortcuts() def create_widgets(self): """创建界面组件""" # 初始化界面变量 self.init_ui_variables() # 创建主框架 main_frame = ttk.Frame(self.root) main_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) # 配置Grid布局权重 main_frame.grid_rowconfigure(0, weight=0) # 工具栏:固定高度 main_frame.grid_rowconfigure(1, weight=1) # 文本区域:可伸缩 main_frame.grid_rowconfigure(2, weight=0) # 状态栏:固定高度 main_frame.grid_columnconfigure(0, weight=1) # 列可伸缩 # 工具栏 self.create_toolbar(main_frame) # 文本编辑区域 self.create_text_area(main_frame) # 状态栏 self.create_status_bar(main_frame) def init_ui_variables(self): """初始化界面变量""" # 目录变量 self.folder_var = tk.StringVar( value=get_setting('folder')) # 文件名变量 self.filename_var = tk.StringVar( value=get_setting('filename')) # 扩展名变量 self.ext_var = tk.StringVar( value=get_setting('ext')) def create_toolbar(self, parent): """创建工具栏""" toolbar = ttk.Frame(parent) toolbar.grid(row=0, column=0, sticky=tk.EW, pady=(0, 5)) # 配置grid权重,使目录框和文件名框可以缩放 toolbar.grid_columnconfigure(0, weight=1) # 目录框列 toolbar.grid_columnconfigure(2, weight=2) # 文件名框列,权重为目录框的2倍 # 目录框 self.dir_entry = ttk.Entry(toolbar, textvariable=self.folder_var) self.dir_entry.grid(row=0, column=0, sticky=tk.EW, padx=(0, 5)) # 绑定失去焦点事件 self.dir_entry.bind('', self.on_folder_focus_out) self.dir_entry.bind('', self.on_folder_focus_out) # 选择目录按钮 browse_btn = ttk.Button(toolbar, text="选择", width=4, command=self.browse_folder) browse_btn.grid(row=0, column=1, padx=(0, 10)) # 文件名框 self.filename_entry = ttk.Entry( toolbar, textvariable=self.filename_var) self.filename_entry.grid(row=0, column=2, sticky=tk.EW, padx=(0, 5)) # 绑定失去焦点事件 self.filename_entry.bind('', self.on_filename_focus_out) self.filename_entry.bind('', self.on_filename_focus_out) # 模板按钮 template_btn = ttk.Button(toolbar, text="模板", width=4, command=self.show_template_help) template_btn.grid(row=0, column=3, padx=(0, 10)) # 扩展名选择框 ext_combo = ttk.Combobox(toolbar, textvariable=self.ext_var, values=['.txt', '.md'], width=8, state='readonly') ext_combo.bind('<>', self.on_ext_change) ext_combo.grid(row=0, column=4, padx=(0, 10)) # 保存按钮 save_btn = ttk.Button(toolbar, text="保存", width=4, command=self.save_note) save_btn.grid(row=0, column=5) def create_text_area(self, parent): """创建文本编辑区域""" text_frame = ttk.Frame(parent) text_frame.grid(row=1, column=0, sticky=tk.NSEW) # 配置文本框架的布局权重 text_frame.grid_rowconfigure(0, weight=1) text_frame.grid_columnconfigure(0, weight=1) # 文本框 self.text_area = tk.Text(text_frame, wrap=tk.WORD) self.text_area.grid(row=0, column=0, sticky=tk.NSEW) # 绑定文本修改事件 self.text_area.bind('', self.on_text_modified) self.text_area.bind('', self.on_text_modified) self.text_area.bind('', self.on_text_modified) self.text_area.bind('', self.on_text_modified) # 粘贴 self.text_area.bind('', self.on_text_modified) # 剪切 # 滚动条 scrollbar = ttk.Scrollbar(text_frame, orient=tk.VERTICAL, command=self.text_area.yview) scrollbar.grid(row=0, column=1, sticky=tk.NS) self.text_area.config(yscrollcommand=scrollbar.set) def create_status_bar(self, parent): """创建状态栏""" status_frame = ttk.Frame(parent) status_frame.grid(row=2, column=0, sticky=tk.EW, pady=(5, 0)) # 配置状态栏布局权重 status_frame.grid_columnconfigure(0, weight=1) # 左侧状态消息可伸缩 status_frame.grid_columnconfigure(1, weight=0) # 右侧尺寸信息固定 # 主状态消息 self.status_var = tk.StringVar() status_label = ttk.Label(status_frame, textvariable=self.status_var) status_label.grid(row=0, column=0, sticky=tk.W) # 窗口尺寸信息 self.size_var = tk.StringVar() size_label = ttk.Label(status_frame, textvariable=self.size_var) size_label.grid(row=0, column=1, sticky=tk.E) # 初始化窗口尺寸显示 self.update_window_size() # 绑定窗口resize事件 self.root.bind('', self.on_window_resize) def bind_shortcuts(self): """绑定快捷键""" self.root.bind('', lambda e: self.save_note()) self.root.bind('', lambda e: self.clear_text()) def on_text_modified(self, event=None): """文本内容修改时的回调""" # 使用after方法延迟检查,确保文本已经更新 self.root.after_idle(self.check_text_modified) def check_text_modified(self): """检查文本是否被修改""" current_content = self.text_area.get("1.0", tk.END).strip() if current_content and not self.text_modified: self.text_modified = True self.update_title() elif not current_content and self.text_modified: self.text_modified = False self.update_title() def update_title(self): """更新窗口标题以显示修改状态""" base_title = "FreeNote - 随手记录" if self.text_modified: self.root.title(f"{base_title} *") else: self.root.title(base_title) def reset_modified_flag(self): """重置修改标志""" self.text_modified = False self.update_title() def clear_text(self): """清空文本框内容""" if self.text_modified: # 如果文本已修改,询问用户是否确认清空 if not messagebox.askyesno("确认", "文本内容已修改但未保存,确定要清空吗?"): return # 清空文本框 self.text_area.delete("1.0", tk.END) # 重置修改标志 self.reset_modified_flag() # 更新状态栏 self.update_status("文本已清空") def on_folder_focus_out(self, event=None): """目录框失去焦点时保存配置""" new_val = self.folder_var.get().strip() old_val = get_setting('folder') if new_val and new_val != old_val: # 更新配置 set_setting('folder', new_val) self.update_status(f"保存文件夹{new_val}") def on_filename_focus_out(self, event=None): """文件名框失去焦点时保存配置""" new_val = self.filename_var.get().strip() old_val = get_setting('filename') if new_val and new_val != old_val: # 更新配置 set_setting('filename', new_val) self.update_status(f"文件名{new_val}") def on_ext_change(self, event=None): """扩展名变化时保存配置""" new_val = self.ext_var.get().strip() old_val = get_setting('ext') if new_val and new_val != old_val: # 更新配置 set_setting('ext', new_val) self.update_status(f"扩展名已更改为{new_val}") def browse_folder(self): """浏览选择目录""" folder = filedialog.askdirectory(initialdir=self.folder_var.get()) if folder: self.folder_var.set(folder) # 触发失去焦点事件来保存配置 self.on_folder_focus_out() def save_note(self): """保存笔记""" # 检查文本是否被修改 if not self.text_modified: messagebox.showinfo("提示", "文本内容未修改,无需保存。") return content = self.text_area.get("1.0", tk.END) try: # 获取参数 filename_template = self.filename_var.get().strip() folder = self.folder_var.get().strip() ext = self.ext_var.get().strip() # 定义覆盖确认回调 def overwrite_callback(filepath): return messagebox.askyesno("确认", f"文件 {filepath} 已存在,是否覆盖?") # 保存文件 filepath = save_note_content( content, filename_template, folder, ext, overwrite_callback=overwrite_callback ) # 更新状态 self.update_status(f"已保存: {filepath}") # 重置修改标志 self.reset_modified_flag() except FileSaveError as e: messagebox.showerror("错误", str(e)) except Exception as e: messagebox.showerror("错误", f"保存失败: {str(e)}") def update_status(self, message): """更新状态栏""" status_text = f"{datetime.datetime.now().strftime('%H:%M:%S')} - {message}" self.status_var.set(status_text) def update_window_size(self): """更新窗口尺寸显示""" width = self.root.winfo_width() height = self.root.winfo_height() self.size_var.set(f"{width} x {height}") def on_window_resize(self, event=None): """窗口尺寸变化事件处理""" # 只响应主窗口的resize事件,忽略子组件的事件 if event and event.widget == self.root: self.update_window_size() def show_template_help(self): """显示模板变量帮助""" help_text = get_template_help_text() messagebox.showinfo("文件名模板帮助", help_text) def create_app(): """创建并返回应用程序实例""" root = tk.Tk() app = FreeNoteApp(root) return root, app