def waiting_screen(size: Tuple[int, int], config: WahooConfig) -> Image.Image: '''Generate a "waiting" image to display on the scoreboard.''' img = Image.new(mode="RGBA", size=size, color=config.get_str("color_bg")) center = (int(size[0] * 0.5), int(size[1] * 0.8)) normal = _fontname_to_file(config.get_str("normal_font")) font_size = 72 fnt = ImageFont.truetype(normal, font_size) draw = ImageDraw.Draw(img) color = config.get_str("color_fg") draw.text(center, "Waiting for results...", font=fnt, fill=color, anchor="ms") return img
def _mockup(config: WahooConfig) -> Heat: heat = Heat(allow_inconsistent=not config.get_bool("inhibit_inconsistent")) #pylint: disable=protected-access heat._parse_do4("""129;1;1;All Lane1;123.00;122.00;0 Lane2;60;60;0 Lane3;75.51;75.59; Lane4;13.33;13.32; Lane5;0;0;0 Lane6;75.95;75.93; Lane7;615.01;615.15;0 Lane8;87.65;87.88;0 Lane9;300.00;300.10;0 Lane10;121.02;120.80;0 F679E29E3D8A4CC4""".split("\n")) heat._parse_scb("""#129 GIRLS 13 & OVER 200 FLY SWIMMER, FIRST Z --ABC SWIMMER, ANOTHER -- REALLYREALLYLONGNAME-- PERSON, JUST A --TEAM SWIM, NO -- BIGBIGBIGLY, NAMENAM--LONGLONGLONGLONG GO, INEDA --FAST SWIM, SLOW -- NAMES, BORING -- IDEAS, OUT O -- """.split("\n")) return heat
def _main(): config = WahooConfig() root = tk.Tk() root.resizable(False, False) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) def exitfn() -> None: root.destroy() def publishfn(_: Image.Image) -> None: "Don't publish images for the mockup" manual = _DolphinManual(root, exitfn, publishfn, config) manual.grid(column=0, row=0, sticky="news") tk.mainloop()
def __init__(self, container: tkContainer, config: WahooConfig, file_cb: Callable[[], None]): super().__init__(container, padding=5, text="Race results") self._config = config self.chooser = tk.StringVar(self) # https://bytes.com/topic/python/answers/829768-tkinter-tab-focus-traversal-causes-listbox-selection-clear-ie-lose-selected-item self._lbox = tk.Listbox(self, listvariable=self.chooser, width=25, exportselection=0) self._lbox.grid(column=0, row=0, sticky="news") scr = ttk.Scrollbar(self, orient="vertical", command=self._lbox.yview) scr.grid(column=1, row=0, sticky="ns") self._lbox.configure(yscrollcommand=scr.set) entries = list(os.scandir(config.get_str("dolphin_dir"))) # Only interested in .do4 files entries = [x for x in entries if x.name.endswith(".do4")] # Sort, most recent first entries.sort(key=lambda e: -e.stat().st_mtime) self._names = [x.name for x in entries] self.chooser.set(self._names) # type: ignore def file_selected(_): file_cb() self._lbox.bind("<<ListboxSelect>>", file_selected) self._lbox.selection_set(0) self._lbox.see(0)
def _main(): root = tk.Tk() root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) root.resizable(False, False) options = WahooConfig() def sel_cb(items): print(items) settings = Settings(root, None, None, None, sel_cb, None, None, options) settings.grid(column=0, row=0, sticky="news") root.update() print(f"Root window: {root.winfo_width()}x{root.winfo_height()}") items = {} items["abc"] = {"name": "cc1", "enabled": True} items["def"] = {"name": "cc2", "enabled": False} items["ghi"] = {"name": "cc1.5", "enabled": True} settings.set_items(items) root.update() image = Image.open("file.png") settings.set_preview(image) tk.mainloop()
def _main(): '''Display a scoreboard mockup''' root = tk.Tk() # Chromecast images are always 1280x720, so we design the image to look # good at those dimensions. size = (1280, 720) canvas = tk.Canvas(root, width=size[0], height=size[1]) canvas.configure(bg="black", borderwidth=0) canvas.pack() root.update() # Create the SB image config = WahooConfig() heat = _mockup(config) image = ScoreboardImage(heat, size, config).image image.save("file.png") pimage = ImageTk.PhotoImage(image) canvas.create_image(canvas.winfo_width() // 2, canvas.winfo_height() // 2, image=pimage, anchor="center") root.mainloop()
def __init__(self, container: TkContainer, config: WahooConfig, color_option: str): super().__init__(container, bg=config.get_str(color_option), relief="solid", padx=7, borderwidth=1, command=self._btn_cb) self._color_option = color_option self._config = config
def main(): '''Runs the Wahoo! Results scoreboard''' global FILE_WATCHER # pylint: disable=global-statement global IC # pylint: disable=global-statement # Determine if running as a PyInstaller exe bundle dsn = None execution_environment = "source" if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'): execution_environment = "executable" dsn = SENTRY_DSN # only report if in executable mode # pylint: disable=abstract-class-instantiated # https://github.com/getsentry/sentry-python/issues/1081 # Initialize Sentry crash reporting sentry_sdk.init( dsn=dsn, sample_rate=1.0, traces_sample_rate=1.0, environment=execution_environment, release=f"wahoo-results@{WAHOO_RESULTS_VERSION}", with_locals=True, integrations=[ThreadingIntegration(propagate_hub=True)], debug=False, ) uname = platform.uname() sentry_sdk.set_tag("os_system", uname.system) sentry_sdk.set_tag("os_release", uname.release) sentry_sdk.set_tag("os_version", uname.version) sentry_sdk.set_tag("os_machine", uname.machine) config = WahooConfig() sentry_sdk.set_user({ "id": config.get_str("client_id"), "ip_address": "{{auto}}", }) hub = sentry_sdk.Hub.current hub.start_session(session_mode="application") bundle_dir = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__))) root = Tk() screen_size = f"{root.winfo_screenwidth()}x{root.winfo_screenheight()}" sentry_sdk.set_context("display", { "size": screen_size, }) root.title("Wahoo! Results") icon_file = os.path.abspath(os.path.join(bundle_dir, 'wahoo-results.ico')) root.iconbitmap(icon_file) root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) FILE_WATCHER = watchdog.observers.Observer() FILE_WATCHER.start() IC = ImageCast(8011) settings_window(root, config) # Intercept the close button so we can save the config before destroying # the main window and exiting the event loop in case we need to display an # error dialog. def close_cb(): try: config.save() except PermissionError as err: messagebox.showerror( title="Error saving configuration", message= f'Unable to write configuration file "{err.filename}". {err.strerror}', detail="Please ensure the working directory is writable.") root.destroy() root.protocol("WM_DELETE_WINDOW", close_cb) root.mainloop() FILE_WATCHER.stop() FILE_WATCHER.join() IC.stop()
def settings_window(root: Tk, options: WahooConfig) -> None: '''Display the settings window''' # Settings window is fixed size root.state("normal") root.overrideredirect(False) # show titlebar root.resizable(False, False) root.geometry("") # allow automatic size settings: Settings def clear_cb(): image = waiting_screen((1280, 720), options) settings.set_preview(image) IC.publish(image) def test_cb() -> None: heat = results.Heat( allow_inconsistent=not options.get_bool("inhibit_inconsistent")) #pylint: disable=protected-access heat._parse_do4("""432;1;1;All Lane1;991.03;991.02; Lane2;48.00;48.00; Lane3;600.20;600.20; Lane4;0;0;0 Lane5;312.34;312.34; Lane6;678.12;679.12; Lane7;1000.03;1000.03; Lane8;1000.03;1000.03; Lane9;1010.03;1010.03; Lane10;1000.03;1000.03; F679E29E3D8A4CC4""".split("\n")) # Names from https://www.name-generator.org.uk/ #pylint: disable=protected-access heat._parse_scb("""#432 GIRLS 13&O 1650 FREE MILLER, STEPHANIE --TEAM1 DAVIS, SARAH --TEAM1 GARCIA, ASHLEY --TEAM1 WILSON, JESSICA --TEAM1 -- MOORE, SAMANTHA --TEAM1 JACKSON, AMBER --TEAM1 TAYLOR, MELISSA --TEAM1 ANDERSON, RACHEL --TEAM1 WHITE, MEGAN --TEAM1 """.split("\n")) heat_cb(heat) def selection_cb(enabled_uuids: List[uuid.UUID]) -> None: for status in IC.get_devices(): if status["uuid"] in enabled_uuids: IC.enable(status["uuid"], True) else: IC.enable(status["uuid"], False) def watchdir_cb(_dir: str) -> None: FILE_WATCHER.unschedule_all() path = options.get_str("dolphin_dir") if os.path.exists(path): FILE_WATCHER.schedule(do4_handler, path) def heat_cb(heat: results.Heat) -> None: sbi = ScoreboardImage(heat, (1280, 720), options) settings.set_preview(sbi.image) IC.publish(sbi.image) def cast_discovery_cb() -> None: items = {} for status in IC.get_devices(): items[status["uuid"]] = { "name": status["name"], "enabled": status["enabled"] } settings.set_items(items) def manual_publish(img: Image.Image) -> None: settings.set_preview(img) IC.publish(img) def manual_btn_cb() -> None: show_manual_chooser(root, manual_publish, options) do4_handler = Do4Handler(heat_cb, options) path = options.get_str("dolphin_dir") if os.path.exists(path): FILE_WATCHER.schedule(do4_handler, path) settings = Settings(root, generate_dolphin_csv, clear_cb, test_cb, selection_cb, watchdir_cb, manual_btn_cb, options) settings.grid(column=0, row=0, sticky="news") IC.set_discovery_callback(cast_discovery_cb) IC.start() clear_cb()