200 lines
7.1 KiB
Python
200 lines
7.1 KiB
Python
import calendar
|
|
from datetime import datetime
|
|
import gi
|
|
from snippets import MaterialIcon
|
|
from fabric.widgets.label import Label
|
|
|
|
gi.require_version("Gtk", "3.0")
|
|
from gi.repository import Gtk, GLib
|
|
|
|
|
|
class Calendar(Gtk.Box):
|
|
def __init__(self):
|
|
super().__init__(
|
|
orientation=Gtk.Orientation.VERTICAL, spacing=8, name="calendar"
|
|
)
|
|
|
|
self.current_year = datetime.now().year
|
|
self.current_month = datetime.now().month
|
|
self.current_day = datetime.now().day
|
|
self.previous_key = (self.current_year, self.current_month)
|
|
|
|
self.cache_threshold = 1 # Allow current month +/- 1 month
|
|
self.month_views = {}
|
|
|
|
self.header = Gtk.Box(spacing=4, name="header")
|
|
self.pack_start(self.header, False, False, 0)
|
|
|
|
self.prev_month_button = Gtk.Button(
|
|
name="prev-month-button",
|
|
child=MaterialIcon("chevron_left"),
|
|
)
|
|
self.prev_month_button.connect("clicked", self.on_prev_month_clicked)
|
|
self.header.pack_start(self.prev_month_button, False, False, 0)
|
|
|
|
self.month_label = Gtk.Label(name="month-label")
|
|
self.header.pack_start(self.month_label, True, True, 0)
|
|
|
|
self.next_month_button = Gtk.Button(
|
|
name="next-month-button",
|
|
child=MaterialIcon("chevron_right"),
|
|
)
|
|
self.next_month_button.connect("clicked", self.on_next_month_clicked)
|
|
self.header.pack_start(self.next_month_button, False, False, 0)
|
|
|
|
self.weekday_row = Gtk.Box(spacing=4, name="weekday-row")
|
|
self.pack_start(self.weekday_row, False, False, 0)
|
|
|
|
self.stack = Gtk.Stack(name="calendar-stack")
|
|
self.stack.set_transition_duration(500)
|
|
self.pack_start(self.stack, True, True, 0)
|
|
|
|
self.update_header()
|
|
self.update_calendar()
|
|
GLib.timeout_add_seconds(60, self.check_date_change)
|
|
|
|
def update_header(self):
|
|
self.month_label.set_text(
|
|
datetime(self.current_year, self.current_month, 1)
|
|
.strftime("%B %Y")
|
|
.capitalize()
|
|
)
|
|
for child in self.weekday_row.get_children():
|
|
self.weekday_row.remove(child)
|
|
|
|
days = self.get_weekday_initials()
|
|
for day in days:
|
|
label = Gtk.Label(label=day.upper(), name="weekday-label")
|
|
self.weekday_row.pack_start(label, True, True, 0)
|
|
self.weekday_row.show_all()
|
|
|
|
def update_calendar(self):
|
|
new_key = (self.current_year, self.current_month)
|
|
|
|
if new_key > self.previous_key:
|
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
|
|
elif new_key < self.previous_key:
|
|
self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT)
|
|
|
|
self.previous_key = new_key
|
|
|
|
if new_key not in self.month_views:
|
|
month_view = self.create_month_view(self.current_year, self.current_month)
|
|
self.month_views[new_key] = month_view
|
|
self.stack.add_titled(
|
|
month_view,
|
|
f"{self.current_year}_{self.current_month}",
|
|
f"{self.current_year}_{self.current_month}",
|
|
)
|
|
|
|
self.stack.set_visible_child_name(f"{self.current_year}_{self.current_month}")
|
|
self.update_header()
|
|
self.stack.show_all()
|
|
self.prune_cache()
|
|
|
|
def prune_cache(self):
|
|
def month_index(key):
|
|
year, month = key
|
|
return year * 12 + (month - 1)
|
|
|
|
current_index = month_index((self.current_year, self.current_month))
|
|
keys_to_remove = [
|
|
key
|
|
for key in self.month_views
|
|
if abs(month_index(key) - current_index) > self.cache_threshold
|
|
]
|
|
|
|
for key in keys_to_remove:
|
|
widget = self.month_views.pop(key)
|
|
self.stack.remove(widget)
|
|
|
|
def create_month_view(self, year, month):
|
|
grid = Gtk.Grid(
|
|
column_homogeneous=True, row_homogeneous=False, name="calendar-grid"
|
|
)
|
|
|
|
cal = calendar.Calendar(firstweekday=6)
|
|
month_days = cal.monthdayscalendar(year, month)
|
|
|
|
prev_month = month - 1 if month > 1 else 12
|
|
next_month = month + 1 if month < 12 else 1
|
|
prev_year = year if month > 1 else year - 1
|
|
next_year = year if month < 12 else year + 1
|
|
|
|
prev_month_days_count = calendar.monthrange(prev_year, prev_month)[1]
|
|
next_month_day = 1
|
|
|
|
for row, week in enumerate(month_days):
|
|
for col, day in enumerate(week):
|
|
day_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="day-box")
|
|
top_spacer = Gtk.Box(hexpand=True, vexpand=True)
|
|
middle_box = Gtk.Box(hexpand=True, vexpand=True)
|
|
bottom_spacer = Gtk.Box(hexpand=True, vexpand=True)
|
|
|
|
if day == 0:
|
|
if row == 0:
|
|
day_num = prev_month_days_count - week[:col].count(0)
|
|
label = Gtk.Label(label=str(day_num), name="prev-month-day")
|
|
else:
|
|
label = Gtk.Label(
|
|
label=str(next_month_day), name="next-month-day"
|
|
)
|
|
next_month_day += 1
|
|
else:
|
|
label = Gtk.Label(label=str(day), name="day-label")
|
|
if (
|
|
day == self.current_day
|
|
and month == datetime.now().month
|
|
and year == datetime.now().year
|
|
):
|
|
label.get_style_context().add_class("current-day")
|
|
|
|
middle_box.pack_start(
|
|
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
|
|
)
|
|
middle_box.pack_start(label, False, False, 0)
|
|
middle_box.pack_start(
|
|
Gtk.Box(hexpand=True, vexpand=True), True, True, 0
|
|
)
|
|
|
|
day_box.pack_start(top_spacer, True, True, 0)
|
|
day_box.pack_start(middle_box, True, True, 0)
|
|
day_box.pack_start(bottom_spacer, True, True, 0)
|
|
|
|
grid.attach(day_box, col, row, 1, 1)
|
|
|
|
grid.show_all()
|
|
return grid
|
|
|
|
def get_weekday_initials(self):
|
|
return [datetime(2023, 1, i + 1).strftime("%a")[:1] for i in range(7)]
|
|
|
|
def on_prev_month_clicked(self, widget):
|
|
if self.current_month == 1:
|
|
self.current_month = 12
|
|
self.current_year -= 1
|
|
else:
|
|
self.current_month -= 1
|
|
self.update_calendar()
|
|
|
|
def on_next_month_clicked(self, widget):
|
|
if self.current_month == 12:
|
|
self.current_month = 1
|
|
self.current_year += 1
|
|
else:
|
|
self.current_month += 1
|
|
self.update_calendar()
|
|
|
|
def check_date_change(self):
|
|
now = datetime.now()
|
|
if (
|
|
now.day != self.current_day
|
|
or now.month != self.current_month
|
|
or now.year != self.current_year
|
|
):
|
|
self.current_day = now.day
|
|
self.current_month = now.month
|
|
self.current_year = now.year
|
|
self.update_calendar()
|
|
return True
|