__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 25 26class ClockApplet(panorama_panel.Applet): 27name = "Clock" 28description = "Read the current time and date" 29 30def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None): 31super().__init__(orientation=orientation, config=config) 32if config is None: 33config = {} 34self.button = Gtk.MenuButton() 35self.button.set_has_frame(False) # flat look 36self.label = Gtk.Label() 37self.button.set_child(self.label) 38 39# Create the monthly calendar 40self.popover = Gtk.Popover() 41self.calendar = Gtk.Calendar() 42self.calendar.set_show_week_numbers(True) 43self.popover.set_child(self.calendar) 44self.button.set_popover(self.popover) 45 46self.append(self.button) 47 48self.formatting = config.get("formatting", "%c") 49# Some placeholders require second precision, but not all of them. If not required, 50# use minute precision 51self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 52self.next_update = None 53self.set_time() 54 55self.context_menu = self.make_context_menu() 56 57right_click_controller = Gtk.GestureClick() 58right_click_controller.set_button(3) 59right_click_controller.connect("pressed", self.show_context_menu) 60 61self.add_controller(right_click_controller) 62 63action_group = Gio.SimpleActionGroup() 64options_action = Gio.SimpleAction.new("options", None) 65options_action.connect("activate", self.show_options) 66action_group.add_action(options_action) 67self.insert_action_group("applet", action_group) 68 69self.options_window = None 70 71def make_context_menu(self): 72menu = Gio.Menu() 73menu.append("Clock _options", "applet.options") 74context_menu = Gtk.PopoverMenu.new_from_model(menu) 75context_menu.set_has_arrow(False) 76context_menu.set_parent(self) 77context_menu.set_halign(Gtk.Align.START) 78context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED) 79return context_menu 80 81def show_context_menu(self, gesture, n_presses, x, y): 82rect = Gdk.Rectangle() 83rect.x = int(x) 84rect.y = int(y) 85rect.width = 1 86rect.height = 1 87 88self.context_menu.set_pointing_to(rect) 89self.context_menu.popup() 90 91def update_formatting(self, entry): 92self.formatting = entry.get_text() 93# Some placeholders require second precision, but not all of them. If not required, 94# use minute precision 95self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 96if self.next_update is not None: 97GLib.source_remove(self.next_update) 98self.next_update = None 99self.set_time() 100 101def show_options(self, _0=None, _1=None): 102if not self.options_window: 103self.options_window = ClockOptions() 104self.options_window.format_entry.set_text(self.formatting) 105self.options_window.format_entry.connect("changed", self.update_formatting) 106self.options_window.present() 107 108def set_time(self): 109datetime = GLib.DateTime.new_now_local() 110formatted_time = datetime.format(self.formatting) 111if formatted_time is not None: 112self.label.set_text(datetime.format(self.formatting)) 113else: 114self.label.set_text("Invalid time formatting") 115return False 116 117if self.has_second_precision: 118current_ms = GLib.DateTime.new_now_local().get_microsecond() // 1000 119self.next_update = GLib.timeout_add(1000 - current_ms + 1, self.set_time) # 1ms is added to ensure the clock is updated 120else: 121now = GLib.DateTime.new_now_local() 122current_ms = now.get_second() * 1000 + now.get_microsecond() // 1000 123self.next_update = GLib.timeout_add(60000 - current_ms + 1, self.set_time) 124return False # Do not rerun the current timeout; a new one has been scheduled 125 126def get_config(self): 127return {"text": self.label.get_text()} 128