""" 高级样式编辑器模块 提供专业的样式编辑和实时预览功能,让用户能够创建和定制个性化的文档样式。 """ import tkinter as tk from tkinter import ttk, colorchooser, messagebox, simpledialog from typing import Optional, Dict, Any import copy from style_manager import style_manager, DocumentStyle, FontStyle, ParagraphStyle from config import Config def open_advanced_editor(parent, style_name): """ 打开高级样式编辑器 Args: parent: 父窗口 style_name: 要编辑的样式名称 """ if not style_name: messagebox.showwarning("警告", "请先选择一个样式") return None # 获取样式 original_style = style_manager.get_style(style_name) if not original_style: messagebox.showerror("错误", f"找不到样式 '{style_name}'") return None editor = AdvancedStyleEditor(parent, original_style) return editor class AdvancedStyleEditor: """ 高级样式编辑器类 """ def __init__(self, parent, original_style): self.parent = parent self.original_style = original_style self.current_style = copy.deepcopy(original_style) # 确保样式对象完整 self._ensure_style_completeness() # 创建编辑窗口 self.window = tk.Toplevel(parent) self.window.title(f'高级样式编辑 - {self.current_style.name}') self.window.geometry('1000x700') self.window.transient(parent) self.window.grab_set() # 样式变量 self.style_vars = {} self.heading_vars = {} self._create_interface() self._bind_events() self._update_preview() def _ensure_style_completeness(self): """确保样式对象的完整性""" if not self.current_style.body_font: self.current_style.body_font = FontStyle() if not self.current_style.body_paragraph: self.current_style.body_paragraph = ParagraphStyle() if not self.current_style.heading_styles: self.current_style.heading_styles = {} # 确保至少有3级标题 - 使用正确的HeadingStyle对象 from style_manager import HeadingStyle for level in range(1, 4): if level not in self.current_style.heading_styles: font_size = max(20 - level * 2, 12) self.current_style.heading_styles[level] = HeadingStyle( font=FontStyle(name="微软雅黑", size=font_size, bold=True), paragraph=ParagraphStyle(line_spacing=1.3, space_before=12, space_after=6), outline_level=level ) def _create_interface(self): """创建界面""" # 创建主布局 main_frame = ttk.Frame(self.window) main_frame.pack(fill='both', expand=True, padx=10, pady=10) # 左侧编辑区域 edit_frame = ttk.Frame(main_frame) edit_frame.pack(side='left', fill='both', expand=True, padx=(0, 10)) # 右侧预览区域 preview_frame = ttk.LabelFrame(main_frame, text='实时预览', padding="10") preview_frame.pack(side='right', fill='both', expand=True) self._create_edit_area(edit_frame) self._create_preview_area(preview_frame) self._create_bottom_buttons() def _create_edit_area(self, parent): """创建编辑区域""" # 创建笔记本控件 notebook = ttk.Notebook(parent) notebook.pack(fill='both', expand=True) # 基本信息选项卡 basic_frame = ttk.Frame(notebook) notebook.add(basic_frame, text='基本信息') self._create_basic_tab(basic_frame) # 正文样式选项卡 body_frame = ttk.Frame(notebook) notebook.add(body_frame, text='正文样式') self._create_body_tab(body_frame) # 标题样式选项卡 heading_frame = ttk.Frame(notebook) notebook.add(heading_frame, text='标题样式') self._create_heading_tab(heading_frame) def _create_basic_tab(self, parent): """创建基本信息选项卡""" # 样式名称 name_frame = ttk.Frame(parent) name_frame.pack(fill='x', padx=10, pady=5) ttk.Label(name_frame, text='样式名称:', width=12).pack(side='left') self.name_var = tk.StringVar(value=self.current_style.name) name_entry = ttk.Entry(name_frame, textvariable=self.name_var) name_entry.pack(side='left', fill='x', expand=True, padx=(5, 0)) # 样式描述 desc_frame = ttk.Frame(parent) desc_frame.pack(fill='x', padx=10, pady=5) ttk.Label(desc_frame, text='描述:', width=12).pack(side='left') self.desc_var = tk.StringVar(value=self.current_style.description) desc_entry = ttk.Entry(desc_frame, textvariable=self.desc_var) desc_entry.pack(side='left', fill='x', expand=True, padx=(5, 0)) def _create_body_tab(self, parent): """创建正文样式选项卡""" # 正文字体设置 font_frame = ttk.LabelFrame(parent, text='正文字体', padding="10") font_frame.pack(fill='x', padx=10, pady=5) # 字体名称 name_frame = ttk.Frame(font_frame) name_frame.pack(fill='x', pady=2) ttk.Label(name_frame, text='字体:', width=10).pack(side='left') self.style_vars['font_name'] = tk.StringVar(value=self.current_style.body_font.name) font_combo = ttk.Combobox(name_frame, textvariable=self.style_vars['font_name'], width=15, values=['宋体', '微软雅黑', '黑体', '楷体', '华文细黑', 'Arial', 'Times New Roman']) font_combo.pack(side='left', padx=(5, 10)) # 字号 ttk.Label(name_frame, text='大小:', width=5).pack(side='left') self.style_vars['font_size'] = tk.IntVar(value=self.current_style.body_font.size) size_spin = ttk.Spinbox(name_frame, from_=8, to=72, textvariable=self.style_vars['font_size'], width=5) size_spin.pack(side='left', padx=(5, 0)) # 字体样式 style_frame = ttk.Frame(font_frame) style_frame.pack(fill='x', pady=2) self.style_vars['font_bold'] = tk.BooleanVar(value=self.current_style.body_font.bold) ttk.Checkbutton(style_frame, text='粗体', variable=self.style_vars['font_bold']).pack(side='left', padx=(0, 10)) self.style_vars['font_italic'] = tk.BooleanVar(value=self.current_style.body_font.italic) ttk.Checkbutton(style_frame, text='斜体', variable=self.style_vars['font_italic']).pack(side='left', padx=(0, 10)) # 字体颜色 ttk.Label(style_frame, text='颜色:', width=5).pack(side='left') self.style_vars['font_color'] = tk.StringVar(value=self.current_style.body_font.color) self.color_label = tk.Label(style_frame, text=' ', bg=self.style_vars['font_color'].get(), relief='solid', width=3) self.color_label.pack(side='left', padx=(5, 5)) ttk.Button(style_frame, text='选择', command=self._choose_font_color).pack(side='left') # 正文段落设置 para_frame = ttk.LabelFrame(parent, text='正文段落', padding="10") para_frame.pack(fill='x', padx=10, pady=5) # 行距 spacing_frame = ttk.Frame(para_frame) spacing_frame.pack(fill='x', pady=2) ttk.Label(spacing_frame, text='行距:', width=10).pack(side='left') self.style_vars['line_spacing'] = tk.DoubleVar(value=self.current_style.body_paragraph.line_spacing) line_spacing_spin = ttk.Spinbox(spacing_frame, from_=0.5, to=3.0, increment=0.1, textvariable=self.style_vars['line_spacing'], width=8) line_spacing_spin.pack(side='left', padx=(5, 10)) # 首行缩进 ttk.Label(spacing_frame, text='首行缩进:', width=10).pack(side='left') self.style_vars['indent'] = tk.DoubleVar(value=self.current_style.body_paragraph.first_line_indent) indent_spin = ttk.Spinbox(spacing_frame, from_=0.0, to=10.0, increment=0.5, textvariable=self.style_vars['indent'], width=8) indent_spin.pack(side='left', padx=(5, 0)) # 段间距 margin_frame = ttk.Frame(para_frame) margin_frame.pack(fill='x', pady=2) ttk.Label(margin_frame, text='段前:', width=10).pack(side='left') self.style_vars['space_before'] = tk.IntVar(value=self.current_style.body_paragraph.space_before) before_spin = ttk.Spinbox(margin_frame, from_=0, to=50, textvariable=self.style_vars['space_before'], width=8) before_spin.pack(side='left', padx=(5, 10)) ttk.Label(margin_frame, text='段后:', width=10).pack(side='left') self.style_vars['space_after'] = tk.IntVar(value=self.current_style.body_paragraph.space_after) after_spin = ttk.Spinbox(margin_frame, from_=0, to=50, textvariable=self.style_vars['space_after'], width=8) after_spin.pack(side='left', padx=(5, 0)) def _create_heading_tab(self, parent): """创建标题样式选项卡""" # 创建滚动区域 canvas = tk.Canvas(parent) scrollbar = ttk.Scrollbar(parent, orient="vertical", command=canvas.yview) scrollable_frame = ttk.Frame(canvas) scrollable_frame.bind( "", lambda e: canvas.configure(scrollregion=canvas.bbox("all")) ) canvas.create_window((0, 0), window=scrollable_frame, anchor="nw") canvas.configure(yscrollcommand=scrollbar.set) # 为每个标题级别创建控件 for level in range(1, 4): level_frame = ttk.LabelFrame(scrollable_frame, text=f'{level}级标题', padding="10") level_frame.pack(fill='x', padx=10, pady=5) heading_style = self.current_style.heading_styles.get(level) if not heading_style: continue # 标题字体名称和大小 h_name_frame = ttk.Frame(level_frame) h_name_frame.pack(fill='x', pady=2) ttk.Label(h_name_frame, text='字体:', width=8).pack(side='left') h_name_var = tk.StringVar(value=heading_style.font.name) h_combo = ttk.Combobox(h_name_frame, textvariable=h_name_var, width=12, values=['宋体', '微软雅黑', '黑体', '楷体']) h_combo.pack(side='left', padx=(5, 10)) ttk.Label(h_name_frame, text='大小:', width=5).pack(side='left') h_size_var = tk.IntVar(value=heading_style.font.size) h_size_spin = ttk.Spinbox(h_name_frame, from_=8, to=72, textvariable=h_size_var, width=5) h_size_spin.pack(side='left', padx=(5, 0)) # 标题颜色 h_color_frame = ttk.Frame(level_frame) h_color_frame.pack(fill='x', pady=2) ttk.Label(h_color_frame, text='颜色:', width=8).pack(side='left') h_color_var = tk.StringVar(value=heading_style.font.color) h_color_label = tk.Label(h_color_frame, text=' ', bg=h_color_var.get(), relief='solid', width=3) h_color_label.pack(side='left', padx=(5, 5)) def make_choose_heading_color(var, label): def choose_color(): color = colorchooser.askcolor(color=var.get())[1] if color: var.set(color) label.config(bg=color) self._update_preview() return choose_color ttk.Button(h_color_frame, text='选择', command=make_choose_heading_color(h_color_var, h_color_label)).pack(side='left') self.heading_vars[level] = { 'name': h_name_var, 'size': h_size_var, 'color': h_color_var } canvas.pack(side="left", fill="both", expand=True) scrollbar.pack(side="right", fill="y") def _create_preview_area(self, parent): """创建预览区域""" # 创建预览容器 preview_container = ttk.Frame(parent) preview_container.pack(fill='both', expand=True) # 预览模式选择 mode_frame = ttk.Frame(preview_container) mode_frame.pack(fill='x', pady=(0, 5)) self.preview_mode = tk.StringVar(value="rich") ttk.Radiobutton(mode_frame, text="丰富预览", variable=self.preview_mode, value="rich", command=self._update_preview).pack(side='left') ttk.Radiobutton(mode_frame, text="文本预览", variable=self.preview_mode, value="text", command=self._update_preview).pack(side='left', padx=(10, 0)) # 预览文本区域 text_frame = ttk.Frame(preview_container) text_frame.pack(fill='both', expand=True) self.preview_text = tk.Text(text_frame, wrap=tk.WORD, state='disabled', font=('微软雅黑', 12), bg='white') self.preview_text.pack(side='left', fill='both', expand=True) # 滚动条 preview_scroll = ttk.Scrollbar(text_frame, orient="vertical", command=self.preview_text.yview) preview_scroll.pack(side="right", fill="y") self.preview_text.configure(yscrollcommand=preview_scroll.set) # 初始化文本标签 self._init_text_tags() def _create_bottom_buttons(self): """创建底部按钮""" button_frame = ttk.Frame(self.window) button_frame.pack(fill='x', padx=10, pady=10) ttk.Button(button_frame, text='保存样式', command=self._save_style).pack(side='left', padx=5) ttk.Button(button_frame, text='另存为', command=self._save_as_style).pack(side='left', padx=5) ttk.Button(button_frame, text='重置', command=self._reset_style).pack(side='left', padx=5) ttk.Button(button_frame, text='关闭', command=self.window.destroy).pack(side='right', padx=5) ttk.Button(button_frame, text='应用并关闭', command=self._apply_and_close).pack(side='right', padx=5) def _choose_font_color(self): """选择字体颜色""" color = colorchooser.askcolor(color=self.style_vars['font_color'].get())[1] if color: self.style_vars['font_color'].set(color) self.color_label.config(bg=color) self._update_preview() # 立即更新预览 def _bind_events(self): """绑定事件""" # 绑定所有变量的变化事件 for var in self.style_vars.values(): if hasattr(var, 'trace'): var.trace('w', lambda *args: self._update_preview()) for level_vars in self.heading_vars.values(): for var in level_vars.values(): if hasattr(var, 'trace'): var.trace('w', lambda *args: self._update_preview()) # 基本信息变量 self.name_var.trace('w', lambda *args: self._update_preview()) self.desc_var.trace('w', lambda *args: self._update_preview()) # 预览模式变化事件(在_create_preview_area中已绑定) def _update_current_style(self): """更新当前样式对象""" # 更新基本信息 self.current_style.name = self.name_var.get() self.current_style.description = self.desc_var.get() # 更新正文字体 self.current_style.body_font.name = self.style_vars['font_name'].get() self.current_style.body_font.size = self.style_vars['font_size'].get() self.current_style.body_font.bold = self.style_vars['font_bold'].get() self.current_style.body_font.italic = self.style_vars['font_italic'].get() self.current_style.body_font.color = self.style_vars['font_color'].get() # 更新正文段落 self.current_style.body_paragraph.line_spacing = self.style_vars['line_spacing'].get() self.current_style.body_paragraph.first_line_indent = self.style_vars['indent'].get() self.current_style.body_paragraph.space_before = self.style_vars['space_before'].get() self.current_style.body_paragraph.space_after = self.style_vars['space_after'].get() # 更新标题样式 for level, vars_dict in self.heading_vars.items(): if level in self.current_style.heading_styles: heading_style = self.current_style.heading_styles[level] # 现在heading_style应该始终是HeadingStyle对象 heading_style.font.name = vars_dict['name'].get() heading_style.font.size = vars_dict['size'].get() heading_style.font.color = vars_dict['color'].get() def _init_text_tags(self): """初始化文本标签样式""" # 配置各种文本标签 self.preview_text.tag_configure('title', font=('微软雅黑', 14, 'bold'), foreground='#2E86AB') self.preview_text.tag_configure('separator', foreground='#666666') self.preview_text.tag_configure('heading1', font=('黑体', 18, 'bold'), foreground='#E74C3C') self.preview_text.tag_configure('heading2', font=('微软雅黑', 16, 'bold'), foreground='#3498DB') self.preview_text.tag_configure('heading3', font=('微软雅黑', 14, 'bold'), foreground='#2ECC71') self.preview_text.tag_configure('body', font=('宋体', 12), foreground='#333333') self.preview_text.tag_configure('bold', font=('微软雅黑', 12, 'bold')) self.preview_text.tag_configure('italic', font=('微软雅黑', 12, 'italic')) self.preview_text.tag_configure('quote', font=('楷体', 11, 'italic'), foreground='#7F8C8D', background='#F8F9FA') self.preview_text.tag_configure('code', font=('Courier New', 10), background='#F4F4F4', foreground='#C0392B') self.preview_text.tag_configure('info', font=('微软雅黑', 10), foreground='#95A5A6') def _update_preview(self): """更新预览""" self._update_current_style() # 清空预览区域 self.preview_text.config(state='normal') self.preview_text.delete(1.0, tk.END) if self.preview_mode.get() == "rich": self._create_rich_preview() else: self._create_text_preview() self.preview_text.config(state='disabled') def _create_rich_preview(self): """创建丰富的样式预览""" # 样式标题 self.preview_text.insert(tk.END, f"样式预览:{self.current_style.name}\n", 'title') self.preview_text.insert(tk.END, f"描述:{self.current_style.description}\n\n", 'info') # 分隔线 self.preview_text.insert(tk.END, "═" * 50 + "\n\n", 'separator') # 动态更新标题样式标签 self._update_heading_tags() # 一级标题预览 self.preview_text.insert(tk.END, "一级标题样式预览\n\n", 'heading1_live') # 二级标题预览 self.preview_text.insert(tk.END, "二级标题样式预览\n\n", 'heading2_live') # 三级标题预览 self.preview_text.insert(tk.END, "三级标题样式预览\n\n", 'heading3_live') # 动态更新正文样式标签 self._update_body_tag() # 正文段落预览 body_text = f"""正文段落样式预览: 这是正文内容的示例,展示当前选择的字体和格式效果。 字体:{self.current_style.body_font.name} {self.current_style.body_font.size}pt 行距:{self.current_style.body_paragraph.line_spacing}倍 首行缩进:{self.current_style.body_paragraph.first_line_indent}字符 段前间距:{self.current_style.body_paragraph.space_before}pt 段后间距:{self.current_style.body_paragraph.space_after}pt 这是包含""" self.preview_text.insert(tk.END, body_text, 'body_live') # 粗体和斜体示例 self.preview_text.insert(tk.END, "粗体文字", 'bold_live') self.preview_text.insert(tk.END, "和", 'body_live') self.preview_text.insert(tk.END, "斜体文字", 'italic_live') self.preview_text.insert(tk.END, "的段落示例。\n\n", 'body_live') # 引用块示例 self.preview_text.insert(tk.END, "引用块样式预览:\n", 'body_live') self.preview_text.insert(tk.END, " 这是引用块的示例内容,展示引用文字的特殊格式效果。\n\n", 'quote') # 代码块示例 self.preview_text.insert(tk.END, "代码块样式预览:\n", 'body_live') self.preview_text.insert(tk.END, ' print("Hello, World!")\n # 这是代码块的示例\n\n', 'code') # 列表示例 self.preview_text.insert(tk.END, "列表样式预览:\n", 'body_live') self.preview_text.insert(tk.END, "• 无序列表项目1\n• 无序列表项目2\n• 无序列表项目3\n\n", 'body_live') self.preview_text.insert(tk.END, "1. 有序列表项目1\n2. 有序列表项目2\n3. 有序列表项目3\n\n", 'body_live') # 样式详情 details = f"""当前样式设置详情: • 字体名称:{self.current_style.body_font.name} • 字体大小:{self.current_style.body_font.size}pt • 字体颜色:{self.current_style.body_font.color} • 是否粗体:{'是' if self.current_style.body_font.bold else '否'} • 是否斜体:{'是' if self.current_style.body_font.italic else '否'} • 行距:{self.current_style.body_paragraph.line_spacing}倍 • 首行缩进:{self.current_style.body_paragraph.first_line_indent}字符""" self.preview_text.insert(tk.END, details, 'info') def _create_text_preview(self): """创建简单文本预览""" preview_content = f"""样式预览:{self.current_style.name} 描述:{self.current_style.description} ───────────────────────────── # 一级标题样式预览 ## 二级标题样式预览 ### 三级标题样式预览 正文段落样式预览: 这是正文内容的示例。使用{self.current_style.body_font.name} {self.current_style.body_font.size}pt字体, 行距为{self.current_style.body_paragraph.line_spacing}倍,首行缩进{self.current_style.body_paragraph.first_line_indent}字符。 段前间距{self.current_style.body_paragraph.space_before}pt,段后间距{self.current_style.body_paragraph.space_after}pt。 这是包含**粗体文字**和*斜体文字*的段落,用于展示内联样式效果。 引用块样式预览: > 这是引用块的示例内容,展示引用文字的特殊格式效果。 代码块样式预览: ``` print("Hello, World!") # 这是代码块的示例 ``` 列表样式预览: • 无序列表项目1 • 无序列表项目2 • 无序列表项目3 1. 有序列表项目1 2. 有序列表项目2 3. 有序列表项目3 字体设置详情: • 字体名称:{self.current_style.body_font.name} • 字体大小:{self.current_style.body_font.size}pt • 字体颜色:{self.current_style.body_font.color} • 粗体:{'是' if self.current_style.body_font.bold else '否'} • 斜体:{'是' if self.current_style.body_font.italic else '否'} """ self.preview_text.insert(tk.END, preview_content) def _update_heading_tags(self): """动态更新标题样式标签""" for level in range(1, 4): tag_name = f'heading{level}_live' if level in self.current_style.heading_styles: heading_style = self.current_style.heading_styles[level] font_name = heading_style.font.name font_size = heading_style.font.size font_weight = 'bold' if heading_style.font.bold else 'normal' font_color = heading_style.font.color self.preview_text.tag_configure(tag_name, font=(font_name, font_size, font_weight), foreground=font_color) def _update_body_tag(self): """动态更新正文样式标签""" font_name = self.current_style.body_font.name font_size = self.current_style.body_font.size font_weight = 'bold' if self.current_style.body_font.bold else 'normal' font_slant = 'italic' if self.current_style.body_font.italic else 'roman' font_color = self.current_style.body_font.color # 更新正文标签 self.preview_text.tag_configure('body_live', font=(font_name, font_size, font_weight, font_slant), foreground=font_color) # 更新粗体标签 self.preview_text.tag_configure('bold_live', font=(font_name, font_size, 'bold', font_slant), foreground=font_color) # 更新斜体标签 self.preview_text.tag_configure('italic_live', font=(font_name, font_size, font_weight, 'italic'), foreground=font_color) def _save_style(self): """保存样式""" try: if not self.current_style.name.strip(): messagebox.showerror("错误", "请输入样式名称") return self._update_current_style() # 尝试更新现有样式,如果失败则创建新样式 if self.current_style.name in style_manager.custom_styles: success = style_manager.update_custom_style(self.current_style) else: success = style_manager.create_custom_style(self.current_style) if success: messagebox.showinfo("成功", f"样式 '{self.current_style.name}' 保存成功") config.current_style = self.current_style.name else: if self.current_style.name in style_manager.builtin_styles: messagebox.showerror("错误", "不能覆盖内置样式!请使用不同的样式名称或'另存为'功能。") else: messagebox.showerror("保存失败", "保存样式失败!请检查文件权限或磁盘空间。") except Exception as e: messagebox.showerror("错误", f"保存过程中发生异常: {str(e)}") def _save_as_style(self): """另存为新样式""" try: new_name = simpledialog.askstring("另存为", "请输入新样式名称:") if not new_name: return if new_name in style_manager.get_style_names(): if new_name in style_manager.builtin_styles: messagebox.showerror("名称冲突", "不能使用内置样式名称!请使用不同的样式名称。") else: messagebox.showerror("名称已存在", f"样式名称 '{new_name}' 已经存在!请使用不同的名称。") return # 修改样式信息 self.current_style.name = new_name self.current_style.description = "基于 " + self.original_style.name + " 定制" self._update_current_style() if style_manager.create_custom_style(self.current_style): messagebox.showinfo("创建成功", f"样式 '{new_name}' 已成功创建!") config_manager = Config() config_manager.set('current_style', new_name) self.window.destroy() else: messagebox.showerror("创建失败", "创建样式失败!请检查文件权限或磁盘空间。") except Exception as e: messagebox.showerror("错误", f"另存为过程中发生异常: {str(e)}") def _reset_style(self): """重置样式""" if messagebox.askyesno("确认重置", "确定要重置为原始样式吗?"): self.current_style = copy.deepcopy(self.original_style) self._ensure_style_completeness() # 重新创建界面(简单的重置方法) self.window.destroy() self.__init__(self.parent, self.original_style) def _apply_and_close(self): """应用并关闭""" self._save_style() self.window.destroy()