-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathText_Manager.py
More file actions
3988 lines (3265 loc) · 155 KB
/
Copy pathText_Manager.py
File metadata and controls
3988 lines (3265 loc) · 155 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
__version__ = "6.44.0"
__build_date__ = "2025-05-15"
__author__ = "杜玛"
__license__ = "MIT"
__copyright__ = "© 2025 杜玛"
__url__ = "https://github.com/duma520"
import sys
import sqlite3
import re
import datetime
import time
import markdown
import os
import math
import glob
from pypinyin import lazy_pinyin
# 布局类
from PyQt5.QtWidgets import QVBoxLayout, QHBoxLayout, QGridLayout, QFormLayout
# 控件类
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QLabel, QLineEdit,
QTextEdit, QPushButton, QListWidget, QMessageBox, QComboBox,
QStatusBar, QTabWidget, QFileDialog, QTreeWidget, QTreeWidgetItem,
QInputDialog, QAction, QMenu, QScrollArea, QShortcut, QDialog,
QDialogButtonBox, QCheckBox, QSpinBox, QDateEdit, QGroupBox,
QListWidgetItem, QToolBar, QFontComboBox, QToolButton, QButtonGroup,
QTableWidget, QTableWidgetItem, QHeaderView, QProgressBar
)
from PyQt5.QtCore import Qt, QSize, QTimer, QDate, QMimeData, QEvent
from PyQt5.QtGui import QFont, QIcon, QTextCursor, QKeySequence, QPainter, QColor
from PyQt5.QtChart import QChart, QPieSeries, QChartView
class TextManager(QMainWindow):
# 类变量 - 集中管理关于信息
ABOUT = {
"name": "高级文本管理工具",
"version": __version__,
"build_date": __build_date__,
"author": __author__,
"license": __license__,
"copyright": __copyright__,
"url": __url__,
"description": "一个功能强大的文本管理工具,支持多种格式和高级搜索功能",
"features": [
"支持纯文本、Markdown和HTML格式",
"全文搜索和高级筛选",
"标签和分类管理",
"回收站功能",
"批量操作",
"文本分析和统计"
]
}
# 类变量 - 集中管理配置参数
SIMILAR_TEXT_DISPLAY_COUNT = 0 # 控制显示的相似文章数量,0表示显示全部
def __init__(self):
print("[启动检查] 1. 进入__init__") # 基础检查点1
super().__init__()
print("[启动检查] 2. 父类初始化完成") # 基础检查点2
title = f"{self.ABOUT['name']} v{self.ABOUT['version']} (Build {self.ABOUT['build_date']})"
self.setWindowTitle(title)
self.setWindowIcon(QIcon('icon.ico'))
# 初始化变量
self.current_view = "normal" # normal/recycle_bin
self.current_id = None
self.db_version = 2 # 当前数据库最新版本
self.default_format = 2 # 默认使用即见即所得模式
# 初始化数据库和UI
self.init_db() # 现在包含版本检查和升级
self.init_ui()
self.init_shortcuts()
# 安装事件过滤器(添加在这里↓↓↓)
self.btn_new.installEventFilter(self)
print("[DEBUG] 事件过滤器已安装") # 调试确认
# 加载初始数据
self.load_categories()
self.load_tags()
self.load_text_list()
self.load_search_history()
# 自动保存定时器
self.auto_save_timer = QTimer()
self.auto_save_timer.timeout.connect(self.auto_save)
self.auto_save_timer.start(30000) # 30秒自动保存
# 添加全局弹窗样式
self.setStyleSheet("""
/* 通用弹窗按钮样式 */
QMessageBox QPushButton, QDialog QPushButton {
min-width: 80px;
padding: 6px 12px;
border-radius: 4px;
border: 1px solid #cbd5e1;
background-color: #e2e8f0;
color: #1e293b; /* 深灰色文字 */
}
QMessageBox QPushButton:hover, QDialog QPushButton:hover {
background-color: #cbd5e1;
}
/* 确认/提交类按钮 */
QPushButton[type="submit"], QPushButton[role="accept"] {
background-color: #10b981; /* 绿色 */
color: white;
}
/* 取消/关闭类按钮 */
QPushButton[type="cancel"], QPushButton[role="reject"] {
background-color: #94a3b8; /* 灰色 */
color: white;
}
/* 进度条样式 */
QProgressBar {
border: 1px solid #cbd5e1;
border-radius: 4px;
text-align: center;
height: 20px;
}
QProgressBar::chunk {
background-color: #3b82f6;
border-radius: 3px;
}
""")
# 备份配置
self.backup_config = {
'max_backups': 30, # 最大备份数量
'backup_dir': os.path.join(os.path.dirname(__file__), 'backups'),
'backup_prefix': 'text_manager_backup_'
}
# 确保备份目录存在
os.makedirs(self.backup_config['backup_dir'], exist_ok=True)
# 调试
print(f"[INIT] 新建按钮连接状态: {self.btn_new.receivers(self.btn_new.clicked) > 0}")
def basic_click_test():
print("[DEBUG] 按钮被点击!") # 最基础的点击确认
self.btn_new.clicked.connect(basic_click_test)
print(f"[DEBUG] 基础点击测试连接状态: {self.btn_new.receivers(self.btn_new.clicked)}")
def init_db(self):
"""初始化数据库并检查升级"""
self.conn = sqlite3.connect('text_manager_enhanced.db')
self.cursor = self.conn.cursor()
# 启用SQLite全文搜索
self.cursor.execute("PRAGMA journal_mode=WAL")
# 创建版本控制表(如果不存在)
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS db_version (
version INTEGER PRIMARY KEY,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 获取当前数据库版本
self.cursor.execute('SELECT version FROM db_version ORDER BY version DESC LIMIT 1')
current_version = self.cursor.fetchone()
current_version = current_version[0] if current_version else 0
# 执行必要的升级
self.upgrade_database(current_version)
# 初始化表结构
self.init_tables()
self.init_default_shortcuts()
self.conn.commit()
def upgrade_database(self, current_version):
"""执行数据库升级"""
if current_version < 1:
# 初始版本创建
self.init_tables()
self.cursor.execute('INSERT INTO db_version (version) VALUES (1)')
print("数据库初始化为版本1")
if current_version < 2:
# 版本2升级:添加is_html列
try:
self.cursor.execute('ALTER TABLE texts ADD COLUMN is_html BOOLEAN DEFAULT 0')
self.cursor.execute('INSERT INTO db_version (version) VALUES (2)')
print("数据库升级到版本2:添加HTML支持")
except sqlite3.OperationalError as e:
if "duplicate column" not in str(e):
raise
# 未来版本升级可以在此继续添加
# if current_version < 3:
# self.upgrade_to_version_3()
def init_tables(self):
"""初始化所有表结构(不含版本控制)"""
self.cursor.executescript('''
CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
parent_id INTEGER DEFAULT 0,
color TEXT DEFAULT '#FFFFFF'
);
CREATE TABLE IF NOT EXISTS texts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT,
category_id INTEGER DEFAULT 0,
is_markdown BOOLEAN DEFAULT 0,
is_html BOOLEAN DEFAULT 0,
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
update_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
word_count INTEGER DEFAULT 0,
chinese_count INTEGER DEFAULT 0,
english_count INTEGER DEFAULT 0,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
CREATE TABLE IF NOT EXISTS texts_fts (
id INTEGER PRIMARY KEY,
title TEXT,
content TEXT
);
CREATE VIRTUAL TABLE IF NOT EXISTS texts_fts USING fts5(
title, content,
tokenize="porter unicode61"
);
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
color TEXT DEFAULT '#FFFFFF'
);
CREATE TABLE IF NOT EXISTS text_tags (
text_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (text_id, tag_id),
FOREIGN KEY (text_id) REFERENCES texts(id),
FOREIGN KEY (tag_id) REFERENCES tags(id)
);
CREATE TABLE IF NOT EXISTS templates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
content TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS search_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
query TEXT NOT NULL,
search_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS recycle_bin (
id INTEGER PRIMARY KEY AUTOINCREMENT,
original_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
deleted_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS shortcuts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
action TEXT NOT NULL UNIQUE,
shortcut TEXT NOT NULL
);
''')
def init_default_shortcuts(self):
"""初始化默认快捷键"""
default_shortcuts = [
('save', 'Ctrl+S'),
('new', 'Ctrl+N'),
('delete', 'Del'),
('search', 'Ctrl+F'),
('toggle_preview', 'Ctrl+P'),
('toggle_view', 'Ctrl+Shift+R')
]
for action, shortcut in default_shortcuts:
self.cursor.execute(
"INSERT OR IGNORE INTO shortcuts (action, shortcut) VALUES (?, ?)",
(action, shortcut)
)
def init_ui(self):
self.MACARON_COLORS = [
# ======================
# 标准马卡龙16色
# ======================
# 粉色系(4色)
QColor(255, 183, 206), # #FFB7CE 经典樱花粉
QColor(255, 209, 220), # #FFD1DC 泡泡糖粉
QColor(255, 215, 225), # #FFD7E1 草莓奶霜
QColor(255, 227, 227), # #FFE3E3 浅珊瑚粉
# 蓝色系(3色)
QColor(162, 225, 246), # #A2E1F6 天空蓝
QColor(200, 255, 240), # #C8FFF0 冰蓝
QColor(214, 232, 255), # #D6E8FF 淡 powder 蓝
# 绿色系(3色)
QColor(181, 234, 215), # #B5EAD7 薄荷绿
QColor(210, 255, 210), # #D2FFD2 苹果绿
QColor(220, 245, 220), # #DCF5DC 嫩绿
# 黄色/橙色系(3色)
QColor(255, 234, 165), # #FFEAA5 柠檬黄
QColor(255, 240, 200), # #FFF0C8 奶油黄
QColor(255, 218, 193), # #FFDAC1 蜜桃橙
# 紫色系(2色)
QColor(199, 206, 234), # #C7CEEA 薰衣草紫
QColor(216, 191, 216), # #D8BFD8 香芋紫
# 中性色(1色)
QColor(240, 230, 221), # #F0E6DD 焦糖奶霜
]
print("[UI初始化] 1. 开始初始化UI")
"""初始化用户界面(功能色区分版)"""
# 基于功能分色的专业样式表
self.setStyleSheet("""
/* ========== 基础样式 ========== */
QMainWindow {
background-color: #f8fafc;
}
QWidget {
font-family: 'Segoe UI', 'Microsoft YaHei';
font-size: 13px;
}
/* ========== 功能按钮色彩系统 ========== */
/* 主操作按钮基础样式 */
QPushButton {
min-width: 80px;
padding: 7px 12px;
border-radius: 4px;
font-weight: 500;
border: none;
color: white;
}
/* 1. 新建 - 创造型操作 (活力蓝) */
QPushButton.new-action {
background-color: #8b5cf6; /* 薰衣草紫 */
}
QPushButton.new-action:hover {
background-color: #7c3aed;
}
QPushButton.new-action:pressed {
background-color: #6d28d9;
}
/* 2. 保存 - 关键操作 (安全绿) */
QPushButton.save-action {
background-color: #10b981; /* 薄荷绿 */
}
QPushButton.save-action:hover {
background-color: #059669;
}
QPushButton.save-action:pressed {
background-color: #047857;
}
/* 3. 删除/危险操作 (警示红) */
QPushButton.danger-action {
background-color: #ef4444; /* 浅玫瑰 */
}
QPushButton.danger-action:hover {
background-color: #dc2626;
}
QPushButton.danger-action:pressed {
background-color: #b91c1c;
}
/* 4. 文本分析 - 分析型操作 (智慧紫) */
QPushButton.analyze-action {
background-color: #7c3aed; /* 深薰衣草 */
}
QPushButton.analyze-action:hover {
background-color: #6d28d9;
}
QPushButton.analyze-action:pressed {
background-color: #5b21b6;
}
/* 5. 复制/导出 - 数据操作 (友好橙) */
QPushButton.data-action {
background-color: #f97316; /* 杏色 */
}
QPushButton.data-action:hover {
background-color: #ea580c;
}
QPushButton.data-action:pressed {
background-color: #c2410c;
}
/* 6. 辅助操作 (中性灰) */
QPushButton.secondary-action {
background-color: #94a3b8;
color: #f8fafc;
}
QPushButton.secondary-action:hover {
background-color: #64748b;
}
/* 视图切换按钮 - 特殊状态色 (淡丁香) */
QPushButton.view-toggle-action {
background-color: #a78bfa;
color: white;
}
QPushButton.view-toggle-action:hover {
background-color: #8b5cf6;
}
QPushButton.view-toggle-action:pressed {
background-color: #7c3aed;
}
/* 批量操作按钮 - 特殊操作色 (浅青) */
QPushButton.batch-action {
background-color: #5eead4;
color: white;
}
QPushButton.batch-action:hover {
background-color: #2dd4bf;
}
QPushButton.batch-action:pressed {
background-color: #14b8a6;
}
/* 批量操作对话框按钮样式 */
QDialog QPushButton {
min-width: 80px;
padding: 6px 12px;
border-radius: 4px;
}
QDialog QPushButton[type="submit"] {
background-color: #3b82f6; /* 蓝色确认按钮 */
color: white;
}
QDialog QPushButton[type="cancel"] {
background-color: #94a3b8; /* 灰色取消按钮 */
color: white;
}
""")
self.resize(1200, 800)
self.setMinimumSize(QSize(900, 600))
print(f"[UI初始化] 2. 主窗口尺寸: {self.size()}")
print(f"[UI初始化] 3. 新建按钮存在: {hasattr(self, 'btn_new')}")
if hasattr(self, 'btn_new'):
print(f"[UI初始化] 4. 按钮状态 - 可见: {self.btn_new.isVisible()}, 启用: {self.btn_new.isEnabled()}")
self.btn_new.setStyleSheet("background: red;") # 强制设置醒目颜色
# 主布局
self.main_widget = QWidget()
self.setCentralWidget(self.main_widget)
self.main_layout = QHBoxLayout()
self.main_widget.setLayout(self.main_layout)
# 左侧面板 (分类树+文本列表)
self.left_panel = QWidget()
self.left_layout = QVBoxLayout()
self.left_panel.setLayout(self.left_layout)
# 视图切换按钮(使用特殊状态色)
self.view_toggle_btn = QPushButton("切换到回收站")
self.view_toggle_btn.setProperty("class", "view-toggle-action") # 添加专属类名
self.view_toggle_btn.setCursor(Qt.PointingHandCursor)
self.view_toggle_btn.clicked.connect(self.toggle_view)
self.left_layout.addWidget(self.view_toggle_btn)
# 分类树
self.category_tree = QTreeWidget()
self.category_tree.setHeaderLabel('分类')
self.category_tree.itemClicked.connect(self.filter_by_category)
self.left_layout.addWidget(self.category_tree)
# 高级搜索组
self.advanced_search_group = QGroupBox("高级搜索")
self.advanced_search_layout = QVBoxLayout()
# 日期范围搜索
self.date_search_layout = QHBoxLayout()
self.date_from = QDateEdit()
self.date_from.setDate(QDate.currentDate().addMonths(-1))
self.date_to = QDateEdit()
self.date_to.setDate(QDate.currentDate())
self.date_search_layout.addWidget(QLabel("从:"))
self.date_search_layout.addWidget(self.date_from)
self.date_search_layout.addWidget(QLabel("到:"))
self.date_search_layout.addWidget(self.date_to)
self.advanced_search_layout.addLayout(self.date_search_layout)
# 字数范围
self.word_count_layout = QHBoxLayout()
self.word_count_min = QSpinBox()
self.word_count_min.setRange(0, 99999)
self.word_count_max = QSpinBox()
self.word_count_max.setRange(0, 99999)
self.word_count_max.setValue(99999)
self.word_count_layout.addWidget(QLabel("字数:"))
self.word_count_layout.addWidget(self.word_count_min)
self.word_count_layout.addWidget(QLabel("-"))
self.word_count_layout.addWidget(self.word_count_max)
self.advanced_search_layout.addLayout(self.word_count_layout)
# 搜索模式
self.search_mode = QComboBox()
self.search_mode.addItems(["普通搜索", "全文检索"])
self.advanced_search_layout.addWidget(self.search_mode)
self.advanced_search_group.setLayout(self.advanced_search_layout)
self.advanced_search_group.setCheckable(True)
self.advanced_search_group.setChecked(False)
self.left_layout.addWidget(self.advanced_search_group)
# 标签云
self.tag_cloud = QComboBox()
self.tag_cloud.setEditable(True)
self.tag_cloud.setPlaceholderText("选择或输入标签...")
self.tag_cloud.currentTextChanged.connect(self.filter_by_tag)
self.left_layout.addWidget(QLabel('标签筛选:'))
self.left_layout.addWidget(self.tag_cloud)
# 搜索历史
self.search_history_combo = QComboBox()
self.search_history_combo.setPlaceholderText("搜索历史...")
self.search_history_combo.currentTextChanged.connect(self.apply_search_history)
self.left_layout.addWidget(QLabel('搜索历史:'))
self.left_layout.addWidget(self.search_history_combo)
# 搜索区域
self.search_input = QLineEdit()
self.search_input.setPlaceholderText('搜索标题/内容/拼音首字母...')
self.search_input.textChanged.connect(self.search_texts)
self.left_layout.addWidget(self.search_input)
# 文本列表
self.text_list = QListWidget()
self.text_list.itemClicked.connect(self.load_text)
self.left_layout.addWidget(self.text_list)
# 批量操作按钮(使用特殊操作色)
self.batch_btn = QPushButton(QIcon.fromTheme('system-run'), "批量操作")
self.batch_btn.setProperty("class", "batch-action") # 添加专属类名
self.batch_btn.setCursor(Qt.PointingHandCursor)
self.batch_btn.clicked.connect(self.show_batch_operations)
self.left_layout.addWidget(self.batch_btn)
# 右侧面板 (编辑区)
self.right_panel = QTabWidget()
self.main_layout.addWidget(self.left_panel, 3)
self.main_layout.addWidget(self.right_panel, 7)
# 创建编辑选项卡
print("[DEBUG] === 开始创建编辑界面 ===")
print(f"[DEBUG] 调用 create_edit_tab()")
self.create_edit_tab()
self.create_preview_tab()
# 状态栏
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
# 字数统计
self.word_count_label = QLabel('字数: 0 (中:0 英:0)')
self.reading_time_label = QLabel('阅读时间: 0分钟')
self.status_bar.addPermanentWidget(self.word_count_label)
self.status_bar.addPermanentWidget(self.reading_time_label)
# 自动保存指示器
self.save_indicator = QLabel('✅ 已自动保存')
self.status_bar.addPermanentWidget(self.save_indicator)
self.save_indicator.setVisible(False)
# 添加阅读进度条
self.reading_progress = QProgressBar()
self.reading_progress.setMaximum(100)
self.reading_progress.setMinimum(0)
self.reading_progress.setFixedWidth(150)
self.reading_progress.setFormat("进度: %p%")
self.reading_progress.setVisible(False) # 默认隐藏
self.status_bar.addPermanentWidget(self.reading_progress)
# 添加阅读进度标签
self.reading_progress_label = QLabel("0%")
self.reading_progress_label.setFixedWidth(40)
self.status_bar.addPermanentWidget(self.reading_progress_label)
# 菜单栏
self.create_menus()
# 确保按钮可见且可点击
print(f"[DEBUG] 新建按钮可见性: {self.btn_new.isVisible()}")
print(f"[DEBUG] 新建按钮启用状态: {self.btn_new.isEnabled()}")
self.btn_new.raise_() # 确保按钮在最上层
# 调试
print(f"[UI_INIT] 新建按钮对象: {self.btn_new}")
print(f"[UI_INIT] 新建按钮连接状态: {self.btn_new.receivers(self.btn_new.clicked) > 0}")
def show_text_analysis(self):
"""显示文本分析对话框"""
if not hasattr(self, 'current_id') or not self.current_id:
QMessageBox.warning(self, "警告", "请先选择要分析的文本!")
return
# 获取当前文本格式
format_index = self.format_combo.currentIndex()
# 根据格式获取内容
try:
if format_index == 0: # 纯文本
content = self.content_input.toPlainText()
elif format_index == 1: # Markdown
content = self.content_input.toPlainText()
else: # HTML
content = self.wysiwyg_editor.toPlainText() if self.wysiwyg_editor.isVisible() else self.content_input.toPlainText()
if not content.strip():
QMessageBox.warning(self, "警告", "当前文本内容为空!")
return
except Exception as e:
QMessageBox.warning(self, "错误", f"获取文本内容失败: {str(e)}")
return
# 使用主窗口状态栏的进度条
self.reading_progress.setVisible(True)
self.reading_progress.setRange(0, 100)
self.reading_progress.setValue(0)
self.reading_progress.setFormat("分析进度: %p%")
dialog = QDialog(self)
dialog.setWindowTitle("文本分析")
dialog.resize(800, 600)
layout = QVBoxLayout()
# 创建选项卡
tab_widget = QTabWidget()
# 1. 基本统计选项卡
stats_tab = QWidget()
stats_layout = QVBoxLayout()
# 添加更多统计信息
self.stats_info = QTextEdit()
self.stats_info.setReadOnly(True)
stats_layout.addWidget(self.stats_info)
# 字数统计图表
self.stats_chart_view = QChartView()
stats_layout.addWidget(self.stats_chart_view)
# 关键词提取
self.keywords_label = QLabel("关键词: ")
stats_layout.addWidget(self.keywords_label)
# 添加段落统计
self.paragraph_stats = QLabel("段落统计: ")
stats_layout.addWidget(self.paragraph_stats)
stats_tab.setLayout(stats_layout)
tab_widget.addTab(stats_tab, "基本统计")
# 2. 相似文本选项卡 (增强版)
similar_tab = QWidget()
similar_layout = QVBoxLayout()
# 相似度分析说明
similarity_desc = QLabel("基于以下特征计算相似度:")
similar_layout.addWidget(similarity_desc)
# 相似度特征表格
self.similarity_table = QTableWidget()
self.similarity_table.setColumnCount(3)
self.similarity_table.setHorizontalHeaderLabels(["特征", "权重", "贡献值"])
self.similarity_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
similar_layout.addWidget(self.similarity_table)
# 相似文本列表 (增强)
self.similar_texts_list = QListWidget()
self.similar_texts_list.setStyleSheet("""
QListWidget::item {
border-bottom: 1px solid #eee;
padding: 8px;
}
QListWidget::item:hover {
background: #f5f5f5;
}
""")
self.similar_texts_list.itemClicked.connect(self.show_similarity_detail)
similar_layout.addWidget(QLabel(f"最相似的{self.SIMILAR_TEXT_DISPLAY_COUNT}篇文本:"))
similar_layout.addWidget(self.similar_texts_list)
# 相似度详情面板
self.similarity_detail = QTextEdit()
self.similarity_detail.setReadOnly(True)
self.similarity_detail.setFixedHeight(150)
similar_layout.addWidget(QLabel("相似度分析详情:"))
similar_layout.addWidget(self.similarity_detail)
similar_tab.setLayout(similar_layout)
tab_widget.addTab(similar_tab, "相似文本")
# 3. 文本特征选项卡 (增强版)
features_tab = QWidget()
features_layout = QVBoxLayout()
# 特征概览卡片
features_group = QGroupBox("文本特征概览")
features_grid = QGridLayout()
# 1. 可读性卡片
readability_card = QGroupBox("📖 可读性")
readability_layout = QVBoxLayout()
self.readability_score = QLabel("正在计算...")
self.readability_bar = QProgressBar()
self.readability_bar.setTextVisible(False)
self.readability_bar.setStyleSheet("""
QProgressBar {
border: 1px solid #ddd;
border-radius: 3px;
height: 10px;
}
QProgressBar::chunk {
background: #4CAF50;
}
""")
readability_layout.addWidget(self.readability_score)
readability_layout.addWidget(self.readability_bar)
readability_card.setLayout(readability_layout)
# 2. 情感分析卡片
sentiment_card = QGroupBox("😊 情感倾向")
sentiment_layout = QVBoxLayout()
self.sentiment_label = QLabel("正在分析...")
self.sentiment_graph = QLabel()
self.sentiment_graph.setFixedHeight(30)
sentiment_layout.addWidget(self.sentiment_label)
sentiment_layout.addWidget(self.sentiment_graph)
sentiment_card.setLayout(sentiment_layout)
# 3. 关键词卡片
keywords_card = QGroupBox("🔑 关键词云")
keywords_layout = QVBoxLayout()
self.keywords_label = QLabel()
self.keywords_label.setWordWrap(True)
keywords_layout.addWidget(self.keywords_label)
keywords_card.setLayout(keywords_layout)
# 4. 风格特征卡片
style_card = QGroupBox("✍️ 写作风格")
style_layout = QVBoxLayout()
self.style_label = QLabel("正在分析...")
style_layout.addWidget(self.style_label)
style_card.setLayout(style_layout)
# 添加到网格
features_grid.addWidget(readability_card, 0, 0)
features_grid.addWidget(sentiment_card, 0, 1)
features_grid.addWidget(keywords_card, 1, 0)
features_grid.addWidget(style_card, 1, 1)
features_group.setLayout(features_grid)
features_layout.addWidget(features_group)
# 详细特征表格
self.features_table = QTableWidget()
self.features_table.setColumnCount(3)
self.features_table.setHorizontalHeaderLabels(["特征类型", "特征值", "说明"])
self.features_table.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
features_layout.addWidget(self.features_table)
features_tab.setLayout(features_layout)
tab_widget.addTab(features_tab, "文本特征")
# 添加选项卡到对话框
layout.addWidget(tab_widget)
# 添加"正在分析"标签和进度条
self.analyzing_label = QLabel("正在分析文本,请稍候...")
self.analyzing_label.setAlignment(Qt.AlignCenter)
self.analyzing_label.setStyleSheet("font-size: 14px; color: #555;")
layout.addWidget(self.analyzing_label)
self.analysis_progress = QProgressBar()
self.analysis_progress.setRange(0, 100)
self.analysis_progress.setValue(0)
layout.addWidget(self.analysis_progress)
# 添加分析按钮
analyze_btn = QPushButton("开始分析")
analyze_btn.clicked.connect(lambda: self.analyze_text(dialog, content))
layout.addWidget(analyze_btn)
dialog.setLayout(layout)
# 创建后立即执行分析
self.analyze_text(dialog, content)
dialog.exec_()
# 分析完成后恢复进度条原始状态
self.reading_progress.setFormat("进度: %p%")
self.reading_progress.setValue(0)
def analyze_text(self, dialog, content):
"""执行文本分析"""
print("[DEBUG] 开始文本分析")
# 初始化进度条
self.reading_progress.setValue(5)
QApplication.processEvents() # 强制更新UI
try:
# 1. 初始化统计信息文本框
self.stats_info.clear() # 先清空内容
print("[DEBUG] 初始化统计信息文本框")
self.reading_progress.setValue(10)
QApplication.processEvents()
# 2. 基本统计
self.update_basic_stats(content)
print("[DEBUG] 基本统计信息更新")
self.reading_progress.setValue(20)
QApplication.processEvents()
# 3. 关键词提取
keywords = self.extract_keywords(content)
self.keywords_label.setText(f"关键词: {', '.join(keywords)}")
print("[DEBUG] 关键词提取:", keywords)
self.reading_progress.setValue(35)
QApplication.processEvents()
# 4. 查找相似文本
self.find_similar_texts(content)
self.reading_progress.setValue(50)
QApplication.processEvents()
# 5. 新增段落统计
paragraph_count = len([p for p in content.split('\n') if p.strip()])
self.paragraph_stats.setText(f"段落统计: {paragraph_count}段")
print("[DEBUG] 段落统计:", paragraph_count)
self.reading_progress.setValue(60)
QApplication.processEvents()
# 6. 完整版可读性评分计算 (Flesch Reading Ease + 中文适配)
# 英文部分计算 (Flesch Reading Ease)
english_words = re.findall(r'\b[a-zA-Z]+\b', content)
english_sentences = re.findall(r'[.!?]+', content)
flesch_score = 0
if english_words and english_sentences:
avg_words_per_sentence = len(english_words) / len(english_sentences)
avg_syllables_per_word = sum(len(re.findall(r'[aeiouyAEIOUY]+', word)) for word in english_words) / len(english_words)
flesch_score = 206.835 - (1.015 * avg_words_per_sentence) - (84.6 * avg_syllables_per_word)
# 中文部分计算 (基于平均句长和词汇难度)
chinese_chars = re.findall(r'[\u4e00-\u9fff]', content)
chinese_sentences = re.split(r'[。!?;;]+', content)
chinese_sentences = [s for s in chinese_sentences if s.strip()]
chinese_score = 0
if chinese_chars and chinese_sentences:
avg_chars_per_sentence = len(chinese_chars) / len(chinese_sentences)
# 中文可读性经验公式 (基于句长和常用词比例)
common_word_ratio = len(re.findall(r'[的了是在有这我你他我们他们]', content)) / len(chinese_chars)
chinese_score = 100 - (avg_chars_per_sentence * 0.5) + (common_word_ratio * 20)
# 综合评分 (根据中英文内容比例)
total_chars = len(content)
if total_chars > 0:
english_ratio = len(''.join(english_words)) / total_chars
chinese_ratio = len(''.join(chinese_chars)) / total_chars
readability = (flesch_score * english_ratio + chinese_score * chinese_ratio)
readability = max(0, min(100, readability)) # 限制在0-100范围内
# 评分描述
if readability >= 90:
level = "非常容易"
description = (
"文本极其易读,适合所有读者,包括小学生。\n"
"典型文本:儿童读物、简单对话、基础说明文。\n"
"平均句子长度:8个词或更少\n"
"平均每词音节数:1.0或更少"
)
elif readability >= 80:
level = "容易"
description = (
"文本非常易读,适合普通大众阅读。\n"
"典型文本:流行小说、报纸文章、博客文章。\n"
"平均句子长度:8-12个词\n"
"平均每词音节数:1.0-1.2"
)
elif readability >= 70:
level = "较容易"
description = (
"文本比较容易理解,适合13-15岁学生。\n"
"典型文本:青少年读物、杂志文章。\n"
"平均句子长度:12-15个词\n"
"平均每词音节数:1.2-1.4"
)
elif readability >= 60:
level = "标准"
description = (
"文本难度适中,适合高中毕业生阅读。\n"
"典型文本:普通报刊、大众非小说类书籍。\n"
"平均句子长度:15-17个词\n"
"平均每词音节数:1.4-1.6"
)
elif readability >= 50:
level = "较难"
description = (
"文本有一定难度,适合大学生阅读。\n"
"典型文本:学术论文、专业杂志、技术文档。\n"
"平均句子长度:17-20个词\n"
"平均每词音节数:1.6-1.8"
)
elif readability >= 30:
level = "困难"
description = (
"文本难度较高,需要专业知识或高等教育背景。\n"
"典型文本:法律文件、学术论文、专业文献。\n"
"平均句子长度:20-25个词\n"
"平均每词音节数:1.8-2.0"
)
else:
level = "非常困难"
description = (
"文本极其难懂,需要专业领域知识。\n"
"典型文本:哲学著作、高级技术规范、古典文学。\n"
"平均句子长度:25个词以上\n"
"平均每词音节数:2.0以上"
)
self.readability_score.setText(
f"可读性评分: {readability:.1f}/100 ({level})\n"
f"英文部分: {flesch_score:.1f} 中文部分: {chinese_score:.1f}"
)
else:
self.readability_score.setText("可读性评分: 无有效内容")
print(f"[DEBUG] 可读性评分: {readability:.1f} (英文:{flesch_score:.1f} 中文:{chinese_score:.1f})")
self.reading_progress.setValue(10)
QApplication.processEvents()
# 7. 完整版情感分析 (支持中英文混合+程度分析)
# 扩展的情感词典 (包含程度词和否定词处理)
sentiment_dict = {
# 中文情感词 (带权重)
'positive': {
'好': 1, '优秀': 2, '成功': 2, '高兴': 1.5, '满意': 1.5,
'喜欢': 1, '爱': 2, '开心': 1.5, '幸福': 2, '棒': 1,
'完美': 2, '精彩': 1.5, '美丽': 1, '聪明': 1, '强大': 1
},
'negative': {
'坏': 1, '差': 1, '失败': 2, '伤心': 1.5, '不满': 1.5,
'讨厌': 1.5, '恨': 2, '痛苦': 2, '糟糕': 1.5, '愚蠢': 1.5,
'难看': 1, '弱': 1, '困难': 1, '麻烦': 1, '失望': 1.5
},
# 英文情感词
'en_positive': {
'good': 1, 'excellent': 2, 'success': 2, 'happy': 1.5, 'satisfied': 1.5,
'like': 1, 'love': 2, 'joy': 1.5, 'great': 1.5, 'perfect': 2
},
'en_negative': {
'bad': 1, 'poor': 1, 'fail': 2, 'sad': 1.5, 'angry': 1.5,
'hate': 2, 'pain': 2, 'terrible': 1.5, 'stupid': 1.5, 'ugly': 1
},
# 程度副词
'intensifiers': {
'非常': 1.5, '特别': 1.5, '极其': 2, '十分': 1.3, '相当': 1.2,
'有点': 0.8, '稍微': 0.7, '略微': 0.7, '过于': 1.3,
'very': 1.5, 'extremely': 2, 'highly': 1.5, 'quite': 1.2