python语言word文档编辑器软件打包保存程序代码1-QZQ-2025-8-9 - 详解

# 需安装的库:pip install pillow pygame python-docx pywin32 opencv-python
import tkinter as tk
from tkinter import filedialog, messagebox, scrolledtext, ttk
from PIL import Image, ImageTk
import os
import io
import zipfile
import json
import pygame
from pygame import mixer
import tempfile
import subprocess
import sys
import datetime
from docx import Document
from docx.shared import Inches
import pythoncom
import win32com.client as win32
import pickle
import base64
import zlib
from io import BytesIO
import re
import shutil # 添加这行到文件顶部的其他import语句中
import traceback
class WordEditor
:
def __init__(self, root):
self.root = root
self.root.title("Word文档编辑器")
self.root.geometry("1200x800")
self.root.configure(bg="#f0f0f0")
# 初始化日志系统
self.log_file = os.path.join(tempfile.gettempdir(),
f"word_editor_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
}.txt")
self.log("程序启动")
# 初始化音频播放器
pygame.init()
mixer.init()
self.current_audio = None # 当前播放的音频
# 当前文档路径
self.current_file = None
self.unsaved_changes = False
# 临时文件夹用于存储媒体文件
self.temp_media_dir = tempfile.mkdtemp(prefix="word_editor_media_")
self.log(f"创建临时目录: {self.temp_media_dir
}")
# 存储插入的多媒体内容
self.inserted_media = [] # 格式: {"id": 唯一ID, "type": 类型, "path": 路径, "name": 文件名, "image_obj": Tk图片对象}
self.media_tags = {
} # 关联Text组件的tag和媒体ID,值为(start, end)元组
# 创建菜单栏
self.create_menu()
# 创建工具栏
self.create_toolbar()
# 创建状态栏
self.status_var = tk.StringVar()
self.status_var.set(f"就绪 - 日志文件: {os.path.basename(self.log_file)
}")
self.status_bar = tk.Label(root, textvariable=self.status_var, bd=1, relief=tk.SUNKEN, anchor=tk.W,
bg="#e0e0e0")
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 创建主编辑区域
self.create_editor()
# 设置主题
self.set_theme("light")
# 设置关闭事件
self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
# 修改临时目录创建方式
self.temp_media_dir = os.path.join(tempfile.gettempdir(), "word_editor_media")
os.makedirs(self.temp_media_dir, exist_ok=True)
self.log(f"创建媒体临时目录: {self.temp_media_dir
}")
def log(self, message):
"""记录日志到文件"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
with open(self.log_file, "a", encoding="utf-8") as f:
f.write(f"[{timestamp
}] {message
}\n")
def create_menu(self):
menu_bar = tk.Menu(self.root)
# 文件菜单
file_menu = tk.Menu(menu_bar, tearoff=0)
file_menu.add_command(label="新建", command=self.new_file, accelerator="Ctrl+N")
file_menu.add_command(label="打开", command=self.open_file, accelerator="Ctrl+O")
file_menu.add_command(label="保存", command=self.save_file, accelerator="Ctrl+S")
file_menu.add_command(label="另存为", command=self.save_as, accelerator="Ctrl+Shift+S")
file_menu.add_command(label="打包保存", command=self.save_packaged, accelerator="Ctrl+Shift+P")
file_menu.add_separator()
file_menu.add_command(label="打印", command=self.print_document, accelerator="Ctrl+P")
file_menu.add_separator()
file_menu.add_command(label="关闭", command=self.close_file, accelerator="Ctrl+W")
file_menu.add_command(label="退出", command=self.on_closing, accelerator="Alt+F4")
menu_bar.add_cascade(label="文件", menu=file_menu)
# 编辑菜单
edit_menu = tk.Menu(menu_bar, tearoff=0)
edit_menu.add_command(label="撤销", command=self.undo, accelerator="Ctrl+Z")
edit_menu.add_command(label="重做", command=self.redo, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="剪切", command=self.cut, accelerator="Ctrl+X")
edit_menu.add_command(label="复制", command=self.copy, accelerator="Ctrl+C")
edit_menu.add_command(label="粘贴", command=self.paste, accelerator="Ctrl+V")
# 添加删除选项
edit_menu.add_command(label="删除", command=self.delete_selection, accelerator="Delete")
edit_menu.add_command(label="删除选中媒体", command=self.delete_selected_media)
edit_menu.add_separator()
edit_menu.add_command(label="全选", command=self.select_all, accelerator="Ctrl+A")
edit_menu.add_command(label="查找", command=self.find_text, accelerator="Ctrl+F")
edit_menu.add_command(label="替换", command=self.replace_text, accelerator="Ctrl+H")
menu_bar.add_cascade(label="编辑", menu=edit_menu)
# 插入菜单
insert_menu = tk.Menu(menu_bar, tearoff=0)
insert_menu.add_command(label="图片", command=self.insert_image)
insert_menu.add_command(label="音频", command=self.insert_audio)
insert_menu.add_command(label="视频", command=self.insert_video)
insert_menu.add_separator()
insert_menu.add_command(label="表格", command=self.insert_table)
insert_menu.add_command(label="超链接", command=self.insert_hyperlink)
menu_bar.add_cascade(label="插入", menu=insert_menu)
# 媒体菜单
media_menu = tk.Menu(menu_bar, tearoff=0)
media_menu.add_command(label="停止播放", command=self.stop_media)
media_menu.add_command(label="暂停播放", command=self.pause_media)
media_menu.add_command(label="继续播放", command=self.resume_media)
# 添加删除媒体选项
media_menu.add_separator()
media_menu.add_command(label="删除当前选中媒体", command=self.delete_selected_media)
menu_bar.add_cascade(label="媒体", menu=media_menu)
# 视图菜单
view_menu = tk.Menu(menu_bar, tearoff=0)
self.theme_var = tk.StringVar(value="light")
view_menu.add_radiobutton(label="浅色主题", variable=self.theme_var, value="light", command=self.set_theme)
view_menu.add_radiobutton(label="深色主题", variable=self.theme_var, value="dark", command=self.set_theme)
view_menu.add_separator()
view_menu.add_checkbutton(label="状态栏", command=self.toggle_statusbar)
menu_bar.add_cascade(label="视图", menu=view_menu)
# 帮助菜单
help_menu = tk.Menu(menu_bar, tearoff=0)
help_menu.add_command(label="关于", command=self.show_about)
help_menu.add_command(label="查看日志", command=self.view_log)
menu_bar.add_cascade(label="帮助", menu=help_menu)
self.root.config(menu=menu_bar)
# 绑定快捷键
self.root.bind("<Control-n>", lambda e: self.new_file())
  self.root.bind("<Control-o>", lambda e: self.open_file())
    self.root.bind("<Control-s>", lambda e: self.save_file())
      self.root.bind("<Control-Shift-S>", lambda e: self.save_as())
        self.root.bind("<Control-Shift-P>", lambda e: self.save_packaged())
          self.root.bind("<Control-p>", lambda e: self.print_document())
            self.root.bind("<Control-w>", lambda e: self.close_file())
              self.root.bind("<Control-f>", lambda e: self.find_text())
                self.root.bind("<Control-h>", lambda e: self.replace_text())
                  # 绑定删除快捷键
                  self.root.bind("<Delete>", lambda e: self.delete_selection())
                    self.root.bind("<BackSpace>", lambda e: self.delete_selection())
                      def create_toolbar(self):
                      toolbar = tk.Frame(self.root, bd=1, relief=tk.RAISED, bg="#e0e0e0")
                      toolbar.pack(side=tk.TOP, fill=tk.X)
                      # 工具栏按钮
                      icons = [
                      ("新建", "", self.new_file),
                      ("打开", "", self.open_file),
                      ("保存", "", self.save_file),
                      ("另存为", "+", self.save_as),
                      ("打包保存", "", self.save_packaged),
                      ("打印", "️", self.print_document),
                      ("剪切", "✂️", self.cut),
                      ("复制", "", self.copy),
                      ("粘贴", "", self.paste),
                      ("删除", "️", self.delete_selection), # 添加删除按钮
                      ("图片", "️", self.insert_image),
                      ("音频", "", self.insert_audio),
                      ("视频", "", self.insert_video),
                      ("停止", "⏹️", self.stop_media),
                      ("暂停", "⏸️", self.pause_media),
                      ("播放", "▶️", self.resume_media),
                      ]
                      for text, icon, cmd in icons:
                      btn = tk.Button(toolbar, text=f"{icon
                      } {text
                      }", command=cmd,
                      bg="#e0e0e0", bd=1, relief=tk.FLAT, padx=5, pady=2)
                      btn.pack(side=tk.LEFT, padx=2, pady=2)
                      btn.bind("<Enter>", lambda e, b=btn: b.config(bg="#d0d0d0"))
                        btn.bind("<Leave>", lambda e, b=btn: b.config(bg="#e0e0e0"))
                          # 字体选择
                          font_frame = tk.Frame(toolbar, bg="#e0e0e0")
                          font_frame.pack(side=tk.LEFT, padx=(20, 5))
                          tk.Label(font_frame, text="字体:", bg="#e0e0e0").pack(side=tk.LEFT)
                          self.font_var = tk.StringVar(value="Arial")
                          font_combo = ttk.Combobox(font_frame, textvariable=self.font_var, width=12, state="readonly")
                          font_combo['values'] = ("Arial", "Times New Roman", "Courier New", "Verdana", "Calibri", "Georgia")
                          font_combo.pack(side=tk.LEFT, padx=5)
                          font_combo.bind("<<ComboboxSelected>>", self.change_font)
                            # 字号选择
                            tk.Label(font_frame, text="字号:", bg="#e0e0e0").pack(side=tk.LEFT, padx=(10, 0))
                            self.size_var = tk.IntVar(value=12)
                            size_combo = ttk.Combobox(font_frame, textvariable=self.size_var, width=4, state="readonly")
                            size_combo['values'] = tuple(range(8, 37, 2))
                            size_combo.pack(side=tk.LEFT, padx=5)
                            size_combo.bind("<<ComboboxSelected>>", self.change_font_size)
                              # 样式按钮
                              style_frame = tk.Frame(toolbar, bg="#e0e0e0")
                              style_frame.pack(side=tk.LEFT, padx=(20, 5))
                              self.bold_var = tk.BooleanVar()
                              bold_btn = tk.Checkbutton(style_frame, text="B", font=("Arial", 10, "bold"),
                              variable=self.bold_var, command=self.toggle_bold,
                              bg="#e0e0e0", bd=1, relief=tk.FLAT)
                              bold_btn.pack(side=tk.LEFT)
                              self.italic_var = tk.BooleanVar()
                              italic_btn = tk.Checkbutton(style_frame, text="I", font=("Arial", 10, "italic"),
                              variable=self.italic_var, command=self.toggle_italic,
                              bg="#e0e0e0", bd=1, relief=tk.FLAT)
                              italic_btn.pack(side=tk.LEFT, padx=5)
                              self.underline_var = tk.BooleanVar()
                              underline_btn = tk.Checkbutton(style_frame, text="U", font=("Arial", 10, "underline"),
                              variable=self.underline_var, command=self.toggle_underline,
                              bg="#e0e0e0", bd=1, relief=tk.FLAT)
                              underline_btn.pack(side=tk.LEFT)
                              # 对齐按钮
                              align_frame = tk.Frame(toolbar, bg="#e0e0e0")
                              align_frame.pack(side=tk.LEFT, padx=(20, 5))
                              align_btns = [
                              ("左对齐", "⬅️", "left"),
                              ("居中", "⏺️", "center"),
                              ("右对齐", "➡️", "right"),
                              ("两端对齐", "⬌", "justify")
                              ]
                              for text, icon, align in align_btns:
                              btn = tk.Button(align_frame, text=icon, command=lambda a=align: self.set_alignment(a),
                              bg="#e0e0e0", bd=1, relief=tk.FLAT, padx=5, pady=2)
                              btn.pack(side=tk.LEFT, padx=2)
                              btn.bind("<Enter>", lambda e, b=btn: b.config(bg="#d0d0d0"))
                                btn.bind("<Leave>", lambda e, b=btn: b.config(bg="#e0e0e0"))
                                  def create_editor(self):
                                  editor_frame = tk.Frame(self.root)
                                  editor_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
                                  # 创建文本编辑区域
                                  self.text_area = scrolledtext.ScrolledText(
                                  editor_frame, wrap=tk.WORD, font=("Arial", 12),
                                  undo=True, maxundo=100, padx=10, pady=10
                                  )
                                  self.text_area.pack(fill=tk.BOTH, expand=True)
                                  # 设置初始文本
                                  self.default_text = (
                                  "欢迎使用Word文档编辑器!\n\n"
                                  "现在可以在文字中间插入多媒体了:\n"
                                  "例如:在这句话 "
                                  " 后面插入图片\n\n"
                                  "开始编辑您的文档..."
                                  )
                                  self.text_area.insert(tk.END, self.default_text)
                                  self.log("已初始化默认文本")
                                  # 绑定事件
                                  self.text_area.bind("<<Modified>>", self.on_text_modified)
                                    self.text_area.bind("<KeyRelease>", self.update_status)
                                      self.text_area.bind("<Button-1>", self.on_media_click)
                                        def new_file(self):
                                        self.log("执行新建文件操作")
                                        if self.check_save():
                                        self.text_area.delete(1.0, tk.END)
                                        self.current_file = None
                                        self.inserted_media = []
                                        self.media_tags = {
                                        }
                                        # 新建文件时插入初始欢迎文本
                                        self.text_area.insert(tk.END, self.default_text)
                                        self.status_var.set("新建文档")
                                        self.root.title("Word文档编辑器 - 未命名")
                                        self.unsaved_changes = False
                                        self.log("已创建新文档")
                                        def open_file(self):
                                        self.log("执行打开文件操作")
                                        if self.check_save():
                                        file_path = filedialog.askopenfilename(
                                        filetypes=[
                                        ("Word文档", "*.docx"),
                                        ("二进制文档", "*.wordbin"),
                                        ("Word 97-2003文档", "*.doc"),
                                        ("文本文件", "*.txt"),
                                        ("所有文件", "*.*")
                                        ]
                                        )
                                        if file_path:
                                        try:
                                        self.status_var.set(f"正在打开文件: {file_path
                                        }")
                                        self.log(f"开始打开文件: {file_path
                                        }")
                                        self.root.update() # 更新UI显示状态
                                        if file_path.lower().endswith('.wordpack'):
                                        self.load_packaged(file_path)
                                        else:
                                        self.load_document(file_path)
                                        self.status_var.set(f"已打开: {os.path.basename(file_path)
                                        }")
                                        self.log(f"成功打开文件: {file_path
                                        }")
                                        except Exception as e:
                                        error_msg = f"打开文件失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def load_document(self, file_path):
                                        try:
                                        self.log(f"开始加载文档: {file_path
                                        }")
                                        self.text_area.delete(1.0, tk.END)
                                        self.inserted_media = []
                                        self.media_tags = {
                                        }
                                        self.log("已清空当前内容")
                                        if file_path.lower().endswith('.wordbin'):
                                        self.load_packaged(file_path) # 专门处理二进制文件
                                        elif file_path.lower().endswith('.docx'):
                                        self.load_docx(file_path)
                                        elif file_path.lower().endswith('.doc'):
                                        self.load_doc(file_path)
                                        else: # 文本文件(含媒体标记)
                                        try:
                                        with open(file_path, "r", encoding="utf-8") as file:
                                        content = file.read()
                                        self.log(f"从文本文件读取内容,长度: {
                                        len(content)
                                        }")
                                        self.text_area.insert(tk.END, content)
                                        self.restore_media_from_text()
                                        except UnicodeDecodeError:
                                        # 如果UTF-8失败,尝试其他编码
                                        with open(file_path, "r", encoding="gbk") as file:
                                        content = file.read()
                                        self.log(f"使用GBK编码读取文件,长度: {
                                        len(content)
                                        }")
                                        self.text_area.insert(tk.END, content)
                                        self.restore_media_from_text()
                                        self.current_file = file_path
                                        self.root.title(f"Word文档编辑器 - {os.path.basename(file_path)
                                        }")
                                        self.status_var.set(f"已打开: {file_path
                                        }")
                                        self.unsaved_changes = False
                                        self.log(f"文档加载完成: {file_path
                                        }")
                                        except Exception as e:
                                        error_msg = f"读取文件失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def load_docx(self, file_path):
                                        """加载DOCX并恢复图片到文字行间"""
                                        try:
                                        self.log(f"开始加载DOCX文件: {file_path
                                        }")
                                        doc = Document(file_path)
                                        self.text_area.delete(1.0, tk.END)
                                        self.log("已清空当前内容")
                                        # 提取文档中的图片
                                        media_dir = os.path.join(self.temp_media_dir, "docx_media")
                                        os.makedirs(media_dir, exist_ok=True)
                                        extracted_images = []
                                        for rel in doc.part.rels.values():
                                        if "image" in rel.target_ref:
                                        img_part = rel.target_part
                                        img_ext = img_part.content_type.split('/')[-1]
                                        if img_ext == 'jpeg':
                                        img_ext = 'jpg'
                                        img_filename = f"image_{
                                        len(extracted_images)
                                        }.{img_ext
                                        }"
                                        img_path = os.path.join(media_dir, img_filename)
                                        with open(img_path, 'wb') as f:
                                        f.write(img_part._blob)
                                        extracted_images.append(img_path)
                                        self.log(f"提取图片: {img_path
                                        }")
                                        # 恢复文本和图片到文字行间
                                        img_idx = 0
                                        for para in doc.paragraphs:
                                        # 先插入段落文本
                                        self.text_area.insert(tk.END, para.text)
                                        # 检查段落中的图片并插入
                                        for run in para.runs:
                                        for rel in run.part.rels.values():
                                        if "image" in rel.target_ref and img_idx <
                                        len(extracted_images):
                                        # 在当前光标位置插入图片
                                        self.insert_image_from_path(extracted_images[img_idx])
                                        img_idx += 1
                                        self.text_area.insert(tk.END, "\n")
                                        self.log(f"DOCX加载完成,共提取 {
                                        len(extracted_images)
                                        } 张图片")
                                        except Exception as e:
                                        error_msg = f"加载DOCX失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.log(error_msg)
                                        def save_packaged(self, file_path=None):
                                        """使用二进制格式打包保存文档"""
                                        if not file_path:
                                        file_path = filedialog.asksaveasfilename(
                                        defaultextension=".wordbin",
                                        filetypes=[("二进制文档", "*.wordbin")]
                                        )
                                        if not file_path:
                                        self.log("用户取消打包保存")
                                        return
                                        try:
                                        self.log(f"开始二进制打包保存到: {file_path
                                        }")
                                        self.status_var.set(f"正在保存二进制文档...")
                                        self.root.update()
                                        # 准备要保存的数据结构
                                        save_data = {
                                        'version': '2.0',
                                        'text_content': self.text_area.get(1.0, tk.END),
                                        'media': [],
                                        'media_tags': self.media_tags,
                                        'settings': {
                                        'font': self.font_var.get(),
                                        'size': self.size_var.get(),
                                        'bold': self.bold_var.get(),
                                        'italic': self.italic_var.get(),
                                        'underline': self.underline_var.get()
                                        }
                                        }
                                        # 创建临时目录用于收集媒体文件
                                        temp_dir = tempfile.mkdtemp(prefix="word_editor_pkg_")
                                        self.log(f"临时打包目录: {temp_dir
                                        }")
                                        # 处理媒体文件
                                        for media in self.inserted_media:
                                        try:
                                        # 复制媒体文件到临时目录
                                        media_name = os.path.basename(media['path'])
                                        dest_path = os.path.join(temp_dir, media_name)
                                        shutil.copy2(media['path'], dest_path)
                                        save_data['media'].append({
                                        'id': media['id'],
                                        'type': media['type'],
                                        'name': media_name,
                                        'path': os.path.relpath(dest_path, temp_dir),
                                        'placeholder': media.get('placeholder', '')
                                        })
                                        self.log(f"添加媒体文件: {media_name
                                        }")
                                        except Exception as e:
                                        self.log(f"保存媒体文件失败: {media['path']
                                        } - {
                                        str(e)
                                        }")
                                        # 创建zip文件
                                        with zipfile.ZipFile(file_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
                                        # 写入主数据文件 - 使用最高兼容的pickle协议
                                        binary_data = pickle.dumps(save_data, protocol=pickle.HIGHEST_PROTOCOL)
                                        zipf.writestr('data.pkl', binary_data)
                                        self.log(f"数据文件已写入,大小: {
                                        len(binary_data)
                                        } 字节")
                                        # 写入媒体文件
                                        for root, _, files in os.walk(temp_dir):
                                        for file in files:
                                        file_path = os.path.join(root, file)
                                        arcname = os.path.relpath(file_path, temp_dir)
                                        zipf.write(file_path, arcname)
                                        self.log(f"添加文件到包: {arcname
                                        }")
                                        self.current_file = file_path
                                        self.root.title(f"Word文档编辑器 - {os.path.basename(file_path)
                                        }")
                                        self.status_var.set(f"已保存二进制文档: {os.path.basename(file_path)
                                        }")
                                        self.unsaved_changes = False
                                        self.log(f"二进制打包保存完成: {file_path
                                        }")
                                        except Exception as e:
                                        error_msg = f"二进制打包保存失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        self.log(traceback.format_exc())
                                        finally:
                                        # 清理临时目录
                                        try:
                                        shutil.rmtree(temp_dir)
                                        self.log(f"临时目录已清理: {temp_dir
                                        }")
                                        except Exception as e:
                                        self.log(f"清理临时目录失败: {
                                        str(e)
                                        }")
                                        def load_packaged(self, file_path):
                                        """加载二进制打包文档"""
                                        try:
                                        self.log(f"开始加载二进制文档: {file_path
                                        }")
                                        self.status_var.set(f"正在加载二进制文档...")
                                        self.root.update()
                                        # 创建临时解压目录
                                        extract_dir = tempfile.mkdtemp(prefix="word_editor_extract_")
                                        self.log(f"临时解压目录: {extract_dir
                                        }")
                                        try:
                                        # 解压zip文件
                                        with zipfile.ZipFile(file_path, 'r') as zip_ref:
                                        zip_ref.extractall(extract_dir)
                                        self.log(f"解压完成,共 {
                                        len(zip_ref.filelist)
                                        } 个文件")
                                        # 检查数据文件是否存在
                                        data_path = os.path.join(extract_dir, 'data.pkl')
                                        if not os.path.exists(data_path):
                                        raise FileNotFoundError(f"未找到数据文件: {data_path
                                        }")
                                        # 读取主数据文件
                                        self.log(f"读取数据文件: {data_path
                                        }")
                                        with open(data_path, 'rb') as f:
                                        # 添加协议版本检查
                                        save_data = pickle.load(f)
                                        self.log(f"数据加载成功,版本: {save_data.get('version', '1.0')
                                        }")
                                        # 清空编辑器
                                        self.text_area.delete(1.0, tk.END)
                                        self.inserted_media = []
                                        self.media_tags = {
                                        }
                                        self.log("编辑器已清空")
                                        # 恢复媒体文件信息
                                        # 恢复媒体文件信息 - 修改这部分
                                        for media_info in save_data.get('media', []):
                                        try:
                                        # 处理路径中的特殊字符和中文
                                        media_path = os.path.join(extract_dir, media_info['path'].encode('utf-8').decode('utf-8'))
                                        media_path = os.path.normpath(media_path) # 规范化路径
                                        if os.path.exists(media_path):
                                        # 复制到更安全的临时位置
                                        safe_temp_dir = os.path.join(self.temp_media_dir, "extracted_media")
                                        os.makedirs(safe_temp_dir, exist_ok=True)
                                        # 使用原始文件名但确保安全
                                        safe_filename = "".join(
                                        c for c in media_info['name'] if c.isalnum() or c in (' ', '.', '_'))
                                        dest_path = os.path.join(safe_temp_dir, safe_filename)
                                        shutil.copy2(media_path, dest_path)
                                        # 更新媒体信息使用新路径
                                        media_type = media_info.get('type', 'image')
                                        if media_type == 'image':
                                        self.restore_image(media_info['id'], dest_path)
                                        elif media_type == 'audio':
                                        self.restore_audio(media_info['id'], dest_path)
                                        elif media_type == 'video':
                                        self.restore_video(media_info['id'], dest_path)
                                        self.log(f"成功恢复媒体: {media_info['name']
                                        }")
                                        else:
                                        self.log(f"媒体文件不存在: {media_path
                                        }")
                                        except Exception as e:
                                        self.log(f"恢复媒体失败: {
                                        str(e)
                                        }")
                                        continue
                                        # 恢复文本内容
                                        text_content = save_data.get('text_content', '')
                                        self.text_area.insert(tk.END, text_content)
                                        self.log(f"文本内容已恢复,长度: {
                                        len(text_content)
                                        }")
                                        # 恢复媒体标记
                                        self.media_tags = save_data.get('media_tags', {
                                        })
                                        self.log(f"恢复媒体标记,共 {
                                        len(self.media_tags)
                                        } 个")
                                        # 恢复媒体到文本中
                                        self.restore_media_from_text()
                                        self.log("媒体内容已恢复到文本")
                                        # 恢复设置
                                        if 'settings' in save_data:
                                        settings = save_data['settings']
                                        self.font_var.set(settings.get('font', 'Arial'))
                                        self.size_var.set(settings.get('size', 12))
                                        self.bold_var.set(settings.get('bold', False))
                                        self.italic_var.set(settings.get('italic', False))
                                        self.underline_var.set(settings.get('underline', False))
                                        self.change_font()
                                        self.log("编辑器设置已恢复")
                                        self.current_file = file_path
                                        self.root.title(f"Word文档编辑器 - {os.path.basename(file_path)
                                        }")
                                        self.status_var.set(f"已加载二进制文档: {os.path.basename(file_path)
                                        }")
                                        self.unsaved_changes = False
                                        self.log(f"二进制文档加载完成: {file_path
                                        }")
                                        finally:
                                        # 清理临时目录
                                        try:
                                        shutil.rmtree(extract_dir)
                                        self.log(f"临时目录已清理: {extract_dir
                                        }")
                                        except Exception as e:
                                        self.log(f"清理临时目录失败: {
                                        str(e)
                                        }")
                                        except zipfile.BadZipFile:
                                        error_msg = "文件不是有效的ZIP格式"
                                        messagebox.showerror("错误", error_msg)
                                        self.log(error_msg)
                                        except pickle.UnpicklingError:
                                        error_msg = "文件不是有效的二进制格式"
                                        messagebox.showerror("错误", error_msg)
                                        self.log(error_msg)
                                        except Exception as e:
                                        error_msg = f"加载二进制文档失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.log(error_msg)
                                        self.log(traceback.format_exc()) # 记录完整的错误堆栈
                                        # 加载失败时显示默认文本
                                        self.text_area.delete(1.0, tk.END)
                                        self.text_area.insert(tk.END, self.default_text)
                                        def get_text_with_media_markers(self):
                                        """生成带媒体标记的文本内容"""
                                        content = self.text_area.get(1.0, tk.END)
                                        # 按位置排序所有媒体标记
                                        media_positions = []
                                        for media_id, (start, end) in self.media_tags.items():
                                        start_idx = self.text_area.index(start)
                                        end_idx = self.text_area.index(end)
                                        media_positions.append((start_idx, end_idx, media_id))
                                        # 按位置排序
                                        media_positions.sort(key=lambda x: x[0])
                                        # 重建文本内容
                                        result = []
                                        last_pos = 0
                                        total_length = len(content)
                                        for start_idx, end_idx, media_id in media_positions:
                                        # 添加前面的文本
                                        if last_pos < start_idx:
                                        result.append(content[last_pos:start_idx])
                                        # 添加媒体标记
                                        result.append(f"[MEDIA:{media_id
                                        }]")
                                        last_pos = end_idx
                                        # 添加剩余文本
                                        if last_pos < total_length:
                                        result.append(content[last_pos:total_length])
                                        return ''.join(result)
                                        def save_file(self):
                                        self.log("执行保存文件操作")
                                        if self.current_file:
                                        if self.current_file.lower().endswith('*.wordbin'):
                                        self.save_packaged(self.current_file)
                                        else:
                                        self.save_document(self.current_file)
                                        else:
                                        self.save_as()
                                        def save_as(self):
                                        self.log("执行另存为操作")
                                        file_types = [
                                        ("Word文档", "*.docx"),
                                        ("二进制文档", "*.wordbin"),
                                        ("Word 97-2003文档", "*.doc"),
                                        ("RTF文档", "*.rtf"),
                                        ("PDF文档", "*.pdf"),
                                        ("文本文件", "*.txt"),
                                        ("所有文件", "*.*")
                                        ]
                                        file_path = filedialog.asksaveasfilename(
                                        defaultextension=".docx",
                                        filetypes=file_types
                                        )
                                        if file_path:
                                        self.log(f"另存为: {file_path
                                        }")
                                        if file_path.lower().endswith('.wordpack'):
                                        self.save_packaged(file_path)
                                        else:
                                        self.save_document(file_path)
                                        def save_document(self, file_path):
                                        try:
                                        self.log(f"开始保存文档到: {file_path
                                        }")
                                        if file_path.lower().endswith('.docx'):
                                        self.save_as_docx(file_path)
                                        elif file_path.lower().endswith('.doc'):
                                        self.save_as_doc(file_path)
                                        elif file_path.lower().endswith('.pdf'):
                                        self.save_as_pdf(file_path)
                                        elif file_path.lower().endswith('.rtf'):
                                        self.save_as_rtf(file_path)
                                        else: # 文本文件(含媒体标记)
                                        content = self.get_text_with_media_markers() # 带媒体位置标记
                                        with open(file_path, "w", encoding="utf-8") as file:
                                        file.write(content)
                                        self.log(f"已保存文本内容到: {file_path
                                        },长度: {
                                        len(content)
                                        }")
                                        self.current_file = file_path
                                        self.root.title(f"Word文档编辑器 - {os.path.basename(file_path)
                                        }")
                                        self.status_var.set(f"已保存: {file_path
                                        }")
                                        self.unsaved_changes = False
                                        self.log(f"文档保存完成: {file_path
                                        }")
                                        except Exception as e:
                                        error_msg = f"保存文件失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        # ------------------------------
                                        # 核心功能:多媒体插入到文字行间
                                        # ------------------------------
                                        def insert_image(self):
                                        """选择图片并插入到当前光标位置"""
                                        self.log("执行插入图片操作")
                                        file_path = filedialog.askopenfilename(
                                        filetypes=[("图片文件", "*.jpg *.jpeg *.png *.gif *.bmp"), ("所有文件", "*.*")]
                                        )
                                        if file_path:
                                        self.log(f"选中图片文件: {file_path
                                        }")
                                        self.insert_image_from_path(file_path)
                                        def insert_image_from_path(self, file_path):
                                        """从路径插入图片到当前光标位置"""
                                        try:
                                        # 生成唯一ID
                                        media_id = f"img_{
                                        len(self.inserted_media)
                                        }_{os.urandom(4).hex()
                                        }"
                                        file_name = os.path.basename(file_path)
                                        self.log(f"插入图片: {file_name
                                        }, ID: {media_id
                                        }")
                                        # 调整图片大小
                                        image = Image.open(file_path)
                                        max_height = int(self.size_var.get() * 2.5) # 基于字号的高度限制
                                        if image.height > max_height:
                                        ratio = max_height / image.height
                                        new_width = int(image.width * ratio)
                                        image = image.resize((new_width, max_height), Image.LANCZOS)
                                        # 转换为Tkinter图片对象
                                        tk_image = ImageTk.PhotoImage(image)
                                        # 获取当前光标位置
                                        cursor_pos = self.text_area.index(tk.INSERT)
                                        self.log(f"图片插入位置: {cursor_pos
                                        }")
                                        # 在光标位置插入图片
                                        self.text_area.image_create(cursor_pos, image=tk_image)
                                        # 计算图片的结束位置
                                        end_pos = self.text_area.index(f"{cursor_pos
                                        }+1c")
                                        # 记录媒体信息
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "image",
                                        "path": file_path,
                                        "name": file_name,
                                        "image_obj": tk_image # 保存引用
                                        })
                                        # 用tag标记图片位置
                                        self.text_area.tag_add(media_id, cursor_pos, end_pos)
                                        self.media_tags[media_id] = (cursor_pos, end_pos)
                                        self.status_var.set(f"已插入图片: {file_name
                                        }")
                                        self.unsaved_changes = True
                                        self.log(f"图片插入完成: {file_name
                                        }")
                                        except Exception as e:
                                        error_msg = f"插入图片失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def insert_audio(self):
                                        """插入音频占位符到文字行间"""
                                        self.log("执行插入音频操作")
                                        file_path = filedialog.askopenfilename(
                                        filetypes=[("音频文件", "*.mp3 *.wav *.ogg"), ("所有文件", "*.*")]
                                        )
                                        if file_path:
                                        try:
                                        media_id = f"audio_{
                                        len(self.inserted_media)
                                        }_{os.urandom(4).hex()
                                        }"
                                        file_name = os.path.basename(file_path)
                                        self.log(f"插入音频: {file_name
                                        }, ID: {media_id
                                        }")
                                        # 创建音频占位符图标
                                        audio_placeholder = " " + os.path.splitext(file_name)[0] + " "
                                        # 在光标位置插入占位符
                                        cursor_pos = self.text_area.index(tk.INSERT)
                                        self.log(f"音频插入位置: {cursor_pos
                                        }")
                                        self.text_area.insert(cursor_pos, audio_placeholder)
                                        # 计算占位符的结束位置
                                        start_pos = cursor_pos
                                        end_pos = self.text_area.index(f"{cursor_pos
                                        }+{
                                        len(audio_placeholder)
                                        }c")
                                        # 用tag标记占位符位置
                                        self.text_area.tag_add(media_id, start_pos, end_pos)
                                        self.media_tags[media_id] = (start_pos, end_pos)
                                        # 设置tag的样式
                                        self.text_area.tag_config(media_id, foreground="#0000FF", underline=True)
                                        # 记录媒体信息
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "audio",
                                        "path": file_path,
                                        "name": file_name,
                                        "placeholder": audio_placeholder
                                        })
                                        self.status_var.set(f"已插入音频: {file_name
                                        }")
                                        self.unsaved_changes = True
                                        self.log(f"音频插入完成: {file_name
                                        }")
                                        except Exception as e:
                                        error_msg = f"插入音频失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def insert_video(self):
                                        """插入视频占位符到文字行间"""
                                        self.log("执行插入视频操作")
                                        file_path = filedialog.askopenfilename(
                                        filetypes=[("视频文件", "*.mp4 *.avi *.mov *.mkv"), ("所有文件", "*.*")]
                                        )
                                        if file_path:
                                        try:
                                        media_id = f"video_{
                                        len(self.inserted_media)
                                        }_{os.urandom(4).hex()
                                        }"
                                        file_name = os.path.basename(file_path)
                                        self.log(f"插入视频: {file_name
                                        }, ID: {media_id
                                        }")
                                        # 创建视频占位符图标
                                        video_placeholder = " " + os.path.splitext(file_name)[0] + " "
                                        # 在光标位置插入占位符
                                        cursor_pos = self.text_area.index(tk.INSERT)
                                        self.log(f"视频插入位置: {cursor_pos
                                        }")
                                        self.text_area.insert(cursor_pos, video_placeholder)
                                        # 计算占位符的结束位置
                                        start_pos = cursor_pos
                                        end_pos = self.text_area.index(f"{cursor_pos
                                        }+{
                                        len(video_placeholder)
                                        }c")
                                        # 用tag标记占位符位置
                                        self.text_area.tag_add(media_id, start_pos, end_pos)
                                        self.media_tags[media_id] = (start_pos, end_pos)
                                        # 设置tag的样式
                                        self.text_area.tag_config(media_id, foreground="#FF0000", underline=True)
                                        # 记录媒体信息
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "video",
                                        "path": file_path,
                                        "name": file_name,
                                        "placeholder": video_placeholder
                                        })
                                        self.status_var.set(f"已插入视频: {file_name
                                        }")
                                        self.unsaved_changes = True
                                        self.log(f"视频插入完成: {file_name
                                        }")
                                        except Exception as e:
                                        error_msg = f"插入视频失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        # ------------------------------
                                        # 多媒体播放控制功能
                                        # ------------------------------
                                        def on_media_click(self, event):
                                        """点击多媒体占位符时触发操作"""
                                        pos = self.text_area.index(f"@{event.x
                                        },{event.y
                                        }")
                                        self.log(f"媒体点击位置: {pos
                                        }")
                                        # 查找点击位置对应的媒体
                                        for media_id, (start, end) in self.media_tags.items():
                                        if self.text_area.compare(start, "<=", pos) and self.text_area.compare(pos, "<=", end):
                                        # 找到对应的媒体
                                        media = next((m for m in self.inserted_media if m["id"] == media_id), None)
                                        if media:
                                        self.log(f"点击了媒体: {media['name']
                                        } ({media['type']
                                        })")
                                        if media["type"] == "audio":
                                        self.play_audio(media["path"])
                                        elif media["type"] == "video":
                                        self.play_video(media["path"])
                                        elif media["type"] == "image":
                                        self.show_full_image(media["path"])
                                        break
                                        def play_audio(self, audio_path):
                                        """播放音频文件"""
                                        try:
                                        self.log(f"播放音频: {audio_path
                                        }")
                                        # 停止当前正在播放的音频
                                        if mixer.music.get_busy():
                                        mixer.music.stop()
                                        # 加载并播放新音频
                                        mixer.music.load(audio_path)
                                        mixer.music.play()
                                        self.current_audio = audio_path
                                        self.status_var.set(f"正在播放音频: {os.path.basename(audio_path)
                                        }")
                                        except Exception as e:
                                        # 尝试备用播放方法
                                        try:
                                        self.log(f"pygame播放音频失败,尝试系统播放器: {
                                        str(e)
                                        }")
                                        subprocess.Popen(['start', audio_path], shell=True)
                                        self.status_var.set(f"正在播放音频: {os.path.basename(audio_path)
                                        }")
                                        except Exception as e2:
                                        error_msg = f"播放音频失败: {
                                        str(e2)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def play_video(self, video_path):
                                        """播放视频文件"""
                                        try:
                                        self.log(f"播放视频: {video_path
                                        }")
                                        # 尝试用系统默认播放器打开
                                        if os.name == 'nt': # Windows系统
                                        os.startfile(video_path)
                                        elif os.name == 'posix': # macOS或Linux
                                        subprocess.call(['open' if sys.platform == 'darwin' else 'xdg-open', video_path])
                                        self.status_var.set(f"正在播放视频: {os.path.basename(video_path)
                                        }")
                                        except Exception as e:
                                        error_msg = f"播放视频失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def stop_media(self):
                                        """停止当前播放的媒体"""
                                        self.log("停止媒体播放")
                                        if mixer.music.get_busy():
                                        mixer.music.stop()
                                        self.status_var.set("音频已停止")
                                        self.current_audio = None
                                        def pause_media(self):
                                        """暂停当前播放的媒体"""
                                        self.log("暂停媒体播放")
                                        if mixer.music.get_busy():
                                        mixer.music.pause()
                                        self.status_var.set("音频已暂停")
                                        def resume_media(self):
                                        """继续播放已暂停的媒体"""
                                        self.log("继续媒体播放")
                                        if not mixer.music.get_busy() and self.current_audio:
                                        mixer.music.unpause()
                                        self.status_var.set(f"继续播放音频: {os.path.basename(self.current_audio)
                                        }")
                                        def restore_media_from_text(self):
                                        """从文本中的[MEDIA:ID]标记恢复多媒体到文字行间"""
                                        try:
                                        self.log("开始从文本中恢复媒体")
                                        content = self.text_area.get(1.0, tk.END)
                                        self.log(f"获取文本内容用于媒体恢复,长度: {
                                        len(content)
                                        }")
                                        # 清除当前内容但保留媒体信息
                                        self.text_area.delete(1.0, tk.END)
                                        self.status_var.set(f"开始恢复媒体内容,文本长度: {
                                        len(content)
                                        }")
                                        self.root.update()
                                        current_pos = 0
                                        media_restored = 0
                                        media_tags_count = 0
                                        while True:
                                        # 查找媒体标记
                                        tag_start = content.find("[MEDIA:", current_pos)
                                        if tag_start == -1:
                                        # 插入剩余文本
                                        remaining_text = content[current_pos:]
                                        self.text_area.insert(tk.END, remaining_text)
                                        self.log(f"插入剩余文本,长度: {
                                        len(remaining_text)
                                        }")
                                        break
                                        # 插入标记前的文本
                                        text_before = content[current_pos:tag_start]
                                        self.text_area.insert(tk.END, text_before)
                                        self.log(f"插入标记前文本,长度: {
                                        len(text_before)
                                        }")
                                        # 解析媒体ID
                                        tag_end = content.find("]", tag_start)
                                        if tag_end == -1:
                                        self.log("未找到媒体标记的结束符],停止恢复")
                                        break
                                        media_id = content[tag_start + 7: tag_end].strip()
                                        media_tags_count += 1
                                        self.log(f"找到媒体标记 #{media_tags_count
                                        }: {media_id
                                        }")
                                        # 查找媒体信息并恢复
                                        media = next((m for m in self.inserted_media if m["id"] == media_id), None)
                                        if media:
                                        media_restored += 1
                                        if media_restored % 5 == 0:
                                        self.status_var.set(f"已恢复 {media_restored
                                        } 个媒体")
                                        self.root.update()
                                        if media["type"] == "image":
                                        self.insert_image_from_path(media["path"])
                                        elif media["type"] == "audio":
                                        self.text_area.insert(tk.END, media["placeholder"])
                                        # 更新tag位置
                                        start = self.text_area.index(tk.END + f"-{
                                        len(media['placeholder'])
                                        }c")
                                        end = self.text_area.index(tk.END)
                                        self.media_tags[media_id] = (start, end)
                                        self.text_area.tag_add(media_id, start, end)
                                        self.text_area.tag_config(media_id, foreground="#0000FF", underline=True)
                                        elif media["type"] == "video":
                                        self.text_area.insert(tk.END, media["placeholder"])
                                        # 更新tag位置
                                        start = self.text_area.index(tk.END + f"-{
                                        len(media['placeholder'])
                                        }c")
                                        end = self.text_area.index(tk.END)
                                        self.media_tags[media_id] = (start, end)
                                        self.text_area.tag_add(media_id, start, end)
                                        self.text_area.tag_config(media_id, foreground="#FF0000", underline=True)
                                        else:
                                        self.log(f"未找到媒体信息: {media_id
                                        },插入原始标记")
                                        self.text_area.insert(tk.END, f"[MEDIA:{media_id
                                        }]")
                                        current_pos = tag_end + 1
                                        self.log(f"媒体恢复完成,共找到 {media_tags_count
                                        } 个媒体标记,成功恢复 {media_restored
                                        } 个媒体")
                                        self.status_var.set(f"媒体恢复完成,共恢复 {media_restored
                                        } 个媒体")
                                        except Exception as e:
                                        error_msg = f"恢复媒体内容失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def restore_image(self, media_id, path):
                                        """从路径恢复图片到文字行间"""
                                        try:
                                        # 确保路径存在
                                        if not os.path.exists(path):
                                        raise FileNotFoundError(f"图片文件不存在: {path
                                        }")
                                        self.log(f"恢复图片: {path
                                        }, ID: {media_id
                                        }")
                                        image = Image.open(path)
                                        # 转换路径为绝对路径并标准化
                                        abs_path = os.path.abspath(path)
                                        abs_path = abs_path.replace("\\", "/") # 统一使用正斜杠
                                        # 记录媒体信息时使用绝对路径
                                        file_name = os.path.basename(path)
                                        tk_image = ImageTk.PhotoImage(image)
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "image",
                                        "path": abs_path, # 使用绝对路径
                                        "name": file_name,
                                        "image_obj": tk_image
                                        })
                                        self.log(f"图片恢复完成: {file_name
                                        }")
                                        except Exception as e:
                                        error_msg = f"恢复图片失败: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        def restore_audio(self, media_id, path):
                                        """恢复音频占位符"""
                                        file_name = os.path.basename(path)
                                        self.log(f"恢复音频: {file_name
                                        }, ID: {media_id
                                        }")
                                        placeholder = " " + os.path.splitext(file_name)[0] + " "
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "audio",
                                        "path": path,
                                        "name": file_name,
                                        "placeholder": placeholder
                                        })
                                        self.log(f"音频恢复完成: {file_name
                                        }")
                                        def restore_video(self, media_id, path):
                                        """恢复视频占位符"""
                                        file_name = os.path.basename(path)
                                        self.log(f"恢复视频: {file_name
                                        }, ID: {media_id
                                        }")
                                        placeholder = " " + os.path.splitext(file_name)[0] + " "
                                        self.inserted_media.append({
                                        "id": media_id,
                                        "type": "video",
                                        "path": path,
                                        "name": file_name,
                                        "placeholder": placeholder
                                        })
                                        self.log(f"视频恢复完成: {file_name
                                        }")
                                        def show_full_image(self, image_path):
                                        """显示完整图片"""
                                        try:
                                        self.log(f"显示完整图片: {image_path
                                        }")
                                        img_window = tk.Toplevel(self.root)
                                        img_window.title("图片预览")
                                        image = Image.open(image_path)
                                        # 限制最大尺寸
                                        max_width = self.root.winfo_screenwidth() - 100
                                        max_height = self.root.winfo_screenheight() - 100
                                        # 计算缩放比例
                                        width_ratio = max_width / image.width
                                        height_ratio = max_height / image.height
                                        ratio = min(width_ratio, height_ratio, 1.0) # 不放大图片
                                        if ratio <
                                        1.0:
                                        new_width = int(image.width * ratio)
                                        new_height = int(image.height * ratio)
                                        image = image.resize((new_width, new_height), Image.LANCZOS)
                                        photo = ImageTk.PhotoImage(image)
                                        # 添加滚动条支持
                                        canvas = tk.Canvas(img_window, width=image.width, height=image.height)
                                        canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
                                        scrollbar_y = tk.Scrollbar(img_window, orient=tk.VERTICAL, command=canvas.yview)
                                        scrollbar_y.pack(side=tk.RIGHT, fill=tk.Y)
                                        scrollbar_x = tk.Scrollbar(img_window, orient=tk.HORIZONTAL, command=canvas.xview)
                                        scrollbar_x.pack(side=tk.BOTTOM, fill=tk.X)
                                        canvas.configure(yscrollcommand=scrollbar_y.set, xscrollcommand=scrollbar_x.set)
                                        # 放置图片
                                        canvas.create_image(0, 0, anchor=tk.NW, image=photo)
                                        canvas.image = photo # 保持引用
                                        # 设置滚动区域
                                        canvas.config(scrollregion=canvas.bbox("all"))
                                        img_window.geometry(f"{image.width
                                        }x{image.height
                                        }")
                                        self.log(f"图片预览窗口已创建")
                                        except Exception as e:
                                        error_msg = f"无法显示图片: {
                                        str(e)
                                        }"
                                        messagebox.showerror("错误", error_msg)
                                        self.status_var.set(error_msg)
                                        self.log(error_msg)
                                        # ------------------------------
                                        # 删除功能
                                        # ------------------------------
                                        def delete_selection(self):
                                        """删除选中的内容(文本或媒体)"""
                                        self.log("执行删除选中内容操作")
                                        try:
                                        # 检查是否有选中内容
                                        selected_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
                                        if selected_text:
                                        # 先尝试删除任何被选中的媒体
                                        deleted_media = self.delete_media_in_selection()
                                        # 如果没有删除媒体,删除文本
                                        if not deleted_media:
                                        self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
                                        self.unsaved_changes = True
                                        self.status_var.set("已删除选中内容")
                                        except tk.TclError:
                                        # 没有选中内容,不执行任何操作
                                        pass
                                        def delete_selected_media(self):
                                        """删除当前选中的媒体(如果有)"""
                                        self.log("执行删除选中媒体操作")
                                        try:
                                        # 获取选中区域
                                        start = self.text_area.index(tk.SEL_FIRST)
                                        end = self.text_area.index(tk.SEL_LAST)
                                        # 查找选中区域内的媒体
                                        deleted = self.delete_media_in_range(start, end)
                                        if deleted:
                                        self.unsaved_changes = True
                                        self.status_var.set(f"已删除 {deleted
                                        } 个媒体")
                                        else:
                                        messagebox.showinfo("提示", "未选中任何媒体")
                                        except tk.TclError:
                                        # 没有选中内容
                                        messagebox.showinfo("提示", "请先选中要删除的媒体")
                                        def delete_media_in_selection(self):
                                        """删除选中区域内的所有媒体"""
                                        try:
                                        start = self.text_area.index(tk.SEL_FIRST)
                                        end = self.text_area.index(tk.SEL_LAST)
                                        return self.delete_media_in_range(start, end)
                                        except tk.TclError:
                                        return 0
                                        def delete_media_in_range(self, start, end):
                                        """删除指定范围内的所有媒体"""
                                        deleted_count = 0
                                        media_to_delete = []
                                        # 找出范围内的所有媒体
                                        for media_id, (media_start, media_end) in self.media_tags.items():
                                        # 检查媒体是否与选择范围重叠
                                        if (self.text_area.compare(media_start, "<=", end) and
                                        self.text_area.compare(media_end, ">=", start)):
                                        media_to_delete.append(media_id)
                                        # 删除找到的媒体
                                        for media_id in media_to_delete:
                                        # 获取媒体位置
                                        media_start, media_end = self.media_tags[media_id]
                                        # 删除文本区域中的媒体
                                        self.text_area.delete(media_start, media_end)
                                        # 从媒体列表中移除
                                        self.inserted_media = [m for m in self.inserted_media if m["id"] != media_id]
                                        # 从媒体标记中移除
                                        del self.media_tags[media_id]
                                        deleted_count += 1
                                        self.log(f"已删除媒体: {media_id
                                        }")
                                        return deleted_count
                                        # ------------------------------
                                        # 其他基础功能
                                        # ------------------------------
                                        def close_file(self):
                                        self.log("执行关闭文件操作")
                                        if self.check_save():
                                        self.text_area.delete(1.0, tk.END)
                                        self.current_file = None
                                        self.inserted_media = []
                                        self.media_tags = {
                                        }
                                        # 关闭文件后显示初始文本
                                        self.text_area.insert(tk.END, self.default_text)
                                        self.status_var.set("就绪")
                                        self.root.title("Word文档编辑器")
                                        self.unsaved_changes = False
                                        self.log("文件已关闭,显示默认文本")
                                        def on_closing(self):
                                        self.log("执行程序关闭操作")
                                        if self.check_save():
                                        if mixer.get_init():
                                        mixer.stop()
                                        mixer.quit()
                                        pygame.quit()
                                        self.root.destroy()
                                        self.log("程序已退出")
                                        def check_save(self):
                                        self.log("检查是否需要保存")
                                        if self.unsaved_changes or self.text_area.edit_modified():
                                        response = messagebox.askyesnocancel(
                                        "保存文档",
                                        "文档已修改,是否保存更改?"
                                        )
                                        if response is None:
                                        self.log("用户取消关闭操作")
                                        return False
                                        if response:
                                        self.log("用户选择保存更改")
                                        self.save_file()
                                        return True
                                        self.log("无需保存")
                                        return True
                                        def print_document(self):
                                        self.log("执行打印操作")
                                        messagebox.showinfo("打印", "文档已发送到打印机")
                                        def undo(self):
                                        self.log("执行撤销操作")
                                        try:
                                        self.text_area.edit_undo()
                                        self.unsaved_changes = True
                                        except:
                                        pass
                                        def redo(self):
                                        self.log("执行重做操作")
                                        try:
                                        self.text_area.edit_redo()
                                        self.unsaved_changes = True
                                        except:
                                        pass
                                        def cut(self):
                                        self.log("执行剪切操作")
                                        self.text_area.event_generate("<<Cut>>")
                                          self.unsaved_changes = True
                                          def copy(self):
                                          self.log("执行复制操作")
                                          self.text_area.event_generate("<<Copy>>")
                                            def paste(self):
                                            self.log("执行粘贴操作")
                                            self.text_area.event_generate("<<Paste>>")
                                              self.unsaved_changes = True
                                              def select_all(self):
                                              self.log("执行全选操作")
                                              self.text_area.tag_add(tk.SEL, "1.0", tk.END)
                                              self.text_area.mark_set(tk.INSERT, "1.0")
                                              self.text_area.see(tk.INSERT)
                                              return "break"
                                              def find_text(self):
                                              self.log("执行查找操作")
                                              find_dialog = tk.Toplevel(self.root)
                                              find_dialog.title("查找")
                                              find_dialog.geometry("400x200")
                                              find_dialog.transient(self.root)
                                              find_dialog.grab_set()
                                              tk.Label(find_dialog, text="查找内容:").pack(pady=(10, 0), padx=10, anchor=tk.W)
                                              find_entry = tk.Entry(find_dialog, width=40)
                                              find_entry.pack(padx=10, pady=5)
                                              find_entry.focus_set()
                                              def find():
                                              text = find_entry.get()
                                              self.log(f"查找内容: {text
                                              }")
                                              if text:
                                              start = self.text_area.search(text, "1.0", stopindex=tk.END)
                                              if start:
                                              end = f"{start
                                              }+{
                                              len(text)
                                              }c"
                                              self.text_area.tag_remove(tk.SEL, "1.0", tk.END)
                                              self.text_area.tag_add(tk.SEL, start, end)
                                              self.text_area.mark_set(tk.INSERT, end)
                                              self.text_area.see(start)
                                              else:
                                              messagebox.showinfo("查找", "找不到该内容。")
                                              tk.Button(find_dialog, text="查找下一个", command=find).pack(pady=10)
                                              def replace_text(self):
                                              self.log("执行替换操作")
                                              replace_dialog = tk.Toplevel(self.root)
                                              replace_dialog.title("替换")
                                              replace_dialog.geometry("400x250")
                                              replace_dialog.transient(self.root)
                                              replace_dialog.grab_set()
                                              tk.Label(replace_dialog, text="查找内容:").pack(pady=(10, 0), padx=10, anchor=tk.W)
                                              find_entry = tk.Entry(replace_dialog, width=40)
                                              find_entry.pack(padx=10, pady=5)
                                              tk.Label(replace_dialog, text="替换为:").pack(pady=(5, 0), padx=10, anchor=tk.W)
                                              replace_entry = tk.Entry(replace_dialog, width=40)
                                              replace_entry.pack(padx=10, pady=5)
                                              def find_next():
                                              text = find_entry.get()
                                              self.log(f"查找替换内容: {text
                                              }")
                                              if text:
                                              start = self.text_area.search(text, "1.0", stopindex=tk.END)
                                              if start:
                                              end = f"{start
                                              }+{
                                              len(text)
                                              }c"
                                              self.text_area.tag_remove(tk.SEL, "1.0", tk.END)
                                              self.text_area.tag_add(tk.SEL, start, end)
                                              self.text_area.mark_set(tk.INSERT, end)
                                              self.text_area.see(start)
                                              self.text_area.see(start)
                                              else:
                                              messagebox.showinfo("替换", "找不到该内容。")
                                              def replace():
                                              text = find_entry.get()
                                              replacement = replace_entry.get()
                                              self.log(f"替换 '{text
                                              }' 为 '{replacement
                                              }'")
                                              if self.text_area.tag_ranges(tk.SEL):
                                              sel_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
                                              if sel_text == text:
                                              self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
                                              self.text_area.insert(tk.SEL_FIRST, replacement)
                                              self.unsaved_changes = True
                                              find_next()
                                              def replace_all():
                                              count = 0
                                              text = find_entry.get()
                                              replacement = replace_entry.get()
                                              self.log(f"全部替换 '{text
                                              }' 为 '{replacement
                                              }'")
                                              if text:
                                              start = "1.0"
                                              while True:
                                              start = self.text_area.search(text, start, stopindex=tk.END)
                                              if not start:
                                              break
                                              end = f"{start
                                              }+{
                                              len(text)
                                              }c"
                                              self.text_area.delete(start, end)
                                              self.text_area.insert(start, replacement)
                                              start = end
                                              count += 1
                                              self.unsaved_changes = True
                                              messagebox.showinfo("替换", f"已替换 {count
                                              } 处。")
                                              self.log(f"完成全部替换,共替换 {count
                                              } 处")
                                              btn_frame = tk.Frame(replace_dialog)
                                              btn_frame.pack(pady=10)
                                              tk.Button(btn_frame, text="查找下一个", command=find_next).pack(side=tk.LEFT, padx=5)
                                              tk.Button(btn_frame, text="替换", command=replace).pack(side=tk.LEFT, padx=5)
                                              tk.Button(btn_frame, text="全部替换", command=replace_all).pack(side=tk.LEFT, padx=5)
                                              def insert_table(self):
                                              self.log("插入表格")
                                              self.text_area.insert(tk.INSERT, "\n[表格]\n")
                                              self.status_var.set("已插入表格")
                                              self.unsaved_changes = True
                                              def insert_hyperlink(self):
                                              self.log("插入超链接")
                                              self.text_area.insert(tk.INSERT, "[超链接]")
                                              self.status_var.set("已插入超链接")
                                              self.unsaved_changes = True
                                              def change_font(self, event=None):
                                              font = self.font_var.get()
                                              self.log(f"更改字体为: {font
                                              }")
                                              try:
                                              self.text_area.config(font=(font, self.size_var.get()))
                                              self.unsaved_changes = True
                                              except:
                                              pass
                                              def change_font_size(self, event=None):
                                              size = self.size_var.get()
                                              self.log(f"更改字号为: {size
                                              }")
                                              try:
                                              self.text_area.config(font=(self.font_var.get(), size))
                                              self.unsaved_changes = True
                                              except:
                                              pass
                                              def toggle_bold(self):
                                              state = "启用" if self.bold_var.get() else "禁用"
                                              self.log(f"粗体{state
                                              }")
                                              current_font = self.text_area.cget("font")
                                              font_family = current_font.split()[0] if current_font else "Arial"
                                              font_size = self.size_var.get()
                                              if self.bold_var.get():
                                              self.text_area.config(font=(font_family, font_size, "bold"))
                                              else:
                                              self.text_area.config(font=(font_family, font_size))
                                              self.unsaved_changes = True
                                              def toggle_italic(self):
                                              state = "启用" if self.italic_var.get() else "禁用"
                                              self.log(f"斜体{state
                                              }")
                                              current_font = self.text_area.cget("font")
                                              font_family = current_font.split()[0] if current_font else "Arial"
                                              font_size = self.size_var.get()
                                              if self.italic_var.get():
                                              self.text_area.config(font=(font_family, font_size, "italic"))
                                              else:
                                              self.text_area.config(font=(font_family, font_size))
                                              self.unsaved_changes = True
                                              def toggle_underline(self):
                                              state = "启用" if self.underline_var.get() else "禁用"
                                              self.log(f"下划线{state
                                              }")
                                              current_font = self.text_area.cget("font")
                                              font_family = current_font.split()[0] if current_font else "Arial"
                                              font_size = self.size_var.get()
                                              if self.underline_var.get():
                                              self.text_area.config(font=(font_family, font_size, "underline"))
                                              else:
                                              self.text_area.config(font=(font_family, font_size))
                                              self.unsaved_changes = True
                                              def set_alignment(self, align):
                                              self.log(f"设置对齐方式: {align
                                              }")
                                              self.status_var.set(f"已设置对齐方式: {align
                                              }")
                                              self.unsaved_changes = True
                                              def set_theme(self, event=None):
                                              theme = self.theme_var.get()
                                              self.log(f"设置主题: {theme
                                              }")
                                              if theme == "light":
                                              bg_color = "#ffffff"
                                              fg_color = "#000000"
                                              toolbar_bg = "#e0e0e0"
                                              status_bg = "#e0e0e0"
                                              text_bg = "#ffffff"
                                              else:
                                              bg_color = "#2d2d2d"
                                              fg_color = "#ffffff"
                                              toolbar_bg = "#3d3d3d"
                                              status_bg = "#3d3d3d"
                                              text_bg = "#1e1e1e"
                                              self.root.config(bg=bg_color)
                                              for widget in self.root.winfo_children():
                                              if isinstance(widget, tk.Frame) and widget.winfo_name() == "!frame":
                                              widget.config(bg=toolbar_bg)
                                              for child in widget.winfo_children():
                                              try:
                                              child.config(bg=toolbar_bg, fg=fg_color)
                                              except:
                                              pass
                                              self.text_area.config(bg=text_bg, fg=fg_color, insertbackground=fg_color)
                                              self.status_bar.config(bg=status_bg, fg=fg_color)
                                              def toggle_statusbar(self):
                                              if self.status_bar.winfo_ismapped():
                                              self.status_bar.pack_forget()
                                              self.log("隐藏状态栏")
                                              else:
                                              self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
                                              self.log("显示状态栏")
                                              def show_about(self):
                                              self.log("显示关于对话框")
                                              about_text = (
                                              "Word文档编辑器(多媒体增强版)\n"
                                              "版本 2.3\n\n"
                                              "支持多媒体与文字混排,可在文字行间自由插入图片、音频、视频。\n"
                                              "使用说明:\n"
                                              "- 插入多媒体后,可直接在周围输入文字,多媒体会自动调整位置\n"
                                              "- 点击音频/视频可播放,点击图片可查看大图\n"
                                              "- 使用媒体菜单可控制播放、暂停和停止\n"
                                              "- 推荐使用「打包保存」确保多媒体不丢失\n"
                                              "- 选中内容或媒体后按Delete键可删除\n\n"
                                              "© 2023 Python Tkinter 项目"
                                              )
                                              messagebox.showinfo("关于", about_text)
                                              def view_log(self):
                                              """查看日志文件"""
                                              self.log("查看日志文件")
                                              try:
                                              if os.path.exists(self.log_file):
                                              if os.name == 'nt': # Windows
                                              os.startfile(self.log_file)
                                              elif os.name == 'posix': # macOS/Linux
                                              subprocess.call(['open' if sys.platform == 'darwin' else 'xdg-open', self.log_file])
                                              self.status_var.set(f"已打开日志文件: {os.path.basename(self.log_file)
                                              }")
                                              else:
                                              messagebox.showinfo("日志", "日志文件不存在")
                                              except Exception as e:
                                              error_msg = f"打开日志文件失败: {
                                              str(e)
                                              }"
                                              messagebox.showerror("错误", error_msg)
                                              self.log(error_msg)
                                              def on_text_modified(self, event):
                                              if self.text_area.edit_modified():
                                              self.unsaved_changes = True
                                              title = self.root.title()
                                              if not title.startswith("*"):
                                              self.root.title(f"*{title
                                              }")
                                              self.text_area.edit_modified(False)
                                              self.log("文本内容已修改")
                                              def update_status(self, event=None):
                                              index = self.text_area.index(tk.INSERT)
                                              line, column = index.split('.')
                                              char_count = len(self.text_area.get(1.0, tk.END)) - 1 # 减去最后的换行符
                                              self.status_var.set(f"行: {line
                                              }, 列: {column
                                              } | 字数: {char_count
                                              } | 日志: {os.path.basename(self.log_file)
                                              }")
                                              # 以下方法为占位符,实际实现可能需要额外库支持
                                              def load_doc(self, file_path):
                                              self.log(f"加载DOC文件: {file_path
                                              }")
                                              messagebox.showinfo("提示", "加载DOC文件功能需要额外支持,这里仅显示文本内容")
                                              try:
                                              pythoncom.CoInitialize()
                                              word = win32.Dispatch("Word.Application")
                                              doc = word.Documents.Open(file_path)
                                              content = doc.Content.Text
                                              self.text_area.insert(tk.END, content)
                                              doc.Close()
                                              word.Quit()
                                              self.log(f"DOC文件文本内容已加载,长度: {
                                              len(content)
                                              }")
                                              except Exception as e:
                                              error_msg = f"加载DOC文件失败: {
                                              str(e)
                                              }"
                                              messagebox.showerror("错误", error_msg)
                                              self.log(error_msg)
                                              with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                                              content = f.read()
                                              self.text_area.insert(tk.END, content)
                                              self.log(f"尝试以文本方式加载DOC文件,长度: {
                                              len(content)
                                              }")
                                              def save_as_docx(self, file_path):
                                              self.log(f"保存为DOCX: {file_path
                                              }")
                                              doc = Document()
                                              full_text = self.text_area.get(1.0, tk.END)
                                              doc.add_paragraph(full_text)
                                              doc.save(file_path)
                                              self.log(f"DOCX保存完成")
                                              def save_as_doc(self, file_path):
                                              self.log(f"保存为DOC: {file_path
                                              }")
                                              messagebox.showinfo("提示", "保存为DOC格式功能受限,已保存为DOCX格式")
                                              if not file_path.endswith('.docx'):
                                              file_path = file_path + '.docx'
                                              self.save_as_docx(file_path)
                                              def save_as_pdf(self, file_path):
                                              self.log(f"保存为PDF: {file_path
                                              }")
                                              messagebox.showinfo("提示", "PDF保存功能需要额外支持")
                                              # 实际实现需要安装pywin32并与Word交互
                                              def save_as_rtf(self, file_path):
                                              self.log(f"保存为RTF: {file_path
                                              }")
                                              with open(file_path, "w", encoding="utf-8") as f:
                                              f.write(self.get_text_with_media_markers())
                                              self.log(f"RTF保存完成")
                                              if __name__ == "__main__":
                                              root = tk.Tk()
                                              app = WordEditor(root)
                                              root.mainloop()
posted @ 2025-08-15 13:52  yjbjingcha  阅读(10)  评论(0)    收藏  举报