Skip to content

Commit 5ef9614

Browse files
committed
feat: 增加课程时间显示模式和灵活的预览触发器
- **功能: 当前课程时间显示模式** - 新增三种显示模式,用于正在进行的课程: - **默认模式**: 显示课程开始时间。 - **结束时间模式**: 显示课程的结束时间。 - **倒计时模式**: 实时显示距离课程结束的倒计时。 - 在设置窗口中添加了相应的选项,允许用户自由切换。 - **功能: "明日课表预览"触发器** - 新增设置项,允许用户指定在第N节课结束后触发明日课表预览。 - 当设置为0时,将保持原有逻辑,即在当天所有课程结束后触发。 - 增强了应用的自动化和用户自定义能力。 - **优化: 更新确认对话框** - 使用一个全新的自定义对话框替代了标准的 `askyesno` 弹窗。 - 新对话框会清晰地展示新版本的更新日志,帮助用户在更新前了解详细变更。 - **其他改进:** - 增加下载器线程数以提升更新速度。 - 优化了课程时间解析的错误处理逻辑。
1 parent 1b7d12f commit 5ef9614

File tree

5 files changed

+220
-30
lines changed

5 files changed

+220
-30
lines changed

app.py

Lines changed: 70 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -460,12 +460,12 @@ def _update_schedule_display(self, weekday_to_show: int) -> None:
460460
color = self._get_course_color(now, course)
461461
if i < len(self.course_labels):
462462
# 强制更新标签颜色状态
463-
self._update_existing_label(i, course, color, force_update=True)
463+
self._update_existing_label(i, course, color, now, force_update=True)
464464
# 调整grid行号
465465
self.course_labels[i].master.grid(row=i)
466466
else:
467467
# 创建新标签并指定grid行号
468-
self._create_new_label(course, color, row=i)
468+
self._create_new_label(course, color, now, row=i)
469469

470470
# 移除多余的标签
471471
self._remove_extra_labels(schedule_for_day)
@@ -492,12 +492,19 @@ def _get_course_color(self, now: datetime, course: Dict[str, str]) -> str:
492492

493493
# 从缓存获取或计算
494494
if cache_key not in self._course_time_cache:
495-
self._course_time_cache[cache_key] = (
496-
datetime.strptime(course["start_time"], "%H:%M").time(),
497-
datetime.strptime(course["end_time"], "%H:%M").time()
498-
)
495+
try:
496+
self._course_time_cache[cache_key] = (
497+
datetime.strptime(course["start_time"], "%H:%M").time(),
498+
datetime.strptime(course["end_time"], "%H:%M").time()
499+
)
500+
except (KeyError, ValueError):
501+
# 如果时间格式错误或键不存在,缓存一个无效标志并返回红色
502+
self._course_time_cache[cache_key] = (None, None)
503+
return "red"
499504

500505
start_time, end_time = self._course_time_cache[cache_key]
506+
if not start_time: # 检查无效标志
507+
return "red"
501508
current_time = now.time()
502509

503510
if start_time <= current_time <= end_time:
@@ -506,10 +513,42 @@ def _get_course_color(self, now: datetime, course: Dict[str, str]) -> str:
506513
return "green" # 已上完的课程为绿色
507514
return "red" # 未上过的课程为红色
508515

