388 lines
16 KiB
Python
388 lines
16 KiB
Python
import tkinter as tk
|
|
from tkinter import ttk
|
|
import yaml
|
|
import colorama
|
|
import threading
|
|
import random
|
|
import time
|
|
import sys
|
|
import csv
|
|
import functools
|
|
from PIL import Image
|
|
Image.CUBIC = Image.BICUBIC
|
|
#Image.CUBIC = Resampling.BICUBIC
|
|
import ttkbootstrap as tb
|
|
|
|
marking_buttons = list()
|
|
version='v2.1'
|
|
config = None
|
|
current_member = dict()
|
|
member_sum = 0
|
|
stdtime = 0.1
|
|
random_stop = 0
|
|
LICENSE = None
|
|
member = None
|
|
is_paused = 1
|
|
is_tab_shown = 0
|
|
default_tab_layout = None
|
|
is_first = 1
|
|
db = None
|
|
marking_colormark = dict() # 标记颜色序号
|
|
is_dark = None
|
|
mk_fg = []
|
|
mk_bg = []
|
|
btnbg = None
|
|
style = None
|
|
btnfg = None
|
|
ttkthemename = None
|
|
about_text = f"""CLASSAUX - 幸运选手α
|
|
带轻量级数据库和随机功能的教学管理辅助程序 版本 {version}
|
|
版权所有 (C) 2025 Wang Zhiyu
|
|
本程序为共产版权的自由软件, 在自由软件联盟发布的GNU通用公共许可协议的约束下,
|
|
你可以对其进行再发布及修改。协议版本为第三版。
|
|
我们希望发布的这款程序有用, 但不保证, 甚至不保证它有经济价值和适合特定用途。
|
|
详情参见 GNU 通用公共许可协议
|
|
你理当已收到一份GNU通用公共许可协议的副本,
|
|
如果没有, 请查阅 <http://www.gnu.org/licenses/>
|
|
--------------------------------------------------------------------------------
|
|
使用的第三方资源与开放源代码库:
|
|
Colorama - Cross-platform colored terminal text
|
|
Tkinter - Python interface to Tcl/Tk
|
|
Pillow - Python Imaging Library
|
|
PyYaml - YAML parser and emitter for Python
|
|
Sarasa Gothic (更纱黑体)
|
|
--------------------------------------------------------------------------------
|
|
开源软件是一项立足于协作精神的事业,
|
|
它的运作和产出不受任何单一个人或者机构的控制。
|
|
不管您来自何方,我们都欢迎您加入并做出贡献。
|
|
开放源代码/报告程序缺陷: https://github.com/david-ajax/classaux
|
|
--------------------------------------------------------------------------------
|
|
"""
|
|
current_member["pause"] = None
|
|
current_member["show"] = ""
|
|
|
|
|
|
def init_theme():
|
|
global ttkthemename
|
|
global mk_bg
|
|
global mk_fg
|
|
global style
|
|
global btnfg
|
|
global btnbg
|
|
global sbtnfg
|
|
global sbtnbg
|
|
if not is_dark:
|
|
mk_bg = ["#FFFFFF","#009900","#ff6666","#0088ff"]
|
|
mk_fg = ["#000000","#FFFFFF","#FFFFFF","#FFFFFF"]
|
|
ttkthemename = "cosmo"
|
|
btnbg = [('pressed', '#2097ff'), ('active', '#9bcbff')]
|
|
btnfg = [('pressed', 'white'), ('active', '#36393b')]
|
|
sbtnbg = 'white'
|
|
sbtnfg = '#36393b'
|
|
else:
|
|
mk_fg = ["#FFFFFF","#00ff00","#ff6666","#9999ff"]
|
|
mk_bg = ["#202020","#005500","#550000","#000055"]
|
|
ttkthemename = "darkly"
|
|
btnbg = [('pressed', '#242424'), ('active', '#242424')]
|
|
btnfg = [('pressed', '#FFFFFF'), ('active', '#dddddd')]
|
|
sbtnbg = "#202020"
|
|
sbtnfg = 'white'
|
|
|
|
def matrix_gen(format):
|
|
ttmem = list()
|
|
tmem = list()
|
|
mem = list(config["member"])
|
|
random.shuffle(mem)
|
|
print(list(enumerate(mem)))
|
|
for i, ele in enumerate(mem):
|
|
tmem.append(ele["name"])
|
|
if i % 8 == 0:
|
|
ttmem.append(tmem)
|
|
tmem = list()
|
|
ttmem.reverse()
|
|
omember = None
|
|
def random_gen():
|
|
global config
|
|
global current_member
|
|
global random_stop
|
|
global member
|
|
member = config['member']
|
|
while not random_stop:
|
|
#print("重置列表")
|
|
random.shuffle(member)
|
|
#print(member)
|
|
for i in member:
|
|
if random_stop:
|
|
break
|
|
#print(i)
|
|
if i['weight'] == 0:
|
|
pass
|
|
else:
|
|
for k in range(0, i['weight']):
|
|
current_member["genuine"] = i['name']
|
|
time.sleep(stdtime)
|
|
|
|
def interface():
|
|
global btnfg
|
|
global btnbg
|
|
global style
|
|
global ttkthemename
|
|
global sbtnbg
|
|
global sbtnfg
|
|
root = tb.Window(themename=ttkthemename)
|
|
#root = tk.Tk()
|
|
root.iconphoto(False, tk.PhotoImage(file='logo.png'))
|
|
style = tb.Style()
|
|
style.configure("Marking.TButton", background=sbtnbg, foreground=sbtnfg)
|
|
style.map("Marking.TButton",
|
|
background=btnbg,
|
|
foreground=btnfg)
|
|
style.configure("TabToggle.TButton", background=sbtnbg, foreground=sbtnfg)
|
|
style.map("TabToggle.TButton",
|
|
background=btnbg,
|
|
foreground=btnfg)
|
|
root.title(f"幸运选手α {version}")
|
|
root.geometry("800x600")
|
|
notebook = ttk.Notebook(root)
|
|
notebook.pack(padx=10, pady=10)
|
|
tab_electing = tk.Frame(notebook, width=800, height=600)
|
|
tab_marking = tk.Frame(notebook, width=800, height=600)
|
|
tab_about = tk.Frame(notebook, width=800, height=600)
|
|
tab_bat = tk.Frame(notebook, width=800, height=600)
|
|
tab_edit = tk.Frame(notebook, width=800, height=600)
|
|
tab_filter = tk.Frame(notebook, width=800, height=600)
|
|
tab_migration = tk.Frame(notebook, width=800, height=600)
|
|
tab_cfg = tk.Frame(notebook, width=800, height=600)
|
|
notebook.add(tab_electing, text='单次抽签')
|
|
notebook.add(tab_marking, text='标记视图')
|
|
notebook.add(tab_bat, text='批量视图')
|
|
notebook.add(tab_edit, text='编辑数据库')
|
|
notebook.add(tab_filter, text='配置筛选器')
|
|
notebook.add(tab_cfg, text='设置')
|
|
notebook.add(tab_migration, text='转换兼容配置')
|
|
notebook.add(tab_about, text='关于')
|
|
marking_colormode = tk.StringVar()
|
|
bat_formationmode = tk.StringVar()
|
|
bat_attrmode = tk.StringVar()
|
|
chosen_attr = tk.StringVar()
|
|
def update_data():
|
|
global is_paused
|
|
if not is_paused:
|
|
wdgt[tab_electing][0].config(text=config['member'][random.randint(0, member_sum - 1)]["name"])
|
|
else:
|
|
wdgt[tab_electing][0].config(text=current_member["show"])
|
|
wdgt[tab_electing][0].after(int(1000 * stdtime * 0.75), update_data)
|
|
|
|
def on_closing():
|
|
root.destroy()
|
|
global random_stop
|
|
random_stop = 1
|
|
|
|
def toggle(): # 仅用于单次抽取模式
|
|
global is_paused
|
|
if not is_paused:
|
|
current_member["show"] = current_member["genuine"]
|
|
is_paused = 1
|
|
wdgt[tab_electing][1].config(text="开始")
|
|
else:
|
|
wdgt[tab_electing][1].config(text="暂停")
|
|
is_paused = 0
|
|
|
|
def toggle_menu():
|
|
global is_tab_shown
|
|
global default_tab_layout
|
|
if not is_tab_shown:
|
|
style.layout('TNotebook.Tab', default_tab_layout)
|
|
is_tab_shown = 1
|
|
else:
|
|
style.layout('TNotebook.Tab', [])
|
|
is_tab_shown = 0
|
|
|
|
def toggle_dark():
|
|
global style
|
|
global is_dark
|
|
global ttkthemename
|
|
global btnbg
|
|
global btnfg
|
|
global sbtnbg
|
|
global sbtnfg
|
|
global is_tab_shown
|
|
is_dark = not is_dark
|
|
init_theme()
|
|
style.theme_use(ttkthemename)
|
|
style.configure("Marking.TButton", background=sbtnbg, foreground=sbtnfg)
|
|
style.map("Marking.TButton",
|
|
background=btnbg,
|
|
foreground=btnfg)
|
|
style.configure("TabToggle.TButton", background=sbtnbg, foreground=sbtnfg)
|
|
style.map("TabToggle.TButton",
|
|
background=btnbg,
|
|
foreground=btnfg)
|
|
marking_clear()
|
|
is_tab_shown = not is_tab_shown
|
|
toggle_menu()
|
|
def marking_clear():
|
|
global style
|
|
global marking_buttons
|
|
marking_colormark.clear()
|
|
for i in marking_buttons:
|
|
marking_change_color(i)
|
|
wdgt = {
|
|
tab_electing: [
|
|
ttk.Label(tab_electing, text="待抽取", font=("Sarasa UI SC", 70, 'bold')),
|
|
ttk.Button(tab_electing, text="开始", command=toggle),
|
|
ttk.Button(tab_electing, text="选项卡", command=toggle_menu,style="TabToggle.TButton"),
|
|
ttk.Button(tab_electing, text="暗色调", command=toggle_dark,style="TabToggle.TButton"),
|
|
],
|
|
tab_about: [
|
|
tk.Text(tab_about, wrap=tk.WORD,font=("Sarasa UI SC", 10, '')),
|
|
#ttk.Label(tab_about, text=about_text)
|
|
],
|
|
tab_edit: [tk.Text(tab_about, wrap=tk.WORD,font=("Sarasa UI Mono SC", 10, '')),
|
|
],
|
|
tab_marking: [
|
|
ttk.Button(tab_marking, text="清空",command=marking_clear),
|
|
ttk.Checkbutton(tab_marking, text="只读模式", onvalue=1, offvalue=0,cursor="arrow",state="normal"),
|
|
ttk.Combobox(tab_marking, textvariable=marking_colormode, values=("双值模式", "三值模式", "五值模式"))
|
|
],
|
|
tab_bat: [
|
|
tk.Text(tab_bat),
|
|
ttk.Button(tab_bat, text="生成新随机", command=matrix_gen),
|
|
ttk.Button(tab_bat, text="复制到剪贴板",style="TabToggle.TButton"),
|
|
ttk.Button(tab_bat, text="导出至文件 (CSV/TXT)",style="TabToggle.TButton"),
|
|
ttk.Combobox(tab_bat, textvariable=bat_formationmode, values=("选择格式...", "线性列表", "ASCII 表格", "CSV (逗号分隔)", "Markdown 表格")),
|
|
ttk.Combobox(tab_bat, textvariable=bat_attrmode, values=("选择随机项...", "姓名", "性别", "居住"))
|
|
],
|
|
tab_filter: [
|
|
ttk.Combobox(tab_filter,textvariable=chosen_attr, values=["选择筛选项...","示例1","示例2"]),
|
|
ttk.Label(tab_filter, text="属性: 无"),
|
|
ttk.Label(tab_filter, text="类型: 无"),
|
|
ttk.Label(tab_filter, text="注意: 更改是实时的")
|
|
],
|
|
tab_migration: [
|
|
ttk.Combobox(tab_filter,textvariable=chosen_attr, values=["选择兼容的配置文件格式...","示例1","示例2"]),
|
|
ttk.Label(tab_filter, text="支持的文件格式: TXT 姓名列表(回车分隔), 标准 CSV 文件"),
|
|
],
|
|
tab_cfg: [
|
|
ttk.Label(tab_cfg, text="外观",font=("Sarasa UI SC", 14, 'bold')),
|
|
ttk.Label(tab_cfg, text="功能",font=("Sarasa UI SC", 14, 'bold')),
|
|
ttk.Label(tab_cfg, text="调试",font=("Sarasa UI SC", 14, 'bold')),
|
|
ttk.Combobox(tab_cfg, values=["选择主题", "cosmo (默认)", "flatly", "darkly"]),
|
|
ttk.Combobox(tab_cfg, values=["选择字体", "更纱黑体 (默认)", "系统默认字体"]),
|
|
]
|
|
}
|
|
|
|
wdgt[tab_electing][0].place(relx=0.5, rely=0.35, anchor='center')
|
|
wdgt[tab_electing][1].place(relx=0.5, rely=0.8, relwidth=0.25, relheight=0.15, anchor='center')
|
|
wdgt[tab_electing][2].place(relx=0.01, rely=0.99, relwidth=0.12, relheight=0.08, anchor='sw')
|
|
wdgt[tab_electing][3].place(relx=0.99, rely=0.99, relwidth=0.12, relheight=0.08, anchor='se')
|
|
|
|
wdgt[tab_about][0].insert(tk.END, about_text,"center")
|
|
wdgt[tab_about][0].insert(tk.END, LICENSE)
|
|
wdgt[tab_about][0].grid(row=1, column=0, sticky=tk.N)
|
|
wdgt[tab_about][0].configure(state="disabled")
|
|
|
|
wdgt[tab_bat][4].current(0)
|
|
wdgt[tab_bat][1].place(relx=0.5, rely=0.845, relwidth=0.25, relheight=0.13, anchor='n')
|
|
wdgt[tab_bat][2].place(relx=0.99, rely=0.99, relwidth=0.3, relheight=0.08, anchor='se')
|
|
wdgt[tab_bat][3].place(relx=0.99, rely=0.91, relwidth=0.3, relheight=0.08, anchor='se')
|
|
wdgt[tab_bat][4].place(relx=0.01, rely=0.91, relwidth=0.3, relheight=0.08, anchor='sw')
|
|
wdgt[tab_bat][5].place(relx=0.01, rely=0.99, relwidth=0.3, relheight=0.08, anchor='sw')
|
|
wdgt[tab_bat][0].place(relx=0.03, rely=0.03, relwidth=0.94, relheight=0.79, anchor='nw')
|
|
|
|
wdgt[tab_marking][0].place(relx=0.5, rely=1, relwidth=0.13, relheight=0.08, anchor='s')
|
|
wdgt[tab_marking][1].place(relx=1, rely=1, relwidth=0.13, relheight=0.08, anchor='se')
|
|
wdgt[tab_marking][2].place(relx=0, rely=1, relwidth=0.15, relheight=0.08, anchor='sw')
|
|
wdgt[tab_marking][2].current(0)
|
|
def marking_change_color(l):
|
|
global style
|
|
global marking_colormark
|
|
global mk_bg
|
|
global mk_fg
|
|
if id(l) not in marking_colormark.keys():
|
|
marking_colormark[id(l)] = 0
|
|
style = tb.Style()
|
|
style.configure(f"MarkingX{id(l)}.TButton", background=mk_bg[marking_colormark[id(l)]], foreground=mk_fg[marking_colormark[id(l)]])
|
|
style.map(f"MarkingX{id(l)}.TButton",
|
|
background=[('pressed', mk_bg[marking_colormark[id(l)]]), ('active', mk_bg[marking_colormark[id(l)]])],
|
|
foreground=[('pressed', mk_fg[marking_colormark[id(l)]]), ('active', mk_fg[marking_colormark[id(l)]])])
|
|
marking_colormark[id(l)] += 1
|
|
marking_colormark[id(l)]%=len(mk_fg)
|
|
# 应用样式
|
|
l.config(style=f"MarkingX{id(l)}.TButton")
|
|
global omember
|
|
tmember = list(omember)
|
|
for i in range((8 - len(member) % 8 )% 8):
|
|
tmember.append({"name":"占位符"})
|
|
for i, j in enumerate(tmember):
|
|
if len(j["name"]) == 2:
|
|
j["name"] = " " + j["name"] + " "
|
|
# 创建按钮并赋值给 l
|
|
l = ttk.Button(tab_marking, text=j["name"], style="Marking.TButton")
|
|
marking_buttons.append(l)
|
|
# 使用 partial 将 l 作为参数传递给回调函数
|
|
marking_change_color(l)
|
|
l.config(command=functools.partial(marking_change_color, l))
|
|
l.grid(column=i % 8, row=i // 8, padx=5, pady=5)
|
|
wdgt[tab_filter][0].current(0)
|
|
wdgt[tab_filter][0].grid(column=0,row=0)
|
|
wdgt[tab_filter][1].grid(column=0,row=1,sticky=tk.W,padx=5,pady=5)
|
|
wdgt[tab_filter][2].grid(column=0,row=2,sticky=tk.W,padx=5,pady=5)
|
|
wdgt[tab_filter][3].place(relx=0.01, rely=1, relwidth=1, relheight=0.08, anchor='sw')
|
|
wdgt[tab_filter][0].grid(column=0,row=0,sticky=tk.W,padx=5,pady=5)
|
|
wdgt[tab_filter][1].grid(column=0,row=1,sticky=tk.W,padx=5,pady=5)
|
|
|
|
wdgt[tab_cfg][0].grid(column=0, row=0, padx=5,pady=5,sticky=tk.W)
|
|
wdgt[tab_cfg][1].grid(column=0, row=3, padx=5,pady=5,sticky=tk.W)
|
|
wdgt[tab_cfg][2].grid(column=0, row=4, padx=5,pady=5,sticky=tk.W)
|
|
wdgt[tab_cfg][3].grid(column=0, row=1, padx=5,pady=5,sticky=tk.W)
|
|
wdgt[tab_cfg][4].grid(column=0, row=2, padx=5,pady=5,sticky=tk.W)
|
|
|
|
global is_first
|
|
if is_first:
|
|
global default_tab_layout
|
|
default_tab_layout = style.layout('TNotebook.Tab')
|
|
style.layout('TNotebook.Tab', [])
|
|
is_first = 0
|
|
root.protocol("WM_DELETE_WINDOW", on_closing)
|
|
update_data()
|
|
root.mainloop()
|
|
|
|
if __name__ == "__main__":
|
|
print("确保此终端支持 UTF-8 字符输出!")
|
|
print(f"幸运选手α {version}")
|
|
print("开放源代码: https://github.com/david-ajax/classaux")
|
|
print("读取 YAML 配置文件与 GPL 协议 ", end="")
|
|
with open("COPYING", encoding='utf_8') as license_file:
|
|
LICENSE = license_file.read()
|
|
with open("config.yaml", encoding='utf_8') as config_file:
|
|
config = yaml.safe_load(config_file.read())
|
|
print(config['member'])
|
|
omember = list(config['member'])
|
|
print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL)
|
|
#print(config)
|
|
print(f"读取数据库 {config['info']['db']} ", end="")
|
|
print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL)
|
|
with open("db.csv", encoding='utf_8-sig') as db_file:
|
|
db = list(csv.reader(db_file, delimiter=',', quotechar='"'))
|
|
for i in db:
|
|
if i[0] == 'NOTE':
|
|
db.remove(i)
|
|
# print(db)
|
|
print(f"配置文件: {config['info']['name']}")
|
|
member_sum = len(config['member'])
|
|
print(f"成员人数: {len(config['member'])}")
|
|
print(f"随机刻间隔时间: {stdtime}s")
|
|
print("启动随机生成器守护线程 ", end="")
|
|
random_thr = threading.Thread(target=random_gen)
|
|
random_thr.start()
|
|
print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL)
|
|
print("初始化主题资源 ", end="")
|
|
is_dark = config["darkmode"]
|
|
init_theme()
|
|
print(colorama.Fore.GREEN + "成功" + colorama.Style.RESET_ALL)
|
|
print("初始化 GUI ", end="")
|
|
print(colorama.Fore.YELLOW + "正运行" + colorama.Style.RESET_ALL)
|
|
interface()
|
|
print(colorama.Fore.GREEN + "结束" + colorama.Style.RESET_ALL)
|