123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400 |
- import abc
- import tkinter as tk
- import tkinter.font as font
- from PIL import Image, ImageTk
- import conf
- from tool.type_ import *
- from sql.db import DB
- class RankingStationException(Exception):
- ...
- class RankingError(RankingStationException):
- ...
- class RankingPageError(RankingStationException):
- ...
- class RankingStationBase(metaclass=abc.ABCMeta):
- def __init__(self, db: DB):
- self._db = db
- self.rank = [[]]
- self.rank_index = 0
- self.rank_count = 1
- self.offset = 0
- self.limit_n = 2
- self.auto: bool = False
- self.auto_to_next: bool = True # auto的移动方向
- self.auto_time: int = 5000 # 5s
- def get_rank(self, offset: int = 0) -> Tuple[bool, list]:
- limit = self.rank_count * self.limit_n
- offset = self.offset + limit * offset # offset为0表示不移动, 1表示向前, -1表示向后
- cur = self._db.search((f"SELECT uid, name, score, reputation "
- f"FROM user "
- f"WHERE manager = 0 "
- f"ORDER BY reputation DESC, score DESC "
- f"LIMIT {limit} OFFSET {offset};"))
- if cur is None or cur.rowcount == 0:
- return False, []
- self.offset = offset
- rank_list = list(cur.fetchall())
- rank = [[]]
- for i, r in enumerate(rank_list):
- if len(rank[-1]) == self.rank_count:
- rank.append([])
- color = None
- if self.offset + i == 0:
- color = "#eaff56"
- elif self.offset + i == 1:
- color = "#ffa631"
- elif self.offset + i == 2:
- color = "#ff7500"
- rank[-1].append((self.offset + i + 1, r[1], r[0], r[2], r[3], color))
- return True, rank
- def rank_page_to_next(self):
- if self.rank_index == len(self.rank) - 1:
- self.set_next_btn(True) # 当 rank_index为最后一项时, 该函数不应该被调用(除非数据库被外部改动)
- return
- self.rank_index += 1
- if self.rank_index == len(self.rank) - 1: # 最后一项
- if len(self.rank[self.rank_index]) == self.rank_count:
- res, rank = self.get_rank(1) # 向前移动一个offset
- if res:
- self.rank = self.rank[self.rank_index], *rank # 调整rank的内容
- self.rank_index = 0
- else:
- self.set_next_btn(True)
- else: # 如果最后一个表格没有填满, 直接判定无next
- self.set_next_btn(True)
- self.show_rank(self.rank[self.rank_index])
- self.set_prev_btn(False)
- if self.auto:
- self.set_run_after_now(self.auto_time, self.update_rank_auto)
- def rank_page_to_prev(self):
- if self.rank_index == 0: # 当 rank_index为最后一项时, 该函数不应该被调用(除非数据库被外部改动)
- self.set_prev_btn(True)
- return
- self.rank_index -= 1
- if self.rank_index == 0: # 回到第一项
- res, rank = self.get_rank(-1) # 向后移动一个offset
- if res:
- self.rank = *rank, self.rank[self.rank_index] # 调整rank的内容
- self.rank_index = 0
- else:
- self.set_prev_btn(True)
- self.show_rank(self.rank[self.rank_index])
- self.set_next_btn(False)
- if self.auto:
- self.set_run_after_now(self.auto_time, self.update_rank_auto)
- def show_rank_first(self):
- self.rank_index = 0
- self.offset = 0
- self.rank_count = self.rank_count
- res, self.rank = self.get_rank(0)
- self.show_rank(self.rank[0])
- self.set_prev_btn(True)
- if len(self.rank) == 1:
- self.set_next_btn(True)
- else:
- self.set_next_btn(False)
- def rank_auto(self, auto):
- if auto:
- self.auto = True
- self.disable_btn()
- self.set_run_after_now(self.auto_time, self.update_rank_auto) # 注册自动函数
- else:
- self.auto = False
- self.able_btn()
- def update_rank_auto(self):
- if not self.auto:
- return
- if (self.auto_to_next and self.is_able_next() or
- not self.auto_to_next and not self.is_able_prev() and self.is_able_next()):
- self.rank_page_to_next()
- self.auto_to_next = True
- elif (not self.auto_to_next and self.is_able_prev() or
- self.auto_to_next and not self.is_able_next() and self.is_able_prev()):
- self.rank_page_to_prev()
- self.auto_to_next = False
- else:
- return # 无法动弹
- @abc.abstractmethod
- def show_rank(self, rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]]):
- ...
- @abc.abstractmethod
- def set_next_btn(self, disable: False):
- ...
- @abc.abstractmethod
- def set_prev_btn(self, disable: False):
- ...
- @abc.abstractmethod
- def disable_btn(self):
- ...
- @abc.abstractmethod
- def able_btn(self):
- ...
- @abc.abstractmethod
- def is_able_prev(self):
- ...
- @abc.abstractmethod
- def is_able_next(self):
- ...
- @abc.abstractmethod
- def set_run_after_now(self, ms, func, *args):
- ...
- @abc.abstractmethod
- def mainloop(self):
- ...
- class RankingStation(RankingStationBase):
- def __init__(self, db: DB):
- self.init_after_run_list: List[Tuple[int, Callable, Tuple]] = []
- super(RankingStation, self).__init__(db)
- self.window = tk.Tk()
- self._sys_height = self.window.winfo_screenheight()
- self._sys_width = self.window.winfo_screenwidth()
- self.height = int(self._sys_height * (2 / 3))
- self.width = int(self._sys_width * (1 / 3))
- self._full_screen = False
- self.__conf_windows()
- self.next_btn: bool = True # 表示开关是否启用
- self.prev_btn: bool = True
- self.__conf_font_size()
- self.__creat_tk()
- self.__conf_tk()
- self.show_rank_first()
- def __creat_tk(self):
- self.rank_frame = tk.Frame(self.window)
- self.rank_title = tk.Label(self.rank_frame)
- self.rank_title_var = tk.StringVar()
- self.rank_count = 7
- self.rank_label = [tk.Label(self.rank_frame) for _ in range(self.rank_count)]
- self.rank_y_height: List[Tuple[float, float]] = [] # rank 标签的y坐标信息
- self.rank_var = [tk.StringVar() for _ in range(self.rank_count)]
- self.rank_btn = [tk.Button(self.rank_frame) for _ in range(3)] # prev, auto, next
- def __conf_font_size(self, n: Union[int, float] = 1):
- self._rank_font_title_size = int(24 * n)
- self._rank_font_size = int(16 * n)
- self._rank_font_btn_size = int(20 * n)
- def __conf_tk(self):
- self.__conf_windows_bg()
- self.__conf_rank()
- def __conf_windows(self):
- self.window.title('HGSSystem: Ranking')
- self.window.geometry(f'{self.width}x{self.height}')
- self.window['bg'] = "#F0FFF0" # 蜜瓜绿
- self.window.resizable(False, False)
- self.bg_img = None
- self.bg_lb = tk.Label(self.window)
- self.bg_lb['bg'] = "#F0FFF0" # 蜜瓜绿
- self.bg_lb.place(relx=0, rely=0, relwidth=1, relheight=1)
- self.window.bind("<F11>", lambda _: self.__switch_full_screen())
- def __conf_windows_bg(self):
- img = Image.open(conf.pic_d['rank_bg']).resize((self.width, self.height))
- self.bg_img = ImageTk.PhotoImage(img)
- self.bg_lb['image'] = self.bg_img
- self.bg_lb.place(relx=0, rely=0, relwidth=1, relheight=1)
- def __switch_full_screen(self):
- self._full_screen = not self._full_screen
- self.window.attributes("-fullscreen", self._full_screen)
- width = self._sys_width * (1 / 3)
- height = self._sys_height * (2 / 3)
- self.width = self.window.winfo_width()
- self.height = self.window.winfo_height()
- n = min((self.height / height), (self.width / width)) # 因为横和纵不是平均放大, 因此取倍数小的
- self.__conf_font_size(n)
- self.__conf_tk()
- self.show_rank_first()
- def __conf_rank(self):
- title_font = self.__make_font(size=self._rank_font_title_size, weight="bold")
- info_font = self.__make_font(size=self._rank_font_size)
- btn_font = self.__make_font(size=self._rank_font_btn_size)
- height = self.height * 0.95
- width = height * (3 / 4)
- # 宽度过大
- if width >= self.width:
- width = self.width * 0.95
- height = width * (4 / 3) / self.width
- relwidth = width / self.width
- relheight = height / self.height
- relx = (1 - relwidth) / 2
- rely = (1 - relheight) / 2
- self.rank_frame['relief'] = "ridge"
- self.rank_frame['bd'] = 5
- self.rank_frame['bg'] = "#F5F5DC"
- self.rank_frame.place(relx=relx, rely=rely, relwidth=relwidth, relheight=relheight)
- self.rank_title['font'] = title_font
- self.rank_title['bg'] = "#F5F5DC"
- self.rank_title['textvariable'] = self.rank_title_var
- self.rank_title.place(relx=0.02, rely=0.00, relwidth=0.96, relheight=0.1)
- self.rank_title_var.set("Ranking.loading...")
- """
- 标签所拥有的总高度为0.82
- 标签数为c
- 间隔数为c-1
- 标签高度为x
- 间隔高度为y
- 规定 5y = x
- 所以: cx + (c - 1)y = 0.82
- 得到: 5cx + (c - 1)y = (5c + c - 1)y = 0.82
- 因此: x = 0.82 / (5c + c - 1)
- """
- height_s = (0.82 / (self.rank_count * 6 - 1)) # 标签的高度
- height_l = height_s * 5 # 间隔的高度
- height = 0.10 # 从 0.10 到 0.82
- for i in range(self.rank_count):
- self.rank_label[i]['font'] = info_font
- self.rank_label[i]['bg'] = "#F5FFFA"
- self.rank_label[i]['textvariable'] = self.rank_var[i]
- self.rank_label[i]['relief'] = "ridge"
- self.rank_label[i]['bd'] = 2
- self.rank_var[i].set("")
- self.rank_y_height.append((height, height_l))
- height += height_l + height_s
- for btn, text, x in zip(self.rank_btn,
- ("prev", "manual" if self.auto else "auto", "next"), (0.050, 0.375, 0.700)):
- btn['font'] = btn_font
- btn['text'] = text
- btn['bg'] = "#00CED1"
- btn.place(relx=x, rely=0.93, relwidth=0.25, relheight=0.06)
- self.rank_btn[0]['command'] = lambda: self.rank_page_to_prev()
- self.rank_btn[1]['command'] = lambda: self.rank_auto(True)
- self.rank_btn[2]['command'] = lambda: self.rank_page_to_next()
- def set_run_after_now(self, ms, func, *args):
- self.window.after(ms, func, *args)
- def __set_rank_info(self, rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]]):
- if len(rank_info) > self.rank_count:
- rank_info = rank_info[:self.rank_count]
- for lb in self.rank_label: # 隐藏全部标签
- lb.place_forget()
- for i, info in enumerate(rank_info):
- no, name, uid, score, eval_, color = info
- self.rank_var[i].set(f"NO.{no} {name}\nUID: {uid[0:conf.ranking_tk_show_uid_len]}\n"
- f"Score: {score} Reputation: {eval_}")
- if color is None:
- self.rank_label[i]['bg'] = "#F5FFFA"
- else:
- self.rank_label[i]['bg'] = color
- rely = self.rank_y_height[i][0]
- relheight = self.rank_y_height[i][1]
- self.rank_label[i].place(relx=0.04, rely=rely, relwidth=0.92, relheight=relheight)
- @staticmethod
- def __make_font(family: str = 'noto', **kwargs):
- return font.Font(family=conf.font_d[family], **kwargs)
- def show_rank(self, rank_info: List[Tuple[int, uname_t, uid_t, score_t, score_t, Optional[str]]]):
- self.__set_rank_info(rank_info)
- self.rank_title_var.set("Ranking")
- def rank_auto(self, auto):
- super(RankingStation, self).rank_auto(auto)
- if auto:
- self.rank_btn[1]['command'] = lambda: self.rank_auto(False)
- self.rank_btn[1]['text'] = 'manual'
- else:
- self.rank_btn[1]['command'] = lambda: self.rank_auto(True)
- self.rank_btn[1]['text'] = 'auto'
- def set_next_btn(self, disable: False):
- if disable or self.auto: # auto 模式令btn失效
- self.rank_btn[2]['state'] = 'disable'
- else:
- self.rank_btn[2]['state'] = 'normal'
- self.next_btn = not disable
- def set_prev_btn(self, disable: False):
- if disable or self.auto:
- self.rank_btn[0]['state'] = 'disable'
- else:
- self.rank_btn[0]['state'] = 'normal'
- self.prev_btn = not disable
- def disable_btn(self):
- self.rank_btn[0]['state'] = 'disable'
- self.rank_btn[2]['state'] = 'disable'
- def able_btn(self):
- if self.prev_btn:
- self.rank_btn[0]['state'] = 'normal'
- if self.next_btn:
- self.rank_btn[2]['state'] = 'normal'
- def is_able_prev(self):
- return self.prev_btn
- def is_able_next(self):
- return self.next_btn
- def mainloop(self):
- self.window.mainloop()
- if __name__ == '__main__':
- mysql_db = DB()
- station = RankingStation(mysql_db)
- station.mainloop()
|