509-
def _update_existing_label(self, index: int, course: Dict[str, str], color: str, force_update: bool = False) -> None:
516+
def _get_course_display_text(self, course: Dict[str, str], color: str, now: datetime) -> str:
517+
"""根据课程状态和设置生成显示文本"""
518+
mode = self.config_handler.current_course_time_display_mode
519+
520+
# 仅当课程正在进行中 ("yellow") 且模式不是 "default" 时,才应用特殊显示
521+
if color == "yellow" and mode != "default":
522+
end_time_str = course.get("end_time", "00:00")
523+
524+
if mode == "end_time":
525+
return f"{end_time_str} {course['name']}"
526+
527+
if mode == "countdown":
528+
try:
529+
end_time_obj = datetime.strptime(end_time_str, "%H:%M").time()
530+
end_datetime = now.replace(hour=end_time_obj.hour, minute=end_time_obj.minute, second=0, microsecond=0)
531+
532+
# 如果结束时间在当前时间之前(例如,刚好过了一秒),则显示为0
533+
if end_datetime < now:
534+
remaining_seconds = 0
535+
else:
536+
remaining_seconds = (end_datetime - now).total_seconds()
537+
538+
minutes = int(remaining_seconds // 60)
539+
seconds = int(remaining_seconds % 60)
540+
return f"{minutes:02d}:{seconds:02d} {course['name']}"
541+
except (ValueError, KeyError):
542+
# 如果时间格式错误或键不存在,回退到默认显示
543+
return f"{course['start_time']} {course['name']}"
544+
545+
# 默认显示开始时间
546+
return f"{course['start_time']} {course['name']}"
547+
548+
def _update_existing_label(self, index: int, course: Dict[str, str], color: str, now: datetime, force_update: bool = False) -> None:
510549
"""更新现有课程标签"""
511550
label = self.course_labels[index]
512-
new_text = f"{course['start_time']} {course['name']}"
551+
new_text = self._get_course_display_text(course, color, now)
513552

514553
# 始终更新文本和颜色状态
515554
label.config(text=new_text)
@@ -591,14 +630,14 @@ def _adjust_ui_layout(self) -> None:
591630
# 强制更新所有部件
592631
self.root.update_idletasks()
593632

594-
def _create_new_label(self, course: Dict[str, str], color: str, row: int) -> None:
633+
def _create_new_label(self, course: Dict[str, str], color: str, now: datetime, row: int) -> None:
595634
"""创建新课程标签"""
596635
course_frame = tk.Frame(self.schedule_frame)
597636
course_frame.grid(row=row, column=0, sticky="ew", pady=2) # 使用指定的行号
598637

599638
label = tk.Label(
600639
course_frame,
601-
text=f"{course['start_time']} {course['name']}",
640+
text=self._get_course_display_text(course, color, now),
602641
font=("微软雅黑", self.config_handler.schedule_size, "bold"),
603642
fg=self.config_handler.font_color,
604643
anchor='w'
@@ -848,30 +887,45 @@ def _check_and_show_tomorrow_preview(self, now: datetime):
848887
if self.week_preview_window and self.week_preview_window.winfo_exists():
849888
return
850889

851-
# 检查当天的所有课程是否都已结束
852890
today_weekday_str = str(now.weekday())
853891
current_schedule_name = self.schedule.get("current_schedule", "default")
854892
schedule_data = self.schedule.get("schedules", {}).get(current_schedule_name, {})
855893
courses_today = schedule_data.get(today_weekday_str, [])
856894

857895
if not courses_today:
858-
return # 今天没课,不触发
896+
return # 今天没课,不触发
859897

898+
trigger_count = self.config_handler.preview_tomorrow_trigger_count
899+
900+
finished_courses_count = 0
860901
all_courses_finished = True
861902
last_course_end_time = None
903+
862904
for course in courses_today:
863905
try:
864906
end_time = datetime.strptime(course['end_time'], "%H:%M").time()
865907
if last_course_end_time is None or end_time > last_course_end_time:
866908
last_course_end_time = end_time
867-
if now.time() <= end_time:
909+
910+
if now.time() > end_time:
911+
finished_courses_count += 1
912+
else:
868913
all_courses_finished = False
869-
break
870914
except (ValueError, KeyError):
871915
continue # 忽略格式错误的课程
872916

873-
# 如果所有课程都结束了,且当前时间在最后一节课结束后,则触发
874-
if all_courses_finished and now.time() > last_course_end_time:
917+
should_trigger = False
918+
# 检查触发条件
919+
if trigger_count > 0:
920+
# 按第N节课触发
921+
if finished_courses_count >= trigger_count:
922+
should_trigger = True
923+
else:
924+
# 按全部课程结束后触发 (旧逻辑)
925+
if all_courses_finished and last_course_end_time and now.time() > last_course_end_time:
926+
should_trigger = True
927+
928+
if should_trigger:
875929
from tools.week_preview import WeekPreviewWindow
876930
self.week_preview_window = WeekPreviewWindow(self.root, self, day_offset=1)
877931
self.week_preview_window.show()

config_handler.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -27,21 +27,23 @@ def _set_default_attributes(self):
2727
self.font_color = "#000000"
2828
self.horizontal_padding = 10
2929
self.vertical_padding = 5
30-
self.time_display_size = 16
31-
self.countdown_size = 14
32-
self.schedule_size = 12
33-
self.transparent_background = False
30+
self.time_display_size = 20
31+
self.countdown_size = 18
32+
self.schedule_size = 18
33+
self.transparent_background = True
3434
self.fullscreen_subtitle = "祝考生考试顺利"
3535
self.debug_mode = False
3636
self.auto_update_check_enabled = False
3737
self.log_retention_days = 7
3838
self.check_prerelease = False
3939
self.auto_preview_tomorrow_enabled = False
40+
self.preview_tomorrow_trigger_count = 0
4041
self.schedule_rotation_enabled = False
4142
self.rotation_schedule1 = ""
4243
self.rotation_schedule2 = ""
4344
self.rotation_start_date = datetime.now()
4445
self.last_weather_location = ""
46+
self.current_course_time_display_mode = "default"
4547

4648
def check_registry_auto_start(self):
4749
"""检查注册表中是否存在开机自启动项"""
@@ -105,21 +107,23 @@ def _create_default_config_file(self):
105107
"font_color": "#000000",
106108
"horizontal_padding": 10,
107109
"vertical_padding": 5,
108-
"time_display_size": 16,
109-
"countdown_size": 14,
110-
"schedule_size": 12,
111-
"transparent_background": False,
110+
"time_display_size": 20,
111+
"countdown_size": 18,
112+
"schedule_size": 18,
113+
"transparent_background": True,
112114
"fullscreen_subtitle": "祝考生考试顺利",
113115
"debug_mode": False,
114116
"auto_update_check_enabled": False,
115117
"log_retention_days": 7,
116118
"check_prerelease": False,
117119
"auto_preview_tomorrow_enabled": False,
120+
"preview_tomorrow_trigger_count": 0,
118121
"schedule_rotation_enabled": False,
119122
"rotation_schedule1": "",
120123
"rotation_schedule2": "",
121124
"rotation_start_date": datetime.now().strftime("%Y-%m-%d"),
122-
"last_weather_location": ""
125+
"last_weather_location": "",
126+
"current_course_time_display_mode": "default"
123127
}
124128
self.config = {
125129
"config_version": CONFIG_VERSION,
@@ -189,11 +193,13 @@ def get_date(key, default_date_str):
189193
self.log_retention_days = get_int("log_retention_days", 7)
190194
self.check_prerelease = get_bool("check_prerelease", False)
191195
self.auto_preview_tomorrow_enabled = get_bool("auto_preview_tomorrow_enabled", False)
196+
self.preview_tomorrow_trigger_count = get_int("preview_tomorrow_trigger_count", 0)
192197
self.schedule_rotation_enabled = get_bool("schedule_rotation_enabled", False)
193198
self.rotation_schedule1 = get_str("rotation_schedule1", "")
194199
self.rotation_schedule2 = get_str("rotation_schedule2", "")
195200
self.rotation_start_date = get_date("rotation_start_date", datetime.now().strftime("%Y-%m-%d"))
196201
self.last_weather_location = get_str("last_weather_location", "")
202+
self.current_course_time_display_mode = get_str("current_course_time_display_mode", "default")
197203

198204
def save_config(self):
199205
"""将当前实例属性保存到文件中对应的配置方案下"""
@@ -240,11 +246,13 @@ def save_config(self):
240246
"log_retention_days": self.log_retention_days,
241247
"check_prerelease": self.check_prerelease,
242248
"auto_preview_tomorrow_enabled": self.auto_preview_tomorrow_enabled,
249+
"preview_tomorrow_trigger_count": self.preview_tomorrow_trigger_count,
243250
"schedule_rotation_enabled": self.schedule_rotation_enabled,
244251
"rotation_schedule1": self.rotation_schedule1,
245252
"rotation_schedule2": self.rotation_schedule2,
246253
"rotation_start_date": self.rotation_start_date.strftime("%Y-%m-%d"),
247-
"last_weather_location": self.last_weather_location
254+
"last_weather_location": self.last_weather_location,
255+
"current_course_time_display_mode": self.current_course_time_display_mode
248256
})
249257

