@@ -58,6 +58,14 @@ def __init__(self, startup_action=None, geometry_override=None):
58
58
self .main_menu = None
59
59
self .was_iconic = False # 初始化窗口状态跟踪属性
60
60
self .is_dialog_open = False # 防止对话框多开
61
+ self .week_preview_window = None # 周课表预览窗口实例
62
+ self .tomorrow_preview_shown_for_today = False # 今天是否已显示过明日预览
63
+
64
+ # --- 课表视图状态管理 ---
65
+ self .displayed_weekday = datetime .now ().weekday () # 当前显示的星期,0-6
66
+ self .view_reset_timer = None # 视图自动重置计时器
67
+ self .swipe_start_x = 0 # 滑动起始x坐标
68
+ # -------------------------
61
69
62
70
logger .log_debug ("Initializing schedule" )
63
71
self ._initialize_schedule ()
@@ -108,9 +116,13 @@ def cleanup_resources(self):
108
116
"""清理所有资源"""
109
117
try :
110
118
# 关闭所有子窗口
111
- for window in [self .editor_window , self .settings_window , self .about_window ]:
112
- if window and window .window .winfo_exists ():
113
- window .window .destroy ()
119
+ for window in [self .editor_window , self .settings_window , self .about_window , self .week_preview_window ]:
120
+ if window and window .winfo_exists ():
121
+ # 兼容不同窗口对象的销毁方式
122
+ if hasattr (window , 'window' ) and window .window .winfo_exists ():
123
+ window .window .destroy ()
124
+ else :
125
+ window .destroy ()
114
126
115
127
# 取消所有定时器
116
128
for timer_id in getattr (self , 'timer_ids' , []):
@@ -225,16 +237,25 @@ def _initialize_ui(self) -> None:
225
237
226
238
def _create_time_display (self ) -> None :
227
239
"""创建时间显示区域"""
228
- self .time_label = tk .Label (
229
- self .root ,
240
+ self .time_date_label = tk .Label (
241
+ self .root ,
230
242
font = ("微软雅黑" , self .config_handler .time_display_size , "bold" ),
231
243
fg = self .config_handler .font_color ,
232
244
bg = "#ecf0f1"
233
245
)
234
- self .time_label .pack (pady = self .config_handler .vertical_padding , fill = tk .X )
235
-
246
+ self .time_date_label .pack (fill = tk .X )
247
+
248
+ self .weekday_label = tk .Label (
249
+ self .root ,
250
+ font = ("微软雅黑" , self .config_handler .time_display_size , "bold" ),
251
+ fg = self .config_handler .font_color ,
252
+ bg = "#ecf0f1"
253
+ )
254
+ self .weekday_label .pack (fill = tk .X )
255
+
236
256
# 添加点击事件
237
- self .time_label .bind ("<Button-1>" , self ._on_time_label_click )
257
+ self .time_date_label .bind ("<Button-1>" , self ._on_time_label_click )
258
+ self .weekday_label .bind ("<Button-1>" , self ._on_time_label_click )
238
259
239
260
def _on_time_label_click (self , event ):
240
261
"""处理时间标签点击事件"""
@@ -332,6 +353,7 @@ def _create_schedule_display(self) -> None:
332
353
self .schedule_frame .pack (padx = self .config_handler .horizontal_padding ,
333
354
pady = self .config_handler .vertical_padding ,
334
355
fill = tk .BOTH , expand = True )
356
+ self ._bind_schedule_events ()
335
357
336
358
def _start_update_loop (self ) -> None :
337
359
"""启动界面更新循环"""
@@ -350,10 +372,25 @@ def update_display(self) -> None:
350
372
351
373
# 只有秒数变化时才更新UI
352
374
if current_second != self .last_second :
353
- self ._update_time_display (now )
354
375
self ._update_countdown_display (now )
355
- self ._update_schedule_display (now )
376
+
377
+ # 根据当前是显示当天还是预览来更新时间/星期显示
378
+ if self .displayed_weekday == now .weekday ():
379
+ # 正常更新时间、秒数和星期
380
+ self ._update_time_display (now )
381
+ # 课表内容也只在显示当天时才随时间更新状态
382
+ self ._update_schedule_display (now .weekday ())
383
+ else :
384
+ # 保持预览状态的显示(日期不变,星期为斜体)
385
+ self .time_date_label .config (text = now .strftime ("%Y-%m-%d" ))
386
+ self .weekday_label .config (
387
+ text = f"星期{ WEEKDAYS [self .displayed_weekday ]} " ,
388
+ font = ("微软雅黑" , self .config_handler .time_display_size , "bold italic" )
389
+ )
390
+
356
391
self .last_second = current_second
392
+
393
+ self ._check_and_show_tomorrow_preview (now )
357
394
358
395
self ._schedule_next_update ()
359
396
@@ -369,11 +406,14 @@ def update_display(self) -> None:
369
406
370
407
def _update_time_display (self , now : datetime ) -> None :
371
408
"""更新时间显示"""
372
- weekday = now .weekday ( )
373
- self .time_label .config (
374
- text = now .strftime ( "%Y-%m-%d \n %H:%M:%S \n " ) +
375
- f"星期 { WEEKDAYS [ weekday ] } "
409
+ self . time_date_label . config ( text = now .strftime ( "%Y-%m-%d \n %H:%M:%S" ) )
410
+ self .weekday_label .config (
411
+ text = f"星期 { WEEKDAYS [ now .weekday ()] } " ,
412
+ font = ( "微软雅黑" , self . config_handler . time_display_size , "bold" )
376
413
)
414
+ # 如果是新的一天,重置预览标志
415
+ if now .hour == 0 and now .minute == 0 and now .second == 0 :
416
+ self .tomorrow_preview_shown_for_today = False
377
417
378
418
def _update_countdown_display (self , now : datetime ) -> None :
379
419
"""更新倒计时显示"""
@@ -384,13 +424,30 @@ def _update_countdown_display(self, now: datetime) -> None:
384
424
else :
385
425
self .countdown_label2 .config (text = str (delta ), fg = self .config_handler .font_color )
386
426
387
- def _update_schedule_display (self , now : datetime ) -> None :
388
- """更新课程表显示"""
427
+ def _update_schedule_display (self , weekday_to_show : int ) -> None :
428
+ """更新课程表显示
429
+ Args:
430
+ weekday_to_show (int): 要显示的星期 (0-6).
431
+ """
389
432
if not hasattr (self , 'course_labels' ):
390
433
self .course_labels = []
391
-
392
- weekday = str (now .weekday ())
393
- today_schedule = self .schedule ["schedules" ][self .schedule ["current_schedule" ]].get (weekday , [])
434
+
435
+ now = datetime .now ()
436
+ weekday_str = str (weekday_to_show )
437
+
438
+ # 更新时间标签以反映当前显示的星期
439
+ displayed_day_str = f"星期{ WEEKDAYS [weekday_to_show ]} "
440
+ if weekday_to_show != now .weekday ():
441
+ self .time_date_label .config (text = now .strftime ("%Y-%m-%d" ))
442
+ self .weekday_label .config (
443
+ text = displayed_day_str ,
444
+ font = ("微软雅黑" , self .config_handler .time_display_size , "bold italic" )
445
+ )
446
+ else :
447
+ # 仅在显示当天时才更新秒数
448
+ self ._update_time_display (now )
449
+
450
+ schedule_for_day = self .schedule ["schedules" ][self .schedule ["current_schedule" ]].get (weekday_str , [])
394
451
395
452
# 在更新前清除所有课程时间缓存
396
453
self ._course_time_cache .clear ()
@@ -399,7 +456,7 @@ def _update_schedule_display(self, now: datetime) -> None:
399
456
self .course_labels = [label for label in self .course_labels if label .winfo_exists ()]
400
457
401
458
# 根据当前课表重新排列所有标签
402
- for i , course in enumerate (today_schedule ):
459
+ for i , course in enumerate (schedule_for_day ):
403
460
color = self ._get_course_color (now , course )
404
461
if i < len (self .course_labels ):
405
462
# 强制更新标签颜色状态
@@ -411,7 +468,7 @@ def _update_schedule_display(self, now: datetime) -> None:
411
468
self ._create_new_label (course , color , row = i )
412
469
413
470
# 移除多余的标签
414
- self ._remove_extra_labels (today_schedule )
471
+ self ._remove_extra_labels (schedule_for_day )
415
472
416
473
def _update_course_labels (self , now : datetime , schedule : List [Dict [str , str ]]) -> None :
417
474
"""更新或创建课程标签"""
@@ -426,6 +483,10 @@ def _update_course_labels(self, now: datetime, schedule: List[Dict[str, str]]) -
426
483
427
484
def _get_course_color (self , now : datetime , course : Dict [str , str ]) -> str :
428
485
"""根据课程时间获取显示颜色"""
486
+ # 如果显示的不是当天的课表,则所有课程都显示为“未开始”状态
487
+ if self .displayed_weekday != now .weekday ():
488
+ return "red"
489
+
429
490
# 使用课程名称+时间作为缓存键
430
491
cache_key = f"{ course ['name' ]} _{ course ['start_time' ]} _{ course ['end_time' ]} "
431
492
@@ -474,10 +535,9 @@ def _update_existing_label(self, index: int, course: Dict[str, str], color: str,
474
535
def _update_font_settings (self ) -> None :
475
536
"""更新所有UI组件的字体设置"""
476
537
# 更新时间显示
477
- self .time_label .config (
478
- font = ("微软雅黑" , self .config_handler .font_size , "bold" ),
479
- fg = self .config_handler .font_color
480
- )
538
+ font_config = ("微软雅黑" , self .config_handler .font_size , "bold" )
539
+ self .time_date_label .config (font = font_config , fg = self .config_handler .font_color )
540
+ self .weekday_label .config (font = font_config , fg = self .config_handler .font_color )
481
541
482
542
# 更新课程标签色块尺寸
483
543
if hasattr (self , 'course_labels' ):
@@ -543,6 +603,10 @@ def _create_new_label(self, course: Dict[str, str], color: str, row: int) -> Non
543
603
fg = self .config_handler .font_color ,
544
604
anchor = 'w'
545
605
)
606
+
607
+ # --- 事件绑定 ---
608
+ self ._bind_events_to_widget (label )
609
+ self ._bind_events_to_widget (course_frame )
546
610
label .pack (side = tk .LEFT , fill = tk .X , expand = True )
547
611
548
612
# 根据字体大小计算色块尺寸
@@ -563,6 +627,110 @@ def _create_new_label(self, course: Dict[str, str], color: str, row: int) -> Non
563
627
564
628
self .schedule_frame .grid_columnconfigure (0 , weight = 1 )
565
629
630
+ def _bind_schedule_events (self ):
631
+ """为课表框架及其所有子控件绑定事件"""
632
+ self ._bind_events_to_widget (self .schedule_frame )
633
+ for child in self .schedule_frame .winfo_children ():
634
+ self ._bind_events_to_widget (child )
635
+ if isinstance (child , tk .Frame ):
636
+ for grandchild in child .winfo_children ():
637
+ self ._bind_events_to_widget (grandchild )
638
+
639
+ def _bind_events_to_widget (self , widget ):
640
+ """辅助函数:为单个控件绑定所有需要的事件"""
641
+ widget .bind ("<Button-1>" , self ._on_schedule_press )
642
+ widget .bind ("<B1-Motion>" , self ._on_schedule_drag )
643
+ widget .bind ("<Double-Button-1>" , self ._on_schedule_double_click )
644
+ widget .bind ("<Triple-Button-1>" , self ._on_schedule_triple_click )
645
+
646
+ def _on_schedule_press (self , event ):
647
+ """处理课表区域的鼠标按下事件"""
648
+ self .swipe_start_x = event .x
649
+ # 如果有重置计时器在运行,则取消它
650
+ if self .view_reset_timer :
651
+ self .root .after_cancel (self .view_reset_timer )
652
+ self .view_reset_timer = None
653
+
654
+ def _on_schedule_drag (self , event ):
655
+ """处理课表区域的拖动事件(滑动)"""
656
+ if not self .swipe_start_x :
657
+ return
658
+
659
+ delta_x = event .x - self .swipe_start_x
660
+ # 设置一个滑动阈值,防止过于敏感
661
+ if abs (delta_x ) > 30 :
662
+ direction = 'left' if delta_x < 0 else 'right'
663
+ self ._handle_swipe (direction )
664
+ self .swipe_start_x = 0 # 重置起始位置,防止一次长滑动触发多次
665
+
666
+ def _on_schedule_double_click (self , event ):
667
+ """处理课表区域的双击事件,立即重置视图"""
668
+ self ._reset_schedule_view_to_today ()
669
+
670
+ def _handle_swipe (self , direction : str ):
671
+ """处理滑动逻辑,切换显示的星期"""
672
+ if direction == 'left' :
673
+ self .displayed_weekday = (self .displayed_weekday + 1 ) % 7
674
+ else :
675
+ self .displayed_weekday = (self .displayed_weekday - 1 + 7 ) % 7
676
+
677
+ self ._update_schedule_display (self .displayed_weekday )
678
+ self ._start_view_reset_timer ()
679
+
680
+ def _start_view_reset_timer (self ):
681
+ """启动一个计时器,在5秒后将视图重置回当天"""
682
+ # 如果已有计时器,先取消
683
+ if self .view_reset_timer :
684
+ self .root .after_cancel (self .view_reset_timer )
685
+
686
+ # 启动新的5秒计时器
687
+ self .view_reset_timer = self .root .after (5000 , self ._reset_schedule_view_to_today )
688
+
689
+ def _on_schedule_double_click (self , event ):
690
+ """处理课表区域的双击事件,立即重置视图"""
691
+ self ._reset_schedule_view_to_today ()
692
+
693
+ def _on_schedule_triple_click (self , event ):
694
+ """处理课表区域的三击事件,开关周课表预览"""
695
+ from tools .week_preview import WeekPreviewWindow
696
+
697
+ # 如果窗口已存在,则销毁它
698
+ if self .week_preview_window and self .week_preview_window .winfo_exists ():
699
+ self .week_preview_window .destroy ()
700
+ self .week_preview_window = None
701
+ else :
702
+ # 创建并显示新窗口
703
+ self .week_preview_window = WeekPreviewWindow (self .root , self )
704
+ self .week_preview_window .show ()
705
+
706
+ def _handle_swipe (self , direction : str ):
707
+ """处理滑动逻辑,切换显示的星期"""
708
+ if direction == 'left' :
709
+ self .displayed_weekday = (self .displayed_weekday + 1 ) % 7
710
+ else :
711
+ self .displayed_weekday = (self .displayed_weekday - 1 + 7 ) % 7
712
+
713
+ self ._update_schedule_display (self .displayed_weekday )
714
+ self ._start_view_reset_timer ()
715
+
716
+ def _start_view_reset_timer (self ):
717
+ """启动一个计时器,在5秒后将视图重置回当天"""
718
+ # 如果已有计时器,先取消
719
+ if self .view_reset_timer :
720
+ self .root .after_cancel (self .view_reset_timer )
721
+
722
+ # 启动新的5秒计时器
723
+ self .view_reset_timer = self .root .after (5000 , self ._reset_schedule_view_to_today )
724
+
725
+ def _reset_schedule_view_to_today (self ):
726
+ """将课表视图重置为显示当天的课程"""
727
+ self .view_reset_timer = None
728
+ today_weekday = datetime .now ().weekday ()
729
+ if self .displayed_weekday != today_weekday :
730
+ self .displayed_weekday = today_weekday
731
+ self ._update_schedule_display (self .displayed_weekday )
732
+
733
+
566
734
def _remove_extra_labels (self , schedule : List [Dict [str , str ]]) -> None :
567
735
"""移除多余的课程标签"""
568
736
# 移除所有超出当前课程数量的标签
@@ -670,3 +838,41 @@ def _create_updater_instance(self, Updater):
670
838
if self .updater is None :
671
839
self .updater = Updater (self .root )
672
840
self .updater .start_background_check ()
841
+
842
+ def _check_and_show_tomorrow_preview (self , now : datetime ):
843
+ """检查是否需要显示明日课表预览"""
844
+ if not self .config_handler .auto_preview_tomorrow_enabled :
845
+ return
846
+ if self .tomorrow_preview_shown_for_today :
847
+ return
848
+ if self .week_preview_window and self .week_preview_window .winfo_exists ():
849
+ return
850
+
851
+ # 检查当天的所有课程是否都已结束
852
+ today_weekday_str = str (now .weekday ())
853
+ current_schedule_name = self .schedule .get ("current_schedule" , "default" )
854
+ schedule_data = self .schedule .get ("schedules" , {}).get (current_schedule_name , {})
855
+ courses_today = schedule_data .get (today_weekday_str , [])
856
+
857
+ if not courses_today :
858
+ return # 今天没课,不触发
859
+
860
+ all_courses_finished = True
861
+ last_course_end_time = None
862
+ for course in courses_today :
863
+ try :
864
+ end_time = datetime .strptime (course ['end_time' ], "%H:%M" ).time ()
865
+ if last_course_end_time is None or end_time > last_course_end_time :
866
+ last_course_end_time = end_time
867
+ if now .time () <= end_time :
868
+ all_courses_finished = False
869
+ break
870
+ except (ValueError , KeyError ):
871
+ continue # 忽略格式错误的课程
872
+
873
+ # 如果所有课程都结束了,且当前时间在最后一节课结束后,则触发
874
+ if all_courses_finished and now .time () > last_course_end_time :
875
+ from tools .week_preview import WeekPreviewWindow
876
+ self .week_preview_window = WeekPreviewWindow (self .root , self , day_offset = 1 )
877
+ self .week_preview_window .show ()
878
+ self .tomorrow_preview_shown_for_today = True
0 commit comments