__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.track_popover(self.popover) 44self.calendar = Gtk.Calendar() 45self.calendar.set_show_week_numbers(True) 46self.popover.set_child(self.calendar) 47self.button.set_popover(self.popover) 48 49self.append(self.button) 50 51self.formatting = config.get("formatting", "%c") 52# Some placeholders require second precision, but not all of them. If not required, 53# use minute precision 54self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 55self.next_update = None 56self.set_time() 57 58self.context_menu = self.make_context_menu() 59self.track_popover(self.context_menu) 60 61right_click_controller = Gtk.GestureClick() 62right_click_controller.set_button(3) 63right_click_controller.connect("pressed", self.show_context_menu) 64 65self.add_controller(right_click_controller) 66 67action_group = Gio.SimpleActionGroup() 68options_action = Gio.SimpleAction.new("options", None) 69options_action.connect("activate", self.show_options) 70action_group.add_action(options_action) 71self.insert_action_group("applet", action_group) 72 73self.options_window = None 74 75def make_context_menu(self): 76menu = Gio.Menu() 77menu.append("Clock _options", "applet.options") 78context_menu = Gtk.PopoverMenu.new_from_model(menu) 79context_menu.set_has_arrow(False) 80context_menu.set_parent(self) 81context_menu.set_halign(Gtk.Align.START) 82context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED) 83return context_menu 84 85def show_context_menu(self, gesture, n_presses, x, y): 86rect = Gdk.Rectangle() 87rect.x = int(x) 88rect.y = int(y) 89rect.width = 1 90rect.height = 1 91 92self.context_menu.set_pointing_to(rect) 93self.context_menu.popup() 94 95def update_formatting(self, entry): 96self.formatting = entry.get_text() 97# Some placeholders require second precision, but not all of them. If not required, 98# use minute precision 99self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS) 100if self.next_update is not None: 101GLib.source_remove(self.next_update) 102self.next_update = None 103self.set_time() 104 105def show_options(self, _0=None, _1=None): 106if self.options_window is None: 107self.options_window = ClockOptions() 108self.options_window.format_entry.set_text(self.formatting) 109self.options_window.format_entry.connect("changed", self.update_formatting) 110 111def reset_window(*args): 112self.options_window = None 113 114self.options_window.connect("close-request", reset_window) 115self.options_window.present() 116 117def set_time(self): 118datetime = GLib.DateTime.new_now_local() 119formatted_time = datetime.format(self.formatting) 120if formatted_time is not None: 121self.label.set_text(datetime.format(self.formatting)) 122else: 123self.label.set_text("Invalid time formatting") 124return False 125 126if self.has_second_precision: 127current_ms = GLib.DateTime.new_now_local().get_microsecond() // 1000 128self.next_update = GLib.timeout_add(1000 - current_ms + 1, self.set_time) # 1ms is added to ensure the clock is updated 129else: 130now = GLib.DateTime.new_now_local() 131current_ms = now.get_second() * 1000 + now.get_microsecond() // 1000 132self.next_update = GLib.timeout_add(60000 - current_ms + 1, self.set_time) 133return False # Do not rerun the current timeout; a new one has been scheduled 134 135def get_config(self): 136return {"formatting": self.formatting} 137 138def set_panel_position(self, position): 139self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position]) 140self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]]) 141