__init__.py
Python script, ASCII text executable
1import os 2from pathlib import Path 3import panorama_panel 4 5import gi 6gi.require_version("Gtk", "4.0") 7 8from gi.repository import Gtk, GLib, Gio, Gdk 9 10 11SECOND_PLACEHOLDERS = ("%c", "%s", "%S", "%T", "%X") 12 13 14module_directory = Path(__file__).resolve().parent 15 16 17@Gtk.Template(filename=str(module_directory / "panorama-clock-options.ui")) 18class ClockOptions(Gtk.Window): 19__gtype_name__ = "ClockOptions" 20format_entry: Gtk.Entry = Gtk.Template.Child() 21 22def __init__(self, **kwargs): 23super().__init__(**kwargs) 24 25self.connect("close-request", lambda *args: self.destroy()) 26 27 28class ClockApplet(panorama_panel.Applet): 29name = "Clock" 30description = "Read the current time and date" 31 32def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None): 33super().__init__(orientation=orientation, config=config) 34if config is None: 35config = {} 36self.button = Gtk.MenuButton() 37self.button.set_has_frame(False) # flat look 38self.label = Gtk.Label() 39self.button.set_child(self.label) 40 41# Create the monthly calendar 42self.popover = Gtk.Popover() 43self.calendar = Gtk.Calendar() 44self.calendar.set_show_week_numbers(True) 45self.popover.set_child(self.calendar) 46self.button.set_popover(self.popover) 47 48self.append(self.button) 49 50self.formatting = config.get("formatting", "%c") 51# Some placeholders require second precision, but not all of them. If not required, 52# use minute precision 53self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 54self.next_update = None 55self.set_time() 56 57self.context_menu = self.make_context_menu() 58 59right_click_controller = Gtk.GestureClick() 60right_click_controller.set_button(3) 61right_click_controller.connect("pressed", self.show_context_menu) 62 63self.add_controller(right_click_controller) 64 65action_group = Gio.SimpleActionGroup() 66options_action = Gio.SimpleAction.new("options", None) 67options_action.connect("activate", self.show_options) 68action_group.add_action(options_action) 69self.insert_action_group("applet", action_group) 70 71self.options_window = None 72 73def make_context_menu(self): 74menu = Gio.Menu() 75menu.append("Clock _options", "applet.options") 76context_menu = Gtk.PopoverMenu.new_from_model(menu) 77context_menu.set_has_arrow(False) 78context_menu.set_parent(self) 79context_menu.set_halign(Gtk.Align.START) 80context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED) 81return context_menu 82 83def show_context_menu(self, gesture, n_presses, x, y): 84rect = Gdk.Rectangle() 85rect.x = int(x) 86rect.y = int(y) 87rect.width = 1 88rect.height = 1 89 90self.context_menu.set_pointing_to(rect) 91self.context_menu.popup() 92 93def update_formatting(self, entry): 94self.formatting = entry.get_text() 95# Some placeholders require second precision, but not all of them. If not required, 96# use minute precision 97self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 98if self.next_update is not None: 99GLib.source_remove(self.next_update) 100self.next_update = None 101self.set_time() 102 103def show_options(self, _0=None, _1=None): 104if self.options_window is None: 105self.options_window = ClockOptions() 106self.options_window.format_entry.set_text(self.formatting) 107self.options_window.format_entry.connect("changed", self.update_formatting) 108 109def reset_window(*args): 110self.options_window = None 111 112self.options_window.connect("close-request", reset_window) 113self.options_window.present() 114 115def set_time(self): 116datetime = GLib.DateTime.new_now_local() 117formatted_time = datetime.format(self.formatting) 118if formatted_time is not None: 119self.label.set_text(datetime.format(self.formatting)) 120else: 121self.label.set_text("Invalid time formatting") 122return False 123 124if self.has_second_precision: 125current_ms = GLib.DateTime.new_now_local().get_microsecond() // 1000 126self.next_update = GLib.timeout_add(1000 - current_ms + 1, self.set_time) # 1ms is added to ensure the clock is updated 127else: 128now = GLib.DateTime.new_now_local() 129current_ms = now.get_second() * 1000 + now.get_microsecond() // 1000 130self.next_update = GLib.timeout_add(60000 - current_ms + 1, self.set_time) 131return False # Do not rerun the current timeout; a new one has been scheduled 132 133def get_config(self): 134return {"formatting": self.formatting} 135 136def set_panel_position(self, position): 137self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position]) 138self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]]) 139