250258
# 更新到主配置字典中

constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# 程序信息
1313
APP_NAME = "桌面课程表 - Course Scheduler"
1414
AUTHOR = "dong"
15-
VERSION = "0.1.16-preview2"
15+
VERSION = "0.1.16-preview3"
1616
CONFIG_VERSION = "2"
1717
PROJECT_URL = "https://github.com/dongkid/course_scheduler"
1818

settings.py

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,46 @@ def _create_course_tab(self) -> None:
380380
preview_frame, text="结束后自动预览明天课表",
381381
variable=self.auto_preview_tomorrow_var,
382382
style="White.TCheckbutton")
383-
self.auto_preview_tomorrow_check.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky=tk.W)
383+
self.auto_preview_tomorrow_check.grid(row=0, column=0, columnspan=4, padx=5, pady=5, sticky=tk.W)
384+
385+
# 第N节课后预览
386+
ttk.Label(preview_frame, text="第N节课后预览 (0为全结束后):").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W)
387+
388+
self.preview_trigger_count_var = tk.StringVar(value=self.main_app.config_handler.preview_tomorrow_trigger_count)
389+
self.preview_trigger_count_entry = ttk.Entry(preview_frame, width=5, textvariable=self.preview_trigger_count_var)
390+
self.preview_trigger_count_entry.grid(row=1, column=1, padx=5, pady=5)
391+
392+
# 减按钮
393+
ttk.Button(
394+
preview_frame,
395+
text="-",
396+
style="PMSmall.TButton",
397+
command=lambda: self.preview_trigger_count_var.set(max(0, int(self.preview_trigger_count_var.get() or 0) - 1))
398+
).grid(row=1, column=2, padx=(5,0), pady=5)
399+
400+
# 加按钮
401+
ttk.Button(
402+
preview_frame,
403+
text="+",
404+
style="PMSmall.TButton",
405+
command=lambda: self.preview_trigger_count_var.set(int(self.preview_trigger_count_var.get() or 0) + 1)
406+
).grid(row=1, column=3, padx=(0,5), pady=5)
407+
408+
# 当前课程时间显示设置
409+
display_mode_frame = ttk.LabelFrame(course_frame, text="当前课程时间显示设置", style="TFrame")
410+
display_mode_frame.pack(fill=tk.X, padx=10, pady=5)
411+
412+
self.course_time_display_mode_var = tk.StringVar(value=self.main_app.config_handler.current_course_time_display_mode)
413+
414+
modes = [("默认", "default"), ("结束时间", "end_time"), ("倒计时", "countdown")]
415+
for i, (text, mode) in enumerate(modes):
416+
ttk.Radiobutton(
417+
display_mode_frame,
418+
text=text,
419+
variable=self.course_time_display_mode_var,
420+
value=mode,
421+
style="White.TRadiobutton"
422+
).grid(row=0, column=i, padx=5, pady=5, sticky=tk.W)
384423

