#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
作业五:现代化记事本软件
功能:完整的文本编辑器,包含文件操作、编辑功能、格式设置及实用辅助功能
"""
import tkinter as tk
from tkinter import ttk, filedialog, messagebox, colorchooser, simpledialog
import os
import sys
import re
import time
from datetime import datetime
import webbrowser
class NotepadApp:
def __init__(self, root):
self.root = root
self.root.title("作业五 - 现代化记事本")
self.root.geometry("1200x800")
# 现代化配色方案
self.colors = {
'primary': '#2c3e50',
'secondary': '#3498db',
'accent': '#e74c3c',
'success': '#27ae60',
'warning': '#f39c12',
'light_bg': '#ecf0f1',
'dark_bg': '#34495e',
'text_light': '#ffffff',
'text_dark': '#2c3e50'
}
self.root.configure(bg=self.colors['light_bg'])
# 当前文件路径
self.current_file = None
self.text_modified = False
self.word_count = 0
self.char_count = 0
self.line_count = 1
# 设置图标(如果有的话)
try:
self.root.iconbitmap("notepad.ico")
except:
pass
self.setup_ui()
self.setup_bindings()
def setup_ui(self):
"""设置用户界面"""
# 创建菜单栏
self.create_menu_bar()
# 创建工具栏
self.create_toolbar()
# 创建状态栏
self.create_status_bar()
# 创建文本编辑区域
self.create_text_area()
def create_menu_bar(self):
"""创建菜单栏"""
menubar = tk.Menu(self.root)
self.root.config(menu=menubar)
# 文件菜单
file_menu = tk.Menu(menubar, 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_file, accelerator="Ctrl+Shift+S")
file_menu.add_separator()
file_menu.add_command(label="页面设置", command=self.page_setup)
file_menu.add_command(label="打印", command=self.print_file, accelerator="Ctrl+P")
file_menu.add_separator()
file_menu.add_command(label="退出", command=self.exit_app)
menubar.add_cascade(label="文件", menu=file_menu)
# 编辑菜单
edit_menu = tk.Menu(menubar, tearoff=0)
edit_menu.add_command(label="撤销", command=self.undo_text, accelerator="Ctrl+Z")
edit_menu.add_command(label="重做", command=self.redo_text, accelerator="Ctrl+Y")
edit_menu.add_separator()
edit_menu.add_command(label="剪切", command=self.cut_text, accelerator="Ctrl+X")
edit_menu.add_command(label="复制", command=self.copy_text, accelerator="Ctrl+C")
edit_menu.add_command(label="粘贴", command=self.paste_text, accelerator="Ctrl+V")
edit_menu.add_command(label="删除", command=self.delete_text, accelerator="Del")
edit_menu.add_separator()
edit_menu.add_command(label="全选", command=self.select_all, accelerator="Ctrl+A")
edit_menu.add_separator()
edit_menu.add_command(label="查找", command=self.find_replace, accelerator="Ctrl+F")
edit_menu.add_command(label="替换", command=self.find_replace, accelerator="Ctrl+H")
edit_menu.add_separator()
edit_menu.add_command(label="插入时间戳", command=self.insert_timestamp, accelerator="F5")
edit_menu.add_command(label="插入日期", command=self.insert_date, accelerator="F6")
menubar.add_cascade(label="编辑", menu=edit_menu)
# 格式菜单
format_menu = tk.Menu(menubar, tearoff=0)
format_menu.add_command(label="自动换行", command=self.toggle_word_wrap)
format_menu.add_separator()
format_menu.add_command(label="字体", command=self.choose_font)
format_menu.add_command(label="背景颜色", command=self.choose_bg_color)
format_menu.add_command(label="文本颜色", command=self.choose_text_color)
menubar.add_cascade(label="格式", menu=format_menu)
# 查看菜单
view_menu = tk.Menu(menubar, tearoff=0)
self.zoom_var = tk.StringVar(value="100%")
view_menu.add_command(label="放大", command=self.zoom_in, accelerator="Ctrl++")
view_menu.add_command(label="缩小", command=self.zoom_out, accelerator="Ctrl+-")
view_menu.add_command(label="恢复默认缩放", command=self.reset_zoom, accelerator="Ctrl+0")
view_menu.add_separator()
view_menu.add_command(label="自动换行", command=self.word_wrap)
view_menu.add_separator()
view_menu.add_command(label="状态栏", command=self.toggle_statusbar)
menubar.add_cascade(label="查看", menu=view_menu)
# 主题菜单
theme_menu = tk.Menu(menubar, tearoff=0)
theme_menu.add_command(label="浅色主题", command=lambda: self.change_theme('light'))
theme_menu.add_command(label="深色主题", command=lambda: self.change_theme('dark'))
theme_menu.add_command(label="护眼主题", command=lambda: self.change_theme('eye_care'))
menubar.add_cascade(label="主题", menu=theme_menu)
# 工具菜单
tools_menu = tk.Menu(menubar, tearoff=0)
tools_menu.add_command(label="备份管理器", command=self.show_backup_manager)
tools_menu.add_command(label="文本分析", command=self.show_statistics)
menubar.add_cascade(label="工具", menu=tools_menu)
# 帮助菜单
help_menu = tk.Menu(menubar, tearoff=0)
help_menu.add_command(label="帮助", command=self.show_help)
help_menu.add_command(label="关于", command=self.show_about)
menubar.add_cascade(label="帮助", menu=help_menu)
self.menubar = menubar
def create_toolbar(self):
"""创建现代化工具栏"""
toolbar_frame = tk.Frame(self.root, bg=self.colors['dark_bg'], relief=tk.FLAT, bd=0, height=50)
toolbar_frame.pack(side=tk.TOP, fill=tk.X)
toolbar_frame.pack_propagate(False) # 保持固定高度
# 工具栏按钮 - 现代化设计
buttons = [
("新建", self.new_file, "📄", self.colors['secondary']),
("打开", self.open_file, "📁", self.colors['secondary']),
("保存", self.save_file, "💾", self.colors['success']),
("", None, "│", None), # 分隔符
("剪切", self.cut_text, "✂️", self.colors['warning']),
("复制", self.copy_text, "📋", self.colors['warning']),
("粘贴", self.paste_text, "📎", self.colors['warning']),
("", None, "│", None),
("撤销", self.undo_text, "↶", self.colors['accent']),
("重做", self.redo_text, "↷", self.colors['accent']),
("", None, "│", None),
("查找", self.find_replace, "🔍", self.colors['primary']),
("替换", self.find_replace, "🔄", self.colors['primary']),
("", None, "│", None),
("统计", self.show_statistics, "📊", self.colors['success']),
("格式化", self.format_text, "✨", self.colors['secondary']),
]
for text, command, icon, color in buttons:
if text == "": # 分隔符
sep = tk.Label(toolbar_frame, text=icon, bg=self.colors['dark_bg'],
fg=self.colors['text_light'], font=('Arial', 12))
sep.pack(side=tk.LEFT, padx=8, pady=12)
else:
btn = tk.Button(toolbar_frame,
text=f"{icon}",
command=command,
relief=tk.FLAT,
bg=color,
fg=self.colors['text_light'],
font=('微软雅黑', 10, 'bold'),
width=4,
height=1,
cursor='hand2')
btn.pack(side=tk.LEFT, padx=3, pady=8)
# 添加工具提示
self.create_tooltip(btn, text)
# 添加搜索框
search_frame = tk.Frame(toolbar_frame, bg=self.colors['dark_bg'])
search_frame.pack(side=tk.RIGHT, padx=10, pady=8)
self.search_var = tk.StringVar()
search_entry = tk.Entry(search_frame, textvariable=self.search_var,
width=20, font=('微软雅黑', 10),
bg='white', fg=self.colors['text_dark'])
search_entry.pack(side=tk.LEFT, padx=(0, 5))
search_entry.bind('<Return>', lambda e: self.quick_search())
search_btn = tk.Button(search_frame, text="🔍",
command=self.quick_search,
relief=tk.FLAT, bg=self.colors['secondary'],
fg=self.colors['text_light'], font=('微软雅黑', 10))
search_btn.pack(side=tk.LEFT)
# 为搜索按钮添加工具提示
self.create_tooltip(search_btn, "快速搜索文本")
self.toolbar_frame = toolbar_frame
def create_status_bar(self):
"""创建现代化状态栏"""
self.status_bar = tk.Frame(self.root, bg=self.colors['dark_bg'], height=25)
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
self.status_bar.pack_propagate(False)
# 左侧状态信息
self.status_info = tk.Label(self.status_bar, text="就绪",
anchor=tk.W,
bg=self.colors['dark_bg'],
fg=self.colors['text_light'],
font=('微软雅黑', 9))
self.status_info.pack(side=tk.LEFT, padx=10, pady=3)
# 中间统计信息
stats_frame = tk.Frame(self.status_bar, bg=self.colors['dark_bg'])
stats_frame.pack(side=tk.LEFT, padx=20)
self.word_count_label = tk.Label(stats_frame, text="字数: 0",
bg=self.colors['dark_bg'],
fg=self.colors['text_light'],
font=('微软雅黑', 8))
self.word_count_label.pack(side=tk.LEFT, padx=5)
self.char_count_label = tk.Label(stats_frame, text="字符: 0",
bg=self.colors['dark_bg'],
fg=self.colors['text_light'],
font=('微软雅黑', 8))
self.char_count_label.pack(side=tk.LEFT, padx=5)
self.line_count_label = tk.Label(stats_frame, text="行数: 1",
bg=self.colors['dark_bg'],
fg=self.colors['text_light'],
font=('微软雅黑', 8))
self.line_count_label.pack(side=tk.LEFT, padx=5)
# 右侧光标位置
self.cursor_info = tk.Label(self.status_bar, text="第1行, 第1列",
anchor=tk.E,
bg=self.colors['dark_bg'],
fg=self.colors['text_light'],
font=('微软雅黑', 9))
self.cursor_info.pack(side=tk.RIGHT, padx=10, pady=3)
def create_text_area(self):
"""创建现代化文本编辑区域"""
# 创建主容器
text_container = tk.Frame(self.root, bg=self.colors['light_bg'])
text_container.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
# 创建滚动条 - 现代化样式
style = ttk.Style()
style.configure("Modern.Vertical.TScrollbar",
background=self.colors['secondary'],
troughcolor=self.colors['light_bg'],
bordercolor=self.colors['light_bg'],
arrowcolor=self.colors['text_light'],
gripcount=0)
v_scrollbar = ttk.Scrollbar(text_container, orient=tk.VERTICAL, style="Modern.Vertical.TScrollbar")
h_scrollbar = ttk.Scrollbar(text_container, orient=tk.HORIZONTAL, style="Modern.Vertical.TScrollbar")
# 创建文本框 - 现代化设计
self.text_area = tk.Text(text_container,
wrap=tk.WORD,
yscrollcommand=v_scrollbar.set,
xscrollcommand=h_scrollbar.set,
font=('微软雅黑', 11),
bg='#ffffff',
fg=self.colors['text_dark'],
selectbackground=self.colors['secondary'],
selectforeground='white',
insertbackground=self.colors['accent'],
relief=tk.FLAT,
bd=2,
padx=15,
pady=15,
undo=True,
maxundo=-1)
# 配置滚动条
v_scrollbar.config(command=self.text_area.yview)
h_scrollbar.config(command=self.text_area.xview)
# 布局 - 使用grid实现更好的控制
self.text_area.grid(row=0, column=0, sticky='nsew')
v_scrollbar.grid(row=0, column=1, sticky='ns')
h_scrollbar.grid(row=1, column=0, sticky='ew')
# 配置权重
text_container.grid_rowconfigure(0, weight=1)
text_container.grid_columnconfigure(0, weight=1)
self.text_area.focus_set()
# 绑定文本变化事件以更新统计信息
self.text_area.bind('<KeyRelease>', self.update_text_stats)
self.text_area.bind('<ButtonRelease>', self.update_text_stats)
def create_tooltip(self, widget, text):
"""创建工具提示"""
def on_enter(event):
# 创建工具提示窗口
x, y, _, _ = widget.bbox("insert")
x += widget.winfo_rootx() + 25
y += widget.winfo_rooty() + 25
# 创建提示窗口
self.tooltip = tk.Toplevel(widget)
self.tooltip.wm_overrideredirect(True)
self.tooltip.wm_geometry(f"+{x}+{y}")
label = tk.Label(self.tooltip, text=text,
bg='yellow', fg='black',
font=('微软雅黑', 8))
label.pack()
def on_leave(event):
if hasattr(self, 'tooltip') and self.tooltip:
self.tooltip.destroy()
widget.bind("<Enter>", on_enter)
widget.bind("<Leave>", on_leave)
def setup_bindings(self):
"""设置键盘绑定"""
# 文件操作
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_file())
self.root.bind('<Control-p>', lambda e: self.print_file())
# 编辑操作
self.root.bind('<Control-z>', lambda e: self.undo_text())
self.root.bind('<Control-y>', lambda e: self.redo_text())
self.root.bind('<Control-x>', lambda e: self.cut_text())
self.root.bind('<Control-c>', lambda e: self.copy_text())
self.root.bind('<Control-v>', lambda e: self.paste_text())
self.root.bind('<Control-a>', lambda e: self.select_all())
self.root.bind('<Control-f>', lambda e: self.find_text())
self.root.bind('<Control-h>', lambda e: self.replace_text())
self.root.bind('<F3>', lambda e: self.find_next())
self.root.bind('<Shift-F3>', lambda e: self.find_prev())
self.root.bind('<F5>', lambda e: self.insert_datetime())
# 缩放操作
self.root.bind('<Control-plus>', lambda e: self.zoom_in())
self.root.bind('<Control-minus>', lambda e: self.zoom_out())
self.root.bind('<Control-0>', lambda e: self.zoom_reset())
# 文本修改检测
self.text_area.bind('<<Modified>>', self.on_text_modified)
self.text_area.bind('<KeyRelease>', self.update_text_stats)
self.text_area.bind('<ButtonRelease-1>', self.update_cursor_info)
# 实时统计更新
self.text_area.bind('<KeyRelease>', self.update_text_stats)
# 文件操作功能
def new_file(self):
"""新建文件"""
if self.text_modified:
if not self.confirm_save():
return
self.text_area.delete(1.0, tk.END)
self.current_file = None
self.text_modified = False
self.update_title()
self.status_bar.config(text="新建文件")
def open_file(self):
"""打开文件"""
if self.text_modified:
if not self.confirm_save():
return
file_path = filedialog.askopenfilename(
title="打开文件",
filetypes=[
("文本文件", "*.txt"),
("所有文件", "*.*")
]
)
if file_path:
try:
with open(file_path, 'r', encoding='utf-8') as file:
content = file.read()
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, content)
self.current_file = file_path
self.text_modified = False
self.update_title()
self.status_bar.config(text=f"已打开: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("错误", f"无法打开文件: {str(e)}")
def save_file(self):
"""保存文件"""
if self.current_file:
try:
content = self.text_area.get(1.0, tk.END)
with open(self.current_file, 'w', encoding='utf-8') as file:
file.write(content)
self.text_modified = False
self.update_title()
self.status_bar.config(text=f"已保存: {os.path.basename(self.current_file)}")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {str(e)}")
else:
self.save_as_file()
def save_as_file(self):
"""另存为文件"""
file_path = filedialog.asksaveasfilename(
title="另存为",
defaultextension=".txt",
filetypes=[
("文本文件", "*.txt"),
("所有文件", "*.*")
]
)
if file_path:
try:
content = self.text_area.get(1.0, tk.END)
with open(file_path, 'w', encoding='utf-8') as file:
file.write(content)
self.current_file = file_path
self.text_modified = False
self.update_title()
self.status_bar.config(text=f"已保存: {os.path.basename(file_path)}")
except Exception as e:
messagebox.showerror("错误", f"无法保存文件: {str(e)}")
def confirm_save(self):
"""确认是否保存修改"""
result = messagebox.askyesnocancel(
"记事本",
"文件已修改,是否保存更改?"
)
if result is None: # 取消
return False
elif result: # 是
self.save_file()
return not self.text_modified
else: # 否
return True
def page_setup(self):
"""页面设置"""
messagebox.showinfo("页面设置", "页面设置功能开发中...")
def print_file(self):
"""打印文件"""
messagebox.showinfo("打印", "打印功能开发中...")
def exit_app(self):
"""退出应用"""
if self.text_modified:
if not self.confirm_save():
return
self.root.quit()
# 编辑功能
def undo_text(self):
"""撤销"""
try:
self.text_area.edit_undo()
except:
pass
def redo_text(self):
"""重做"""
try:
self.text_area.edit_redo()
except:
pass
def cut_text(self):
"""剪切"""
try:
self.text_area.event_generate("<<Cut>>")
except:
pass
def copy_text(self):
"""复制"""
try:
self.text_area.event_generate("<<Copy>>")
except:
pass
def paste_text(self):
"""粘贴"""
try:
self.text_area.event_generate("<<Paste>>")
except:
pass
def delete_text(self):
"""删除"""
try:
self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
except:
pass
def find_text(self):
"""查找文本"""
self.find_dialog = tk.Toplevel(self.root)
self.find_dialog.title("查找")
self.find_dialog.geometry("300x150")
self.find_dialog.transient(self.root)
self.find_dialog.resizable(False, False)
tk.Label(self.find_dialog, text="查找内容:").pack(pady=5)
self.find_entry = tk.Entry(self.find_dialog, width=30)
self.find_entry.pack(pady=5)
self.find_entry.focus()
button_frame = tk.Frame(self.find_dialog)
button_frame.pack(pady=10)
tk.Button(button_frame, text="查找下一个", command=self.find_next).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="取消", command=self.find_dialog.destroy).pack(side=tk.LEFT, padx=5)
self.find_entry.bind('<Return>', lambda e: self.find_next())
def find_next(self):
"""查找下一个"""
if hasattr(self, 'find_entry') and self.find_entry.get():
search_text = self.find_entry.get()
content = self.text_area.get(1.0, tk.END)
start_pos = self.text_area.index(tk.INSERT)
idx = content.find(search_text, int(start_pos.split('.')[1]))
if idx != -1:
line = content.count('\n', 0, idx) + 1
col = idx - content.rfind('\n', 0, idx)
self.text_area.tag_remove(tk.SEL, 1.0, tk.END)
self.text_area.tag_add(tk.SEL, f"{line}.{col}", f"{line}.{col+len(search_text)}")
self.text_area.mark_set(tk.INSERT, f"{line}.{col+len(search_text)}")
self.text_area.see(tk.INSERT)
else:
messagebox.showinfo("查找", "找不到指定的文本")
def find_prev(self):
"""查找上一个"""
if hasattr(self, 'find_entry') and self.find_entry.get():
search_text = self.find_entry.get()
content = self.text_area.get(1.0, tk.INSERT)
idx = content.rfind(search_text)
if idx != -1:
line = content.count('\n', 0, idx) + 1
col = idx - content.rfind('\n', 0, idx)
self.text_area.tag_remove(tk.SEL, 1.0, tk.END)
self.text_area.tag_add(tk.SEL, f"{line}.{col}", f"{line}.{col+len(search_text)}")
self.text_area.mark_set(tk.INSERT, f"{line}.{col}")
self.text_area.see(tk.INSERT)
else:
messagebox.showinfo("查找", "找不到指定的文本")
def replace_text(self):
"""替换文本"""
self.replace_dialog = tk.Toplevel(self.root)
self.replace_dialog.title("替换")
self.replace_dialog.geometry("350x200")
self.replace_dialog.transient(self.root)
self.replace_dialog.resizable(False, False)
tk.Label(self.replace_dialog, text="查找内容:").pack(pady=5)
self.replace_find_entry = tk.Entry(self.replace_dialog, width=30)
self.replace_find_entry.pack(pady=5)
tk.Label(self.replace_dialog, text="替换为:").pack(pady=5)
self.replace_with_entry = tk.Entry(self.replace_dialog, width=30)
self.replace_with_entry.pack(pady=5)
button_frame = tk.Frame(self.replace_dialog)
button_frame.pack(pady=10)
tk.Button(button_frame, text="查找下一个", command=self.replace_find_next).pack(side=tk.LEFT, padx=2)
tk.Button(button_frame, text="替换", command=self.replace_current).pack(side=tk.LEFT, padx=2)
tk.Button(button_frame, text="全部替换", command=self.replace_all).pack(side=tk.LEFT, padx=2)
tk.Button(button_frame, text="取消", command=self.replace_dialog.destroy).pack(side=tk.LEFT, padx=2)
def replace_find_next(self):
"""替换查找下一个"""
self.find_next()
def replace_current(self):
"""替换当前"""
if hasattr(self, 'replace_find_entry') and self.replace_find_entry.get():
if self.text_area.tag_ranges(tk.SEL):
selected_text = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
if selected_text == self.replace_find_entry.get():
self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
self.text_area.insert(tk.SEL_FIRST, self.replace_with_entry.get())
self.find_next()
def replace_all(self):
"""全部替换"""
if hasattr(self, 'replace_find_entry') and self.replace_find_entry.get():
content = self.text_area.get(1.0, tk.END)
new_content = content.replace(self.replace_find_entry.get(), self.replace_with_entry.get())
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, new_content)
def select_all(self):
"""全选"""
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)
def insert_datetime(self):
"""插入日期时间"""
from datetime import datetime
now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.text_area.insert(tk.INSERT, now)
# 格式功能
def toggle_word_wrap(self):
"""切换自动换行"""
current_wrap = self.text_area.cget("wrap")
if current_wrap == tk.WORD:
self.text_area.config(wrap=tk.NONE)
self.status_bar.config(text="自动换行: 关闭")
else:
self.text_area.config(wrap=tk.WORD)
self.status_bar.config(text="自动换行: 开启")
def choose_font(self):
"""选择字体"""
font_dialog = tk.Toplevel(self.root)
font_dialog.title("字体")
font_dialog.geometry("400x300")
font_dialog.transient(self.root)
# 字体选择
tk.Label(font_dialog, text="字体:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W)
font_family = ttk.Combobox(font_dialog, values=['微软雅黑', '宋体', '黑体', '楷体', 'Arial', 'Times New Roman'])
font_family.set('微软雅黑')
font_family.grid(row=0, column=1, padx=5, pady=5)
# 字号选择
tk.Label(font_dialog, text="大小:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
font_size = ttk.Combobox(font_dialog, values=['8', '9', '10', '11', '12', '14', '16', '18', '20', '24'])
font_size.set('12')
font_size.grid(row=1, column=1, padx=5, pady=5)
# 样式选择
tk.Label(font_dialog, text="样式:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W)
font_style = ttk.Combobox(font_dialog, values=['常规', '粗体', '斜体', '粗斜体'])
font_style.set('常规')
font_style.grid(row=2, column=1, padx=5, pady=5)
# 示例文本
sample_text = tk.Text(font_dialog, height=5, width=30)
sample_text.insert(1.0, "这是一段示例文本")
sample_text.grid(row=3, column=0, columnspan=2, padx=5, pady=10)
def apply_font():
family = font_family.get()
size = font_size.get()
style = font_style.get()
weight = 'normal'
slant = 'roman'
if '粗体' in style:
weight = 'bold'
if '斜体' in style:
slant = 'italic'
new_font = (family, size, weight, slant)
self.text_area.config(font=new_font)
sample_text.config(font=new_font)
def ok_clicked():
apply_font()
font_dialog.destroy()
button_frame = tk.Frame(font_dialog)
button_frame.grid(row=4, column=0, columnspan=2, pady=10)
tk.Button(button_frame, text="确定", command=ok_clicked).pack(side=tk.LEFT, padx=5)
tk.Button(button_frame, text="取消", command=font_dialog.destroy).pack(side=tk.LEFT, padx=5)
# 绑定事件
font_family.bind('<<ComboboxSelected>>', lambda e: apply_font())
font_size.bind('<<ComboboxSelected>>', lambda e: apply_font())
font_style.bind('<<ComboboxSelected>>', lambda e: apply_font())
def choose_bg_color(self):
"""选择背景颜色"""
color = colorchooser.askcolor(title="选择背景颜色")
if color[1]:
self.text_area.config(bg=color[1])
def choose_text_color(self):
"""选择文本颜色"""
color = colorchooser.askcolor(title="选择文本颜色")
if color[1]:
self.text_area.config(fg=color[1])
# 查看功能
def zoom_in(self):
"""放大"""
current_font = self.text_area.cget("font")
size = int(current_font[1])
if size < 72:
self.text_area.config(font=(current_font[0], size + 2))
self.update_zoom_info(size + 2)
def zoom_out(self):
"""缩小"""
current_font = self.text_area.cget("font")
size = int(current_font[1])
if size > 6:
self.text_area.config(font=(current_font[0], size - 2))
self.update_zoom_info(size - 2)
def zoom_reset(self):
"""重置缩放"""
self.text_area.config(font=('微软雅黑', 12))
self.update_zoom_info(12)
def update_zoom_info(self, size):
"""更新缩放信息"""
zoom_percent = int((size / 12) * 100)
self.zoom_var.set(f"{zoom_percent}%")
self.status_bar.config(text=f"缩放: {zoom_percent}%")
def toggle_statusbar(self):
"""切换状态栏显示"""
if self.status_bar.winfo_ismapped():
self.status_bar.pack_forget()
else:
self.status_bar.pack(side=tk.BOTTOM, fill=tk.X)
# 帮助功能
def show_help(self):
"""显示帮助"""
help_text = """作业五 - 记事本软件使用说明
文件操作:
- 新建: Ctrl+N
- 打开: Ctrl+O
- 保存: Ctrl+S
- 另存为: Ctrl+Shift+S
- 打印: Ctrl+P
编辑操作:
- 撤销: Ctrl+Z
- 重做: Ctrl+Y
- 剪切: Ctrl+X
- 复制: Ctrl+C
- 粘贴: Ctrl+V
- 全选: Ctrl+A
- 查找: Ctrl+F
- 替换: Ctrl+H
- 插入时间: F5
格式设置:
- 自动换行
- 字体设置
- 颜色设置
查看功能:
- 放大: Ctrl++
- 缩小: Ctrl+-
- 重置缩放: Ctrl+0"""
help_window = tk.Toplevel(self.root)
help_window.title("记事本帮助")
help_window.geometry("500x400")
text_area = tk.Text(help_window, wrap=tk.WORD, font=('微软雅黑', 10))
text_area.insert(1.0, help_text)
text_area.config(state=tk.DISABLED)
scrollbar = ttk.Scrollbar(help_window, orient=tk.VERTICAL, command=text_area.yview)
text_area.config(yscrollcommand=scrollbar.set)
text_area.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def show_about(self):
"""显示关于信息"""
about_text = """作业五 - 记事本软件
版本: 1.0
作者: 软件构造作业
功能: 完整的文本编辑器
包含文件操作、编辑功能、格式设置等完整功能。"""
messagebox.showinfo("关于记事本", about_text)
# 辅助功能
def update_title(self):
"""更新窗口标题"""
if self.current_file:
title = f"作业五 - 记事本软件 - {os.path.basename(self.current_file)}"
if self.text_modified:
title += " *"
else:
title = "作业五 - 记事本软件 - 无标题"
if self.text_modified:
title += " *"
self.root.title(title)
def on_text_modified(self, event=None):
"""文本修改事件"""
self.text_modified = True
self.update_title()
self.text_area.edit_modified(False)
def update_cursor_info(self, event=None):
"""更新光标位置信息"""
cursor_pos = self.text_area.index(tk.INSERT)
line, col = cursor_pos.split('.')
self.cursor_info.config(text=f"第{line}行, 第{int(col)+1}列")
def update_text_stats(self, event=None):
"""更新文本统计信息 - 改进版,准确识别中文字符"""
content = self.text_area.get(1.0, tk.END)
# 字符数(包括空格)
self.char_count = len(content) - 1 # 减去末尾的换行符
# 改进的字数统计:准确识别中文字符
# 中文:每个汉字算一个字
# 英文:按单词统计
chinese_chars = re.findall(r'[\u4e00-\u9fff]', content)
english_words = re.findall(r'[a-zA-Z]+', content)
# 中文字数 + 英文单词数
self.word_count = len(chinese_chars) + len(english_words)
# 行数
self.line_count = int(self.text_area.index('end-1c').split('.')[0])
# 段落数(以空行分隔)
paragraphs = re.split(r'\n\s*\n', content.strip())
self.paragraph_count = len(paragraphs) if content.strip() else 0
# 空格数
self.space_count = content.count(' ')
# 更新显示
self.word_count_label.config(text=f"字数: {self.word_count}")
self.char_count_label.config(text=f"字符: {self.char_count}")
self.line_count_label.config(text=f"行数: {self.line_count}")
# 更新光标位置
self.update_cursor_info()
def quick_search(self):
"""快速搜索功能"""
search_text = self.search_var.get()
if search_text:
content = self.text_area.get(1.0, tk.END)
# 清除之前的搜索结果
self.text_area.tag_remove("search", 1.0, tk.END)
# 高亮所有匹配项
start_idx = "1.0"
while True:
start_idx = self.text_area.search(search_text, start_idx, tk.END)
if not start_idx:
break
end_idx = f"{start_idx}+{len(search_text)}c"
self.text_area.tag_add("search", start_idx, end_idx)
start_idx = end_idx
# 设置高亮样式
self.text_area.tag_config("search", background="yellow", foreground="black")
# 跳转到第一个匹配项
first_match = self.text_area.search(search_text, "1.0", tk.END)
if first_match:
self.text_area.see(first_match)
self.text_area.mark_set(tk.INSERT, first_match)
def show_statistics(self):
"""显示详细统计信息 - 增强版,包含文本分析"""
content = self.text_area.get(1.0, tk.END)
# 计算各种统计信息
char_count = len(content) - 1
# 改进的字数统计
chinese_chars = re.findall(r'[\u4e00-\u9fff]', content)
english_words = re.findall(r'[a-zA-Z]+', content)
word_count = len(chinese_chars) + len(english_words)
line_count = int(self.text_area.index('end-1c').split('.')[0])
# 段落数(以空行分隔)
paragraphs = re.split(r'\n\s*\n', content.strip())
paragraph_count = len(paragraphs) if content.strip() else 0
# 空格数
space_count = content.count(' ')
# 文本分析
# 中文字符数
chinese_count = len(chinese_chars)
# 英文字符数
english_chars = re.findall(r'[a-zA-Z]', content)
english_char_count = len(english_chars)
# 数字字符数
digit_count = len(re.findall(r'\d', content))
# 标点符号数
punctuation_count = len(re.findall(r'[!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~]', content))
# 创建统计对话框
stats_window = tk.Toplevel(self.root)
stats_window.title("文本统计与分析")
stats_window.geometry("400x450")
stats_window.resizable(False, False)
# 居中显示
stats_window.transient(self.root)
stats_window.grab_set()
# 创建滚动框架
canvas = tk.Canvas(stats_window)
scrollbar = ttk.Scrollbar(stats_window, orient="vertical", command=canvas.yview)
scrollable_frame = tk.Frame(canvas)
scrollable_frame.bind(
"<Configure>",
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
)
canvas.create_window((0, 0), window=scrollable_frame, anchor="nw")
canvas.configure(yscrollcommand=scrollbar.set)
# 统计信息框架
stats_frame = tk.Frame(scrollable_frame, padx=20, pady=20)
stats_frame.pack(fill=tk.BOTH, expand=True)
# 标题
title_label = tk.Label(stats_frame, text="📊 文本统计与分析",
font=('微软雅黑', 16, 'bold'),
fg=self.colors['primary'])
title_label.pack(pady=(0, 20))
# 基础统计
basic_frame = tk.LabelFrame(stats_frame, text="基础统计", font=('微软雅黑', 12, 'bold'))
basic_frame.pack(fill=tk.X, pady=(0, 15))
basic_items = [
("📝 字符数(含空格)", f"{char_count}"),
("🔤 字数", f"{word_count}"),
("📄 行数", f"{line_count}"),
("📑 段落数", f"{paragraph_count}"),
("␣ 空格数", f"{space_count}"),
]
for label, value in basic_items:
item_frame = tk.Frame(basic_frame)
item_frame.pack(fill=tk.X, pady=3)
tk.Label(item_frame, text=label, font=('微软雅黑', 10)).pack(side=tk.LEFT)
tk.Label(item_frame, text=value, font=('微软雅黑', 10, 'bold'),
fg=self.colors['secondary']).pack(side=tk.RIGHT)
# 字符分析
char_frame = tk.LabelFrame(stats_frame, text="字符分析", font=('微软雅黑', 12, 'bold'))
char_frame.pack(fill=tk.X, pady=(0, 15))
char_items = [
("🔡 中文字符", f"{chinese_count}"),
("🔠 英文字符", f"{english_char_count}"),
("🔢 数字字符", f"{digit_count}"),
("❗ 标点符号", f"{punctuation_count}"),
]
for label, value in char_items:
item_frame = tk.Frame(char_frame)
item_frame.pack(fill=tk.X, pady=3)
tk.Label(item_frame, text=label, font=('微软雅黑', 10)).pack(side=tk.LEFT)
tk.Label(item_frame, text=value, font=('微软雅黑', 10, 'bold'),
fg=self.colors['secondary']).pack(side=tk.RIGHT)
# 文本分析建议
if char_count > 0:
analysis_frame = tk.LabelFrame(stats_frame, text="分析建议", font=('微软雅黑', 12, 'bold'))
analysis_frame.pack(fill=tk.X, pady=(0, 15))
# 计算比例
chinese_ratio = chinese_count / char_count * 100
english_ratio = english_char_count / char_count * 100
suggestions = []
if chinese_ratio > 70:
suggestions.append("📖 主要使用中文")
elif english_ratio > 70:
suggestions.append("📖 主要使用英文")
else:
suggestions.append("📖 中英文混合")
if line_count > 50:
suggestions.append("📄 文档较长")
elif line_count < 10:
suggestions.append("📄 文档较短")
if paragraph_count > 10:
suggestions.append("📑 段落结构清晰")
for suggestion in suggestions:
tk.Label(analysis_frame, text=suggestion, font=('微软雅黑', 10),
fg=self.colors['success']).pack(anchor=tk.W, pady=2)
# 关闭按钮
close_btn = tk.Button(stats_frame, text="关闭",
command=stats_window.destroy,
bg=self.colors['secondary'],
fg='white',
font=('微软雅黑', 10),
width=10)
close_btn.pack(pady=(15, 0))
# 布局滚动框架
canvas.pack(side="left", fill="both", expand=True)
scrollbar.pack(side="right", fill="y")
def find_replace(self):
"""查找和替换功能"""
# 创建查找替换对话框
find_window = tk.Toplevel(self.root)
find_window.title("查找和替换")
find_window.geometry("400x200")
find_window.resizable(False, False)
find_window.transient(self.root)
find_window.grab_set()
# 主框架
main_frame = tk.Frame(find_window, padx=20, pady=20)
main_frame.pack(fill=tk.BOTH, expand=True)
# 查找部分
tk.Label(main_frame, text="查找内容:", font=('微软雅黑', 10)).grid(row=0, column=0, sticky=tk.W, pady=5)
find_var = tk.StringVar()
find_entry = tk.Entry(main_frame, textvariable=find_var, width=30, font=('微软雅黑', 10))
find_entry.grid(row=0, column=1, pady=5, padx=5)
# 替换部分
tk.Label(main_frame, text="替换为:", font=('微软雅黑', 10)).grid(row=1, column=0, sticky=tk.W, pady=5)
replace_var = tk.StringVar()
replace_entry = tk.Entry(main_frame, textvariable=replace_var, width=30, font=('微软雅黑', 10))
replace_entry.grid(row=1, column=1, pady=5, padx=5)
# 选项
case_sensitive = tk.BooleanVar()
case_check = tk.Checkbutton(main_frame, text="区分大小写", variable=case_sensitive,
font=('微软雅黑', 9))
case_check.grid(row=2, column=0, columnspan=2, sticky=tk.W, pady=5)
# 按钮框架
btn_frame = tk.Frame(main_frame)
btn_frame.grid(row=3, column=0, columnspan=2, pady=15)
def find_next():
"""查找下一个匹配项"""
search_text = find_var.get()
if not search_text:
return
# 清除之前的搜索结果
self.text_area.tag_remove("search", 1.0, tk.END)
# 从当前光标位置开始查找
start_idx = self.text_area.index(tk.INSERT)
# 查找匹配项
if case_sensitive.get():
found_idx = self.text_area.search(search_text, start_idx, tk.END)
else:
found_idx = self.text_area.search(search_text.lower(), start_idx, tk.END, nocase=1)
if found_idx:
end_idx = f"{found_idx}+{len(search_text)}c"
self.text_area.tag_add("search", found_idx, end_idx)
self.text_area.tag_config("search", background="yellow", foreground="black")
self.text_area.see(found_idx)
self.text_area.mark_set(tk.INSERT, found_idx)
else:
messagebox.showinfo("查找", "未找到匹配项")
def replace():
"""替换当前匹配项"""
search_text = find_var.get()
replace_text = replace_var.get()
if not search_text:
return
# 获取当前选中的文本(应该是搜索高亮的部分)
try:
selected = self.text_area.get(tk.SEL_FIRST, tk.SEL_LAST)
if selected == search_text or (not case_sensitive.get() and selected.lower() == search_text.lower()):
self.text_area.delete(tk.SEL_FIRST, tk.SEL_LAST)
self.text_area.insert(tk.SEL_FIRST, replace_text)
find_next() # 查找下一个
except:
messagebox.showinfo("替换", "请先使用查找功能定位到要替换的文本")
def replace_all():
"""替换所有匹配项"""
search_text = find_var.get()
replace_text = replace_var.get()
if not search_text:
return
content = self.text_area.get(1.0, tk.END)
if case_sensitive.get():
new_content = content.replace(search_text, replace_text)
else:
new_content = re.sub(re.escape(search_text), replace_text, content, flags=re.IGNORECASE)
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, new_content)
messagebox.showinfo("替换", f"已替换所有匹配项")
# 按钮
tk.Button(btn_frame, text="查找下一个", command=find_next,
width=10).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="替换", command=replace,
width=10).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="全部替换", command=replace_all,
width=10).pack(side=tk.LEFT, padx=5)
tk.Button(btn_frame, text="关闭", command=find_window.destroy,
width=10).pack(side=tk.LEFT, padx=5)
# 焦点设置
find_entry.focus_set()
def format_text(self):
"""格式化文本功能"""
# 获取当前选中的文本
try:
# 保存选择范围的起始和结束位置
sel_start = self.text_area.index(tk.SEL_FIRST)
sel_end = self.text_area.index(tk.SEL_LAST)
selected_text = self.text_area.get(sel_start, sel_end)
# 创建格式化选项对话框
format_window = tk.Toplevel(self.root)
format_window.title("文本格式化")
format_window.geometry("400x300")
format_window.resizable(False, False)
format_window.transient(self.root)
format_window.grab_set()
# 格式化选项
options_frame = tk.Frame(format_window, padx=20, pady=20)
options_frame.pack(fill=tk.BOTH, expand=True)
tk.Label(options_frame, text="选择格式化选项:",
font=('微软雅黑', 12, 'bold')).pack(pady=(0, 15))
# 格式化函数
def to_upper():
formatted = selected_text.upper()
self.text_area.delete(sel_start, sel_end)
self.text_area.insert(sel_start, formatted)
# 重新选中格式化后的文本
self.text_area.tag_add(tk.SEL, sel_start, f"{sel_start} + {len(formatted)}c")
self.text_area.mark_set(tk.INSERT, sel_start)
format_window.destroy()
def to_lower():
formatted = selected_text.lower()
self.text_area.delete(sel_start, sel_end)
self.text_area.insert(sel_start, formatted)
# 重新选中格式化后的文本
self.text_area.tag_add(tk.SEL, sel_start, f"{sel_start} + {len(formatted)}c")
self.text_area.mark_set(tk.INSERT, sel_start)
format_window.destroy()
def capitalize_words():
formatted = ' '.join(word.capitalize() for word in selected_text.split())
self.text_area.delete(sel_start, sel_end)
self.text_area.insert(sel_start, formatted)
# 重新选中格式化后的文本
self.text_area.tag_add(tk.SEL, sel_start, f"{sel_start} + {len(formatted)}c")
self.text_area.mark_set(tk.INSERT, sel_start)
format_window.destroy()
def remove_extra_spaces():
# 保留文本中的换行符,只处理连续的空格
lines = selected_text.split('\n')
formatted_lines = []
for line in lines:
# 只替换连续的空格,保留单个空格
formatted_line = re.sub(r' +', ' ', line)
formatted_lines.append(formatted_line)
formatted = '\n'.join(formatted_lines)
self.text_area.delete(sel_start, sel_end)
self.text_area.insert(sel_start, formatted)
# 重新选中格式化后的文本
self.text_area.tag_add(tk.SEL, sel_start, f"{sel_start} + {len(formatted)}c")
self.text_area.mark_set(tk.INSERT, sel_start)
format_window.destroy()
# 按钮
tk.Button(options_frame, text="转换为大写", command=to_upper,
width=15).pack(pady=5)
tk.Button(options_frame, text="转换为小写", command=to_lower,
width=15).pack(pady=5)
tk.Button(options_frame, text="单词首字母大写", command=capitalize_words,
width=15).pack(pady=5)
tk.Button(options_frame, text="去除多余空格", command=remove_extra_spaces,
width=15).pack(pady=5)
tk.Button(options_frame, text="取消", command=format_window.destroy,
width=15).pack(pady=10)
except:
messagebox.showinfo("提示", "请先选择要格式化的文本")
def insert_timestamp(self):
"""插入时间戳"""
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.text_area.insert(tk.INSERT, timestamp)
def insert_date(self):
"""插入日期"""
date = datetime.datetime.now().strftime("%Y年%m月%d日")
self.text_area.insert(tk.INSERT, date)
def auto_save(self):
"""自动保存功能"""
if hasattr(self, 'current_file') and self.current_file:
try:
content = self.text_area.get(1.0, tk.END)
with open(self.current_file, 'w', encoding='utf-8') as f:
f.write(content)
# 在状态栏显示自动保存信息
self.status_var.set(f"自动保存完成 - {datetime.datetime.now().strftime('%H:%M:%S')}")
self.root.after(3000, lambda: self.status_var.set("")) # 3秒后清除状态
except Exception as e:
messagebox.showerror("自动保存错误", f"自动保存失败: {str(e)}")
def setup_auto_save(self):
"""设置自动保存定时器"""
# 每5分钟自动保存一次
self.root.after(300000, self.auto_save_timer) # 5分钟 = 300000毫秒
# 设置自动备份
self.setup_auto_backup()
def auto_save_timer(self):
"""自动保存定时器"""
if hasattr(self, 'current_file') and self.current_file:
self.auto_save()
# 重新设置定时器
self.root.after(300000, self.auto_save_timer)
def setup_auto_backup(self):
"""设置自动备份功能"""
self.auto_backup_enabled = True
self.auto_backup_interval = 60000 # 60秒
self.last_backup_time = time.time()
self.backup_count = 0
self.max_backups = 10 # 最多保留10个备份
# 创建备份目录
backup_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backups')
if not os.path.exists(backup_dir):
os.makedirs(backup_dir)
self.backup_dir = backup_dir
self.auto_backup()
def auto_backup(self):
"""自动备份功能"""
if self.auto_backup_enabled and self.current_file:
current_time = time.time()
if current_time - self.last_backup_time >= self.auto_backup_interval / 1000:
try:
content = self.text_area.get(1.0, tk.END)
if content.strip(): # 只在有内容时备份
# 生成备份文件名
timestamp = time.strftime("%Y%m%d_%H%M%S")
filename = os.path.basename(self.current_file)
backup_file = os.path.join(self.backup_dir, f"{filename}.backup_{timestamp}.txt")
with open(backup_file, 'w', encoding='utf-8') as f:
f.write(content)
self.backup_count += 1
self.last_backup_time = current_time
# 清理旧备份
self.cleanup_old_backups()
# 更新状态栏
self.status_var.set(f"自动备份完成 ({timestamp})")
self.root.after(3000, lambda: self.status_var.set(""))
except Exception as e:
print(f"自动备份失败: {e}")
# 继续定时器
if self.auto_backup_enabled:
self.root.after(self.auto_backup_interval, self.auto_backup)
def cleanup_old_backups(self):
"""清理旧的备份文件"""
try:
backup_files = []
for file in os.listdir(self.backup_dir):
if file.endswith('.txt') and 'backup_' in file:
file_path = os.path.join(self.backup_dir, file)
backup_files.append((file_path, os.path.getmtime(file_path)))
# 按修改时间排序
backup_files.sort(key=lambda x: x[1])
# 删除多余的备份文件
while len(backup_files) > self.max_backups:
oldest_file = backup_files.pop(0)[0]
os.remove(oldest_file)
except Exception as e:
print(f"清理备份文件失败: {e}")
def show_backup_manager(self):
"""显示备份管理器"""
if not hasattr(self, 'backup_dir') or not os.path.exists(self.backup_dir):
messagebox.showinfo("备份管理器", "暂无备份文件")
return
backup_window = tk.Toplevel(self.root)
backup_window.title("备份管理器")
backup_window.geometry("600x400")
backup_window.resizable(True, True)
# 居中显示
backup_window.transient(self.root)
backup_window.grab_set()
# 标题
title_label = tk.Label(backup_window, text="📂 备份管理器",
font=('微软雅黑', 16, 'bold'),
fg=self.colors['primary'])
title_label.pack(pady=10)
# 备份文件列表框架
list_frame = tk.Frame(backup_window)
list_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)
# 列表标题
header_frame = tk.Frame(list_frame)
header_frame.pack(fill=tk.X)
tk.Label(header_frame, text="备份文件", font=('微软雅黑', 12, 'bold')).pack(side=tk.LEFT)
tk.Label(header_frame, text="大小", font=('微软雅黑', 12, 'bold')).pack(side=tk.RIGHT)
# 备份文件列表
listbox_frame = tk.Frame(list_frame)
listbox_frame.pack(fill=tk.BOTH, expand=True)
scrollbar = ttk.Scrollbar(listbox_frame)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
self.backup_listbox = tk.Listbox(listbox_frame, yscrollcommand=scrollbar.set,
font=('微软雅黑', 10), selectmode=tk.SINGLE)
self.backup_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.config(command=self.backup_listbox.yview)
# 填充备份文件列表
backup_files = []
for file in os.listdir(self.backup_dir):
if file.endswith('.txt') and 'backup_' in file:
file_path = os.path.join(self.backup_dir, file)
file_size = os.path.getsize(file_path)
backup_files.append((file, file_size, file_path))
# 按文件名排序(时间戳排序)
backup_files.sort(reverse=True)
for file, size, file_path in backup_files:
size_str = f"{size} bytes" if size < 1024 else f"{size/1024:.1f} KB"
self.backup_listbox.insert(tk.END, f"{file} - {size_str}")
self.backup_listbox.file_paths = getattr(self.backup_listbox, 'file_paths', [])
self.backup_listbox.file_paths.append(file_path)
# 按钮框架
button_frame = tk.Frame(backup_window)
button_frame.pack(pady=10)
# 恢复按钮
restore_btn = tk.Button(button_frame, text="恢复选中备份",
command=self.restore_backup,
bg=self.colors['success'],
fg='white',
font=('微软雅黑', 10))
restore_btn.pack(side=tk.LEFT, padx=5)
# 删除按钮
delete_btn = tk.Button(button_frame, text="删除选中备份",
command=self.delete_backup,
bg=self.colors['danger'],
fg='white',
font=('微软雅黑', 10))
delete_btn.pack(side=tk.LEFT, padx=5)
# 关闭按钮
close_btn = tk.Button(button_frame, text="关闭",
command=backup_window.destroy,
bg=self.colors['secondary'],
fg='white',
font=('微软雅黑', 10))
close_btn.pack(side=tk.LEFT, padx=5)
def restore_backup(self):
"""恢复选中的备份"""
selection = self.backup_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择一个备份文件")
return
file_path = self.backup_listbox.file_paths[selection[0]]
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# 确认恢复
if messagebox.askyesno("确认恢复", "确定要恢复此备份吗?当前内容将被覆盖。"):
self.text_area.delete(1.0, tk.END)
self.text_area.insert(1.0, content)
messagebox.showinfo("成功", "备份已恢复")
except Exception as e:
messagebox.showerror("错误", f"恢复备份失败: {e}")
def delete_backup(self):
"""删除选中的备份"""
selection = self.backup_listbox.curselection()
if not selection:
messagebox.showwarning("警告", "请先选择一个备份文件")
return
file_path = self.backup_listbox.file_paths[selection[0]]
try:
if messagebox.askyesno("确认删除", "确定要删除此备份文件吗?"):
os.remove(file_path)
self.backup_listbox.delete(selection[0])
del self.backup_listbox.file_paths[selection[0]]
messagebox.showinfo("成功", "备份文件已删除")
except Exception as e:
messagebox.showerror("错误", f"删除备份失败: {e}")
def word_wrap(self):
"""切换自动换行"""
current_wrap = self.text_area.cget("wrap")
if current_wrap == "none":
self.text_area.config(wrap="word")
self.status_var.set("自动换行已开启")
else:
self.text_area.config(wrap="none")
self.status_var.set("自动换行已关闭")
self.root.after(2000, lambda: self.status_var.set(""))
def zoom_in(self):
"""放大字体"""
current_font = self.text_font
current_size = int(current_font.split()[-1])
new_size = min(current_size + 2, 36) # 最大36号字体
self.text_font = f"微软雅黑 {new_size}"
self.text_area.config(font=(self.text_font))
self.status_var.set(f"字体大小: {new_size}pt")
self.root.after(2000, lambda: self.status_var.set(""))
def zoom_out(self):
"""缩小字体"""
current_font = self.text_font
current_size = int(current_font.split()[-1])
new_size = max(current_size - 2, 8) # 最小8号字体
self.text_font = f"微软雅黑 {new_size}"
self.text_area.config(font=(self.text_font))
self.status_var.set(f"字体大小: {new_size}pt")
self.root.after(2000, lambda: self.status_var.set(""))
def reset_zoom(self):
"""重置字体大小"""
self.text_font = "微软雅黑 12"
self.text_area.config(font=(self.text_font))
self.status_var.set("字体大小已重置")
self.root.after(2000, lambda: self.status_var.set(""))
def change_theme(self, theme_name):
"""切换主题"""
themes = {
'light': {
'primary': '#2c3e50',
'secondary': '#3498db',
'accent': '#e74c3c',
'success': '#27ae60',
'warning': '#f39c12',
'light_bg': '#ecf0f1',
'dark_bg': '#34495e',
'text_light': '#ffffff',
'text_dark': '#2c3e50',
'text_area_bg': '#ffffff',
'text_area_fg': '#2c3e50'
},
'dark': {
'primary': '#3498db',
'secondary': '#2c3e50',
'accent': '#e74c3c',
'success': '#27ae60',
'warning': '#f39c12',
'light_bg': '#2c3e50',
'dark_bg': '#1a252f',
'text_light': '#ffffff',
'text_dark': '#ecf0f1',
'text_area_bg': '#34495e',
'text_area_fg': '#ecf0f1'
},
'eye_care': {
'primary': '#2c3e50',
'secondary': '#27ae60',
'accent': '#e74c3c',
'success': '#27ae60',
'warning': '#f39c12',
'light_bg': '#f0f8ff',
'dark_bg': '#4682b4',
'text_light': '#ffffff',
'text_dark': '#2c3e50',
'text_area_bg': '#f5f5f5',
'text_area_fg': '#2c3e50'
}
}
if theme_name in themes:
self.colors = themes[theme_name]
# 更新界面颜色
self.root.configure(bg=self.colors['light_bg'])
self.toolbar_frame.configure(bg=self.colors['dark_bg'])
self.status_bar.configure(bg=self.colors['dark_bg'])
# 更新文本区域
self.text_area.configure(
bg=self.colors['text_area_bg'],
fg=self.colors['text_area_fg'],
selectbackground=self.colors['secondary']
)
# 更新状态栏标签
for widget in self.status_bar.winfo_children():
if isinstance(widget, tk.Label):
widget.configure(bg=self.colors['dark_bg'], fg=self.colors['text_light'])
# 更新工具栏按钮
for widget in self.toolbar_frame.winfo_children():
if isinstance(widget, tk.Button):
widget.configure(bg=self.colors['secondary'], fg=self.colors['text_light'])
self.status_var.set(f"已切换到{theme_name}主题")
self.root.after(2000, lambda: self.status_var.set(""))
def main():
"""主函数"""
root = tk.Tk()
app = NotepadApp(root)
# 设置自动保存
app.setup_auto_save()
root.mainloop()
if __name__ == "__main__":
main()