diff --git a/bridgeClock.wxg b/bridgeClock.wxg index 91bfa1d..da1d5f4 100644 --- a/bridgeClock.wxg +++ b/bridgeClock.wxg @@ -1,5 +1,5 @@ - + @@ -83,29 +83,77 @@ 0 Copperplate Gothic Bold - + 1 - + 0 wxEXPAND - - #b00fff - #ffff00 - - 256 - roman - - bold - 0 - - - - + + wxHORIZONTAL 1 + + + 0 + wxEXPAND + + #b00fff + #ffff00 + + 256 + roman + + bold + 0 + + + + + 1 + + + + + 0 + wxEXPAND + + #b00fff + #ffff00 + + 256 + roman + + bold + 0 + + + + 1 + + + + + 0 + wxEXPAND + + #b00fff + #ffff00 + + 256 + roman + + bold + 0 + + + + + 1 + + diff --git a/bridge_clock_main.py b/bridge_clock_main.py index 1f7b463..a3c4a53 100644 --- a/bridge_clock_main.py +++ b/bridge_clock_main.py @@ -225,6 +225,35 @@ def _pause_game(self) -> None: self.button_start.SetLabelText("Start") self.button_start.SetValue(False) + def _set_bg(self, colour: str) -> None: + """Set the background colour of the clock and round text areas.""" + bc_log("in _set_bg") + for widget in ( + self.label_clock_colon, + self.label_clock_minutes, + self.label_clock_seconds, + self.label_round, + ): + widget.SetBackgroundColour(colour) + self.panel_1.Refresh() + + @staticmethod + def _display_label(widget: wx.TextCtrl, label: str) -> None: + """Change and resize a clock label.""" + widget.SetLabelText(label) + + def _display_round_label(self, label: str) -> None: + """For ease of reading: display round text.""" + self._display_label(self.label_round, label) + + def _display_clock_minutes_label(self, label: str) -> None: + """For ease of reading: display minutes.""" + self._display_label(self.label_clock_minutes, label) + + def _display_clock_seconds_label(self, label: str) -> None: + """For ease of reading: change clock_seconds text.""" + self._display_label(self.label_clock_seconds, label) + def _round_1(self) -> None: """Return to round 1.""" self.round = 1 @@ -236,9 +265,8 @@ def _break_this_round(self) -> bool: def _update_round(self) -> None: """Change the round label""" - self.label_round.SetLabelText(f"Round {self.round}") - self.label_clock.SetBackgroundColour(RUN_COLOUR) - self.label_round.SetBackgroundColour(RUN_COLOUR) + self._display_round_label(f"Round {self.round}") + self._set_bg(RUN_COLOUR) self.panel_1.Layout() def _reset_clock(self) -> None: @@ -252,7 +280,13 @@ def _reset_clock(self) -> None: def _update_clock(self) -> None: """Display the current countdown clock value""" time_left = self.round_end - wx.DateTime.Now() - self.label_clock.SetLabelText(time_left.Format("%M:%S")) + (minutes, seconds) = time_left.Format("%M:%S").split(":") + for time, widget in ( + (minutes, self.label_clock_minutes), + (seconds, self.label_clock_seconds), + ): + if time != widget.GetLabelText(): + self._display_label(widget, time) self.panel_1.Layout() def _update_statusbar(self) -> None: @@ -311,9 +345,8 @@ def _next_round(self) -> None: def _go_to_break(self) -> None: """Display a visible hospitality break, and count down.""" self._in_break = True - self.label_clock.SetBackgroundColour(BREAK_COLOUR) - self.label_round.SetBackgroundColour(BREAK_COLOUR) - self.label_round.SetLabelText("NEXT ROUND IN:") + self._set_bg(BREAK_COLOUR) + self._display_round_label("NEXT ROUND IN:") self.round_end = wx.DateTime.Now() + wx.TimeSpan.Minutes( self.settings.break_length ) @@ -323,8 +356,8 @@ def _go_to_break(self) -> None: def _game_over(self) -> None: """Game is over.""" self._pause_game() - self.label_clock.SetLabelText("Done! ") - self._handle_resize(self.label_clock, "Done! ") + self._display_label(self.label_clock_minutes, "Game") + self._display_label(self.label_clock_seconds, "Over") self.panel_1.Layout() self._game_finished = True bc_log(f"End of Game, {wx.DateTime.UNow().Format('%H:%M:%S.%l')}") @@ -334,7 +367,10 @@ def _present_file_dialog( ) -> None: """Popup a file dialog for loading or saving configuration.""" dlg = wx.FileDialog( - self, message=message, defaultDir=str(Path(__file__)), wildcard=wildcard + self, + message=message, + defaultDir=str(Path(__file__) / "settings"), + wildcard=wildcard, ) if dlg.ShowModal() == wx.ID_OK: pth = Path(dlg.GetPath()) @@ -435,23 +471,53 @@ def on_close(self, event) -> None: event.Skip() @staticmethod - def _handle_resize(obj: wx.Control, scale_text: str) -> None: - """Scale text to fit window.""" - w, h = obj.GetSize() - tw, th = obj.GetTextExtent(scale_text).Get() - scale = min(h / th, w / tw) - new_font = obj.GetFont().Scaled(scale) - obj.SetFont(new_font) + def _rescale_text( + boundary_object: wx.Sizer | wx.Window, widget_info: Tuple[wx.Window, str] + ) -> None: + """Work out the new font size for the scaled widgets and set it in them. + + Note that this works only for widgets in horizontal format; height is constant. + """ + if not widget_info: + bc_log("_rescale_text given no widgets to scale, ignoring!") + return + + current_width, current_height = boundary_object.GetSize() + total_width = 0 + for widget, scale_text in widget_info: + text_width, text_height = widget.GetTextExtent(scale_text).Get() + total_width += text_width + scale = min(current_width / total_width, current_height / text_height) + new_font = widget_info[0][0].GetFont().Scaled(scale) + for widget, _ in widget_info: + widget.SetFont(new_font) + + def _resize_round(self) -> None: + """Scale Round text to fit window.""" + if self._in_break: + scale_text = "TIME TO NEXT:" + else: + scale_text = "ROUND 8" if self.settings.rounds < 10 else "ROUND 88" + round_widget_info = ((self.label_round, scale_text),) + self._rescale_text(self.label_round, round_widget_info) + + def _resize_clock(self) -> None: + """Scale all clock widgets to fit window.""" + minutes_text = "88" if self.settings.round_length < 100 else "888" + clock_widget_info = ( + (self.label_clock_minutes, minutes_text), + (self.label_clock_colon, ":"), + (self.label_clock_seconds, "88"), + ) + self._rescale_text(self.sizer_clock, clock_widget_info) def on_resize(self, event) -> None: """Hack to resolve sizer not auto-sizing with panel on maximize/unmaximize.""" self.Layout() + bc_log("In on_resize!") self.sizer_1.SetDimension(self.panel_1.GetPosition(), self.panel_1.GetSize()) - self._handle_resize(self.label_round, "NEXT ROUND IN:") - self._handle_resize( - self.label_clock, - "88:88" if self.settings.round_length < 100 else "888:88", - ) + self._resize_round() + self._resize_clock() self.panel_1.Layout() event.Skip() @@ -528,7 +594,7 @@ def on_goto_break(self, event): "Break already scheduled for this round.", style=wx.PD_AUTO_HIDE, ) - dlg.SetPosition( # show above "go to break" button) + dlg.SetPosition( # show above "go to break" button self.button_break.GetScreenPosition() - (0, dlg.GetSize().GetHeight()) ) wx.CallLater(500, dlg.Update, 100) diff --git a/clock_main_frame.py b/clock_main_frame.py index b93e60c..b2e161d 100644 --- a/clock_main_frame.py +++ b/clock_main_frame.py @@ -3,10 +3,9 @@ # -*- coding: UTF-8 -*- # -# generated by wxGlade 1.0.5 on Sat Sep 28 17:40:11 2024 +# generated by wxGlade 1.0.5 on Tue Oct 15 15:16:29 2024 # # automated code generation - disable some pylint checks from them. -# pylint: disable=line-too-long # wxPython being what it is... # pylint: disable=too-many-ancestors # pylint: disable=too-many-instance-attributes @@ -86,7 +85,10 @@ def __init__(self, *args, **kwds): # pylint: disable=too-many-statements self.sizer_1 = wx.BoxSizer(wx.VERTICAL) self.label_round = wx.StaticText( - self.panel_1, wx.ID_ANY, "Round 1", style=wx.ALIGN_CENTER_HORIZONTAL + self.panel_1, + wx.ID_ANY, + "Round 1", + style=wx.ALIGN_CENTER_HORIZONTAL | wx.ST_NO_AUTORESIZE, ) self.label_round.SetBackgroundColour(wx.Colour(176, 0, 255)) self.label_round.SetForegroundColour(wx.Colour(255, 255, 255)) @@ -102,17 +104,42 @@ def __init__(self, *args, **kwds): # pylint: disable=too-many-statements ) self.sizer_1.Add(self.label_round, 1, wx.EXPAND, 0) - self.label_clock = wx.StaticText( - self.panel_1, wx.ID_ANY, "20:00", style=wx.ALIGN_CENTER_HORIZONTAL + self.sizer_clock = wx.BoxSizer(wx.HORIZONTAL) + self.sizer_1.Add(self.sizer_clock, 6, wx.EXPAND, 0) + + self.label_clock_minutes = wx.StaticText( + self.panel_1, wx.ID_ANY, "15", style=wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE + ) + self.label_clock_minutes.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_minutes.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_minutes.SetFont( + wx.Font( + 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" + ) + ) + self.sizer_clock.Add(self.label_clock_minutes, 3, wx.EXPAND, 0) + + self.label_clock_colon = wx.StaticText(self.panel_1, wx.ID_ANY, ":") + self.label_clock_colon.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_colon.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_colon.SetFont( + wx.Font( + 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" + ) + ) + self.sizer_clock.Add(self.label_clock_colon, 0, wx.EXPAND, 0) + + self.label_clock_seconds = wx.StaticText( + self.panel_1, wx.ID_ANY, "00", style=wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE ) - self.label_clock.SetBackgroundColour(wx.Colour(176, 15, 255)) - self.label_clock.SetForegroundColour(wx.Colour(255, 255, 0)) - self.label_clock.SetFont( + self.label_clock_seconds.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_seconds.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_seconds.SetFont( wx.Font( 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" ) ) - self.sizer_1.Add(self.label_clock, 8, wx.EXPAND, 0) + self.sizer_clock.Add(self.label_clock_seconds, 3, wx.EXPAND, 0) self.sizer_2 = wx.BoxSizer(wx.HORIZONTAL) self.sizer_1.Add(self.sizer_2, 0, wx.EXPAND, 0) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e396d5a --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +wxPython \ No newline at end of file diff --git a/wxglade_out.py b/wxglade_out.py index 7f33979..76dc2e0 100644 --- a/wxglade_out.py +++ b/wxglade_out.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 # -*- coding: UTF-8 -*- # -# generated by wxGlade 1.0.5 on Sat Sep 28 17:40:11 2024 +# generated by wxGlade 1.0.5 on Tue Oct 15 15:16:29 2024 # import wx @@ -36,10 +36,20 @@ def __init__(self, *args, **kwds): self.Bind(wx.EVT_MENU, self.on_menu_settings_customize, item) self.frame_menubar.Append(wxglade_tmp_menu, "&Settings") wxglade_tmp_menu = wx.Menu() - self.frame_menubar.i_view_buttons = wxglade_tmp_menu.Append(wx.ID_ANY, "Hide_Buttons", "", wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU, self.on_menu_view_buttons, self.frame_menubar.i_view_buttons) - self.frame_menubar.i_view_statusbar = wxglade_tmp_menu.Append(wx.ID_ANY, "Hide Statusbar", "", wx.ITEM_CHECK) - self.Bind(wx.EVT_MENU, self.on_menu_view_statusbar, self.frame_menubar.i_view_statusbar) + self.frame_menubar.i_view_buttons = wxglade_tmp_menu.Append( + wx.ID_ANY, "Hide_Buttons", "", wx.ITEM_CHECK + ) + self.Bind( + wx.EVT_MENU, self.on_menu_view_buttons, self.frame_menubar.i_view_buttons + ) + self.frame_menubar.i_view_statusbar = wxglade_tmp_menu.Append( + wx.ID_ANY, "Hide Statusbar", "", wx.ITEM_CHECK + ) + self.Bind( + wx.EVT_MENU, + self.on_menu_view_statusbar, + self.frame_menubar.i_view_statusbar, + ) self.frame_menubar.Append(wxglade_tmp_menu, "&View") wxglade_tmp_menu = wx.Menu() item = wxglade_tmp_menu.Append(wx.ID_ANY, "About", "") @@ -55,21 +65,68 @@ def __init__(self, *args, **kwds): for i in range(len(frame_statusbar_fields)): self.frame_statusbar.SetStatusText(frame_statusbar_fields[i], i) - self.panel_1 = wx.Panel(self, wx.ID_ANY, style=wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE) + self.panel_1 = wx.Panel( + self, wx.ID_ANY, style=wx.CLIP_CHILDREN | wx.FULL_REPAINT_ON_RESIZE + ) self.sizer_1 = wx.BoxSizer(wx.VERTICAL) - self.label_round = wx.StaticText(self.panel_1, wx.ID_ANY, "Round 1", style=wx.ALIGN_CENTER_HORIZONTAL) + self.label_round = wx.StaticText( + self.panel_1, + wx.ID_ANY, + "Round 1", + style=wx.ALIGN_CENTER_HORIZONTAL | wx.ST_NO_AUTORESIZE, + ) self.label_round.SetBackgroundColour(wx.Colour(176, 0, 255)) self.label_round.SetForegroundColour(wx.Colour(255, 255, 255)) - self.label_round.SetFont(wx.Font(36, wx.FONTFAMILY_SWISS, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 0, "Copperplate Gothic Bold")) + self.label_round.SetFont( + wx.Font( + 36, + wx.FONTFAMILY_SWISS, + wx.FONTSTYLE_NORMAL, + wx.FONTWEIGHT_NORMAL, + 0, + "Copperplate Gothic Bold", + ) + ) self.sizer_1.Add(self.label_round, 1, wx.EXPAND, 0) - self.label_clock = wx.StaticText(self.panel_1, wx.ID_ANY, "20:00", style=wx.ALIGN_CENTER_HORIZONTAL) - self.label_clock.SetBackgroundColour(wx.Colour(176, 15, 255)) - self.label_clock.SetForegroundColour(wx.Colour(255, 255, 0)) - self.label_clock.SetFont(wx.Font(256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "")) - self.sizer_1.Add(self.label_clock, 8, wx.EXPAND, 0) + self.sizer_clock = wx.BoxSizer(wx.HORIZONTAL) + self.sizer_1.Add(self.sizer_clock, 6, wx.EXPAND, 0) + + self.label_clock_minutes = wx.StaticText( + self.panel_1, wx.ID_ANY, "15", style=wx.ALIGN_RIGHT | wx.ST_NO_AUTORESIZE + ) + self.label_clock_minutes.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_minutes.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_minutes.SetFont( + wx.Font( + 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" + ) + ) + self.sizer_clock.Add(self.label_clock_minutes, 3, wx.EXPAND, 0) + + self.label_clock_colon = wx.StaticText(self.panel_1, wx.ID_ANY, ":") + self.label_clock_colon.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_colon.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_colon.SetFont( + wx.Font( + 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" + ) + ) + self.sizer_clock.Add(self.label_clock_colon, 0, wx.EXPAND, 0) + + self.label_clock_seconds = wx.StaticText( + self.panel_1, wx.ID_ANY, "00", style=wx.ALIGN_LEFT | wx.ST_NO_AUTORESIZE + ) + self.label_clock_seconds.SetBackgroundColour(wx.Colour(176, 15, 255)) + self.label_clock_seconds.SetForegroundColour(wx.Colour(255, 255, 0)) + self.label_clock_seconds.SetFont( + wx.Font( + 256, wx.FONTFAMILY_ROMAN, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, 0, "" + ) + ) + self.sizer_clock.Add(self.label_clock_seconds, 3, wx.EXPAND, 0) self.sizer_2 = wx.BoxSizer(wx.HORIZONTAL) self.sizer_1.Add(self.sizer_2, 0, wx.EXPAND, 0) @@ -187,8 +244,10 @@ def on_resize(self, event): # wxGlade: RoundTimer. print("Event handler 'on_resize' not implemented!") event.Skip() + # end of class RoundTimer + class SetupDialog(wx.Dialog): def __init__(self, *args, **kwds): # begin wxGlade: SetupDialog.__init__ @@ -215,14 +274,20 @@ def __init__(self, *args, **kwds): label_3 = wx.StaticText(self.panel_1, wx.ID_ANY, "Minutes per Round:") grid_sizer_1.Add(label_3, 0, wx.ALIGN_CENTER_VERTICAL, 0) - self.text_round_count = wx.TextCtrl(self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE) + self.text_round_count = wx.TextCtrl( + self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE + ) self.text_round_count.SetMinSize((50, -1)) self.text_round_count.SetToolTip("Number of rounds to count (9, 13, ...)") grid_sizer_1.Add(self.text_round_count, 0, wx.ALIGN_CENTER, 0) - self.text_round_length = wx.TextCtrl(self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE) + self.text_round_length = wx.TextCtrl( + self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE + ) self.text_round_length.SetMinSize((50, -1)) - self.text_round_length.SetToolTip("Length of each round (minutes). Include time for round change!") + self.text_round_length.SetToolTip( + "Length of each round (minutes). Include time for round change!" + ) grid_sizer_1.Add(self.text_round_length, 0, wx.ALIGN_CENTER, 0) label_4 = wx.StaticText(self.panel_1, wx.ID_ANY, "Hospitality Breaks") @@ -231,7 +296,7 @@ def __init__(self, *args, **kwds): grid_sizer_2 = wx.FlexGridSizer(2, 3, 0, 20) sizer_3.Add(grid_sizer_2, 2, wx.ALIGN_CENTER_HORIZONTAL, 0) - label_5 = wx.StaticText(self.panel_1, wx.ID_ANY, "After Rounds (e.g. \"4,9\")") + label_5 = wx.StaticText(self.panel_1, wx.ID_ANY, 'After Rounds (e.g. "4,9")') grid_sizer_2.Add(label_5, 0, 0, 0) label_6 = wx.StaticText(self.panel_1, wx.ID_ANY, "Break Length") @@ -240,32 +305,48 @@ def __init__(self, *args, **kwds): label_7 = wx.StaticText(self.panel_1, wx.ID_ANY, "Invisible?") grid_sizer_2.Add(label_7, 0, 0, 0) - self.text_break_rounds = wx.TextCtrl(self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE) + self.text_break_rounds = wx.TextCtrl( + self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE + ) self.text_break_rounds.SetMinSize((80, -1)) - self.text_break_rounds.SetToolTip("Rounds to give a break after. If more than one, separate by commas (e.g. 4, 9)") + self.text_break_rounds.SetToolTip( + "Rounds to give a break after. If more than one, separate by commas (e.g. 4, 9)" + ) grid_sizer_2.Add(self.text_break_rounds, 0, wx.ALIGN_CENTER_HORIZONTAL, 0) - self.text_break_length = wx.TextCtrl(self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE) + self.text_break_length = wx.TextCtrl( + self.panel_1, wx.ID_ANY, "", style=wx.TE_CENTRE + ) self.text_break_length.SetMinSize((50, -1)) self.text_break_length.SetToolTip("Minutes to pause for the break.") grid_sizer_2.Add(self.text_break_length, 0, wx.ALIGN_CENTER, 0) self.check_invisible = wx.CheckBox(self.panel_1, wx.ID_ANY, "") - self.check_invisible.SetToolTip("If checked, rather than showing a break explicitly, just add the time to the current round.") + self.check_invisible.SetToolTip( + "If checked, rather than showing a break explicitly, just add the time to the current round." + ) grid_sizer_2.Add(self.check_invisible, 0, wx.ALIGN_CENTER, 0) sizer_4 = wx.BoxSizer(wx.HORIZONTAL) sizer_3.Add(sizer_4, 1, wx.ALIGN_CENTER_HORIZONTAL, 0) - self.check_sounds = wx.CheckBox(self.panel_1, wx.ID_ANY, "Sounds:", style=wx.ALIGN_RIGHT) + self.check_sounds = wx.CheckBox( + self.panel_1, wx.ID_ANY, "Sounds:", style=wx.ALIGN_RIGHT + ) self.check_sounds.SetToolTip("Play sounds") sizer_4.Add(self.check_sounds, 0, wx.ALIGN_CENTER_VERTICAL, 0) - self.check_manual = wx.CheckBox(self.panel_1, wx.ID_ANY, "Manually Start Rounds:", style=wx.ALIGN_RIGHT) - self.check_manual.SetToolTip("If checked, will not automatically start each next round.") + self.check_manual = wx.CheckBox( + self.panel_1, wx.ID_ANY, "Manually Start Rounds:", style=wx.ALIGN_RIGHT + ) + self.check_manual.SetToolTip( + "If checked, will not automatically start each next round." + ) sizer_4.Add(self.check_manual, 0, wx.EXPAND | wx.LEFT, 30) - self.check_restart = wx.CheckBox(self.panel_1, wx.ID_ANY, "Restart Game", style=wx.ALIGN_RIGHT) + self.check_restart = wx.CheckBox( + self.panel_1, wx.ID_ANY, "Restart Game", style=wx.ALIGN_RIGHT + ) self.check_restart.Hide() sizer_4.Add(self.check_restart, 0, wx.ALIGN_CENTER_VERTICAL | wx.LEFT, 30) @@ -300,8 +381,10 @@ def on_restart_checked(self, event): # wxGlade: SetupDialog. print("Event handler 'on_restart_checked' not implemented!") event.Skip() + # end of class SetupDialog + class MyApp(wx.App): def OnInit(self): self.round_timer = RoundTimer(None, wx.ID_ANY, "") @@ -309,6 +392,7 @@ def OnInit(self): self.round_timer.Show() return True + # end of class MyApp if __name__ == "__main__":