By using this site, you agree to have cookies stored on your device, strictly for functional purposes, such as storing your session and preferences.

Dismiss

 __init__.py

View raw Download
text/plain • 6.08 kiB
Python script, ASCII text executable
        
            
1
"""
2
Clock applet for the Panorama panel.
3
Copyright 2025, roundabout-host.com <vlad@roundabout-host.com>
4
5
This program is free software: you can redistribute it and/or modify
6
it under the terms of the GNU General Public Licence as published by
7
the Free Software Foundation, either version 3 of the Licence, or
8
(at your option) any later version.
9
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public Licence for more details.
14
15
You should have received a copy of the GNU General Public Licence
16
along with this program. If not, see <https://www.gnu.org/licenses/>.
17
"""
18
19
import os
20
import locale
21
from pathlib import Path
22
import panorama_panel
23
24
import gi
25
gi.require_version("Gtk", "4.0")
26
27
from gi.repository import Gtk, GLib, Gio, Gdk
28
29
30
SECOND_PLACEHOLDERS = ("%c", "%s", "%S", "%T", "%X")
31
32
33
module_directory = Path(__file__).resolve().parent
34
locale.bindtextdomain("panorama-panel-clock", module_directory / "locale")
35
_ = lambda x: locale.dgettext("panorama-panel-clock", x)
36
37
38
@Gtk.Template(filename=str(module_directory / "panorama-clock-options.ui"))
39
class ClockOptions(Gtk.Window):
40
__gtype_name__ = "ClockOptions"
41
format_entry: Gtk.Entry = Gtk.Template.Child()
42
43
def __init__(self, **kwargs):
44
super().__init__(**kwargs)
45
46
self.connect("close-request", lambda *args: self.destroy())
47
48
49
class ClockApplet(panorama_panel.Applet):
50
name = _("Clock")
51
description = _("Read the current time and date")
52
icon = Gio.ThemedIcon.new("preferences-system-time")
53
54
def __init__(self, orientation=Gtk.Orientation.HORIZONTAL, config=None, **kwargs):
55
super().__init__(orientation=orientation, config=config, **kwargs)
56
if config is None:
57
config = {}
58
self.button = Gtk.MenuButton()
59
self.button.set_has_frame(False) # flat look
60
self.label = Gtk.Label()
61
self.button.set_child(self.label)
62
63
# Create the monthly calendar
64
self.popover = Gtk.Popover()
65
panorama_panel.track_popover(self.popover)
66
self.calendar = Gtk.Calendar()
67
self.calendar.set_show_week_numbers(True)
68
self.popover.set_child(self.calendar)
69
self.button.set_popover(self.popover)
70
71
self.append(self.button)
72
73
self.formatting = config.get("formatting", "%c")
74
# Some placeholders require second precision, but not all of them. If not required,
75
# use minute precision
76
self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS)
77
self.next_update = None
78
self.set_time()
79
80
self.context_menu = self.make_context_menu()
81
panorama_panel.track_popover(self.context_menu)
82
83
right_click_controller = Gtk.GestureClick()
84
right_click_controller.set_button(3)
85
right_click_controller.connect("pressed", self.show_context_menu)
86
87
self.add_controller(right_click_controller)
88
89
action_group = Gio.SimpleActionGroup()
90
options_action = Gio.SimpleAction.new("options", None)
91
options_action.connect("activate", self.show_options)
92
action_group.add_action(options_action)
93
self.insert_action_group("applet", action_group)
94
95
self.options_window = None
96
97
def make_context_menu(self):
98
menu = Gio.Menu()
99
menu.append(_("Clock _options"), "applet.options")
100
context_menu = Gtk.PopoverMenu.new_from_model(menu)
101
context_menu.set_has_arrow(False)
102
context_menu.set_parent(self)
103
context_menu.set_halign(Gtk.Align.START)
104
context_menu.set_flags(Gtk.PopoverMenuFlags.NESTED)
105
return context_menu
106
107
def show_context_menu(self, gesture, n_presses, x, y):
108
rect = Gdk.Rectangle()
109
rect.x = int(x)
110
rect.y = int(y)
111
rect.width = 1
112
rect.height = 1
113
114
self.context_menu.set_pointing_to(rect)
115
self.context_menu.popup()
116
117
def update_formatting(self, entry):
118
self.formatting = entry.get_text()
119
# Some placeholders require second precision, but not all of them. If not required,
120
# use minute precision
121
self.has_second_precision = any(placeholder in self.formatting for placeholder in SECOND_PLACEHOLDERS)
122
if self.next_update is not None:
123
GLib.source_remove(self.next_update)
124
self.next_update = None
125
self.set_time()
126
self.emit("config-changed")
127
128
def show_options(self, _0=None, _1=None):
129
if self.options_window is None:
130
self.options_window = ClockOptions()
131
self.options_window.format_entry.set_text(self.formatting)
132
self.options_window.format_entry.connect("changed", self.update_formatting)
133
134
def reset_window(*args):
135
self.options_window = None
136
137
self.options_window.connect("close-request", reset_window)
138
self.options_window.present()
139
140
def set_time(self):
141
datetime = GLib.DateTime.new_now_local()
142
formatted_time = datetime.format(self.formatting)
143
if formatted_time is not None:
144
self.label.set_text(datetime.format(self.formatting))
145
else:
146
self.label.set_text(_("Invalid time formatting"))
147
return False
148
149
if self.has_second_precision:
150
current_ms = GLib.DateTime.new_now_local().get_microsecond() // 1000
151
self.next_update = GLib.timeout_add(1000 - current_ms + 1, self.set_time) # 1ms is added to ensure the clock is updated
152
else:
153
now = GLib.DateTime.new_now_local()
154
current_ms = now.get_second() * 1000 + now.get_microsecond() // 1000
155
self.next_update = GLib.timeout_add(60000 - current_ms + 1, self.set_time)
156
return False # Do not rerun the current timeout; a new one has been scheduled
157
158
def get_config(self):
159
return {"formatting": self.formatting}
160
161
def set_panel_position(self, position):
162
self.popover.set_position(panorama_panel.OPPOSITE_POSITION[position])
163
self.button.set_direction(panorama_panel.POSITION_TO_ARROW[panorama_panel.OPPOSITE_POSITION[position]])
164