385424
# 课表轮换设置
386425
rotation_frame = ttk.LabelFrame(course_frame, text="课表轮换设置", style="TFrame")
@@ -843,8 +882,24 @@ def apply_settings(self):
843882
self.main_app.config_handler.rotation_schedule1 = self.schedule1_var.get()
844883
self.main_app.config_handler.rotation_schedule2 = self.schedule2_var.get()
845884

885+
# 保存课表轮换设置
886+
self.main_app.config_handler.schedule_rotation_enabled = self.rotation_var.get()
887+
self.main_app.config_handler.rotation_schedule1 = self.schedule1_var.get()
888+
self.main_app.config_handler.rotation_schedule2 = self.schedule2_var.get()
889+
890+
# 应用当前课程时间显示模式设置
891+
self.main_app.config_handler.current_course_time_display_mode = self.course_time_display_mode_var.get()
892+
846893
# 应用预览设置
847894
self.main_app.config_handler.auto_preview_tomorrow_enabled = self.auto_preview_tomorrow_var.get()
895+
try:
896+
trigger_count = int(self.preview_trigger_count_var.get())
897+
if trigger_count < 0:
898+
raise ValueError
899+
self.main_app.config_handler.preview_tomorrow_trigger_count = trigger_count
900+
except ValueError:
901+
messagebox.showerror("错误", "请输入有效的预览触发课程数(非负整数)", parent=self.window)
902+
return
848903

849904
# 应用日志保留天数设置
850905
try:
@@ -972,7 +1027,9 @@ def _load_config_into_ui(self):
9721027
self.break_duration_entry.delete(0, tk.END)
9731028
self.break_duration_entry.insert(0, str(handler.break_duration))
9741029
self.auto_preview_tomorrow_var.set(handler.auto_preview_tomorrow_enabled)
1030+
self.preview_trigger_count_var.set(str(handler.preview_tomorrow_trigger_count))
9751031
self.rotation_var.set(handler.schedule_rotation_enabled)
1032+
self.course_time_display_mode_var.set(handler.current_course_time_display_mode)
9761033
self.schedule1_var.set(handler.rotation_schedule1)
9771034
self.schedule2_var.set(handler.rotation_schedule2)
9781035
self.countdown_name_entry.delete(0, tk.END)

0 commit comments

Comments
 (0)