Files
nwg-panel/nwg_panel/modules/clock.py

351 lines
14 KiB
Python

#!/usr/bin/env python3
import os.path
from gi.repository import GLib
import subprocess
from datetime import datetime
from nwg_panel.tools import (check_key, eprint, local_dir, load_json, save_json, update_image, update_gtk_entry,
create_background_task, cmd_through_compositor)
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk, GtkLayerShell
class Clock(Gtk.EventBox):
def __init__(self, settings, icons_path=""):
self.reminder_img_updated = False
self.path = ""
self.cal = None
self.note_box = None
self.popup = None
self.note_entry = None
self.icons_path = icons_path
self.settings = settings
Gtk.EventBox.__init__(self)
self.box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=0)
self.add(self.box)
self.label = Gtk.Label.new("")
defaults = {"root-css-name": "root-clock",
"css-name": "clock",
"tooltip-text": "",
"tooltip-date-format": False,
"on-right-click": "",
"on-middle-click": "",
"on-scroll-up": "",
"on-scroll-down": "",
"interval": 1,
"angle": 0.0,
"calendar-path": "",
"calendar-css-name": "calendar-window",
"calendar-placement": "top",
"calendar-margin-horizontal": 0,
"calendar-margin-vertical": 0,
"calendar-icon-size": 24,
"calendar-interval": 60,
"calendar-on": True}
for key in defaults:
check_key(settings, key, defaults[key])
self.reminder_img = Gtk.Image()
self.calendar = {}
self.set_property("name", settings["root-css-name"])
self.label.set_property("name", settings["css-name"])
self.label.set_angle(settings["angle"])
if settings["tooltip-text"]:
self.set_tooltip_text(settings["tooltip-text"])
if "format" not in settings:
self.settings["format"] = "%a, %d. %b %H:%M:%S"
self.connect('button-release-event', self.on_button_release)
if self.settings["calendar-on"] or self.settings["on-middle-click"] or self.settings["on-right-click"] or \
self.settings["on-scroll-up"] or self.settings["on-scroll-down"]:
self.connect('enter-notify-event', self.on_enter_notify_event)
self.connect('leave-notify-event', self.on_leave_notify_event)
if settings["on-scroll-up"] or settings["on-scroll-down"]:
self.add_events(Gdk.EventMask.SCROLL_MASK)
self.connect('scroll-event', self.on_scroll)
self.load_calendar()
self.build_box()
self.refresh()
def update_widget(self, output, tooltip=""):
self.label.set_text(output)
if self.settings["tooltip-date-format"] and tooltip:
self.set_tooltip_text(tooltip)
return False
def get_output(self):
now = datetime.now()
try:
time = now.strftime(self.settings["format"])
tooltip = now.strftime(self.settings["tooltip-text"]) if self.settings["tooltip-date-format"] else ""
GLib.idle_add(self.update_widget, time, tooltip)
except Exception as e:
print(e)
ymd = now.strftime("%Y#%m#%d").split("#")
y = ymd[0]
try:
month = int(ymd[1]) - 1
m = str(month)
except:
m = None
d = ymd[2]
if self.has_note(y, m, d):
if not self.reminder_img_updated:
update_image(self.reminder_img, "gtk-apply", self.settings["calendar-icon-size"], self.icons_path)
self.reminder_img_updated = True
self.reminder_img.set_visible(True)
else:
self.reminder_img.set_visible(False)
def refresh(self):
thread = create_background_task(self.get_output, self.settings["interval"])
thread.start()
thread = create_background_task(self.reload_calendar, self.settings["calendar-interval"])
thread.start()
def build_box(self):
if self.settings["calendar-on"]:
self.box.pack_start(self.reminder_img, False, False, 0)
self.box.pack_start(self.label, False, False, 6)
def on_enter_notify_event(self, widget, event):
widget.set_state_flags(Gtk.StateFlags.DROP_ACTIVE, clear=False)
widget.set_state_flags(Gtk.StateFlags.SELECTED, clear=False)
def on_leave_notify_event(self, widget, event):
widget.unset_state_flags(Gtk.StateFlags.DROP_ACTIVE)
widget.unset_state_flags(Gtk.StateFlags.SELECTED)
def on_button_release(self, widget, event):
if event.button == 1:
if self.settings["calendar-on"]:
self.display_calendar_window()
elif event.button == 2 and self.settings["on-middle-click"]:
self.launch(self.settings["on-middle-click"])
elif event.button == 3 and self.settings["on-right-click"]:
self.launch(self.settings["on-right-click"])
def on_scroll(self, widget, event):
if event.direction == Gdk.ScrollDirection.UP and self.settings["on-scroll-up"]:
self.launch(self.settings["on-scroll-up"])
elif event.direction == Gdk.ScrollDirection.DOWN and self.settings["on-scroll-up"]:
self.launch(self.settings["on-scroll-down"])
else:
print("No command assigned")
def launch(self, cmd):
cmd = cmd_through_compositor(cmd)
print(f"Executing: {cmd}")
subprocess.Popen('{}'.format(cmd), shell=True)
def display_calendar_window(self):
if self.popup:
if self.popup.is_visible():
self.popup.destroy()
return
self.popup.destroy()
self.load_calendar()
self.popup = Gtk.Window.new(Gtk.WindowType.TOPLEVEL)
self.popup.set_property("name", self.settings["calendar-css-name"])
self.popup.connect("key-release-event", self.handle_keyboard)
GtkLayerShell.init_for_window(self.popup)
GtkLayerShell.set_layer(self.popup, GtkLayerShell.Layer.TOP)
GtkLayerShell.set_keyboard_mode(self.popup, GtkLayerShell.KeyboardMode.ON_DEMAND)
if self.settings["calendar-placement"] in ["top-left", "top", "top-right"]:
GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.TOP, 1)
elif self.settings["calendar-placement"] in ["bottom-left", "bottom", "bottom-right"]:
GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.BOTTOM, 1)
if self.settings["calendar-placement"] in ["top-left", "bottom-left"]:
GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.LEFT, 1)
elif self.settings["calendar-placement"] in ["top-right", "bottom-right"]:
GtkLayerShell.set_anchor(self.popup, GtkLayerShell.Edge.RIGHT, 1)
GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.TOP, self.settings["calendar-margin-vertical"])
GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.BOTTOM, self.settings["calendar-margin-vertical"])
GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.RIGHT, self.settings["calendar-margin-horizontal"])
GtkLayerShell.set_margin(self.popup, GtkLayerShell.Edge.LEFT, self.settings["calendar-margin-horizontal"])
vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0)
vbox.set_property("margin", 6)
self.popup.add(vbox)
self.cal = Gtk.Calendar.new()
self.cal.connect("day-selected", self.on_day_selected)
self.cal.connect("next-month", self.mark_days)
self.cal.connect("prev-month", self.mark_days)
self.cal.connect("next-year", self.mark_days)
self.cal.connect("prev-year", self.mark_days)
vbox.pack_start(self.cal, False, False, 0)
self.popup.show_all()
self.mark_days()
self.note_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 0)
self.note_entry = Gtk.Entry()
self.note_entry.set_property("margin-top", 6)
self.note_entry.connect("changed", self.on_note_changed)
self.note_entry.connect("icon-release", self.on_note_icon_click)
update_gtk_entry(self.note_entry, Gtk.EntryIconPosition.SECONDARY,
"edit-clear", self.settings["calendar-icon-size"],
self.icons_path)
vbox.pack_start(self.note_box, False, False, 0)
self.note_box.pack_start(self.note_entry, True, True, 0)
btn = Gtk.Button()
btn.set_property("margin-top", 6)
img = Gtk.Image()
btn.set_image(img)
update_image(img, "gtk-close", self.settings["calendar-icon-size"], self.icons_path)
btn.set_tooltip_text("Cancel & close")
btn.connect("clicked", self.cancel_close_popup)
btn.set_always_show_image(True)
self.note_box.pack_start(btn, False, False, 0)
btn = Gtk.Button()
btn.set_property("margin-top", 6)
img = Gtk.Image()
btn.set_image(img)
update_image(img, "object-select", self.settings["calendar-icon-size"], self.icons_path)
btn.set_tooltip_text("Save & close")
btn.connect("clicked", self.apply_close_popup)
btn.set_always_show_image(True)
self.note_box.pack_start(btn, False, False, 0)
self.popup.set_size_request(self.popup.get_allocated_width() * 2, 0)
y, m, d = self.cal.get_date()
if self.has_note(y, m, d):
self.cal.select_day(d)
def mark_days(self, *args):
for i in range(1, 32):
self.cal.unmark_day(i)
y, m, d = self.cal.get_date()
y, m, d = str(y), str(m), str(d)
if y in self.calendar and m in self.calendar[y]:
for i in range(1, 32):
if str(i) in self.calendar[y][m]:
self.cal.mark_day(i)
def has_note(self, year, month, day):
y = str(year)
m = str(month)
d = str(day)
if y in self.calendar and m in self.calendar[y] and d in self.calendar[y][m] and self.calendar[y][m][d]:
return True
return False
def cancel_close_popup(self, *args):
self.popup.destroy()
def apply_close_popup(self, *args):
c = {}
if len(self.calendar) > 0:
for key_year in self.calendar:
if len(self.calendar[key_year]) > 0:
for key_month in self.calendar[key_year]:
if len(self.calendar[key_year][key_month]) > 0:
for key_day in self.calendar[key_year][key_month]:
if self.calendar[key_year][key_month][key_day]:
if key_year not in c:
c[key_year] = {}
if key_month not in c[key_year]:
c[key_year][key_month] = {}
note = self.calendar[key_year][key_month][key_day]
if note:
c[key_year][key_month][key_day] = note
save_json(c, self.path)
self.popup.destroy()
def load_calendar(self):
if self.settings["calendar-path"]:
self.path = self.settings["calendar-path"]
c = load_json(self.path)
if c is not None:
self.calendar = c
return True
else:
result = save_json(self.calendar, self.path)
if result == "ok":
print("Created new calendar file at '{}'".format(self.path))
return True
else:
eprint("Couldn't create '{}': {}. Using default path.".format(self.path, result))
self.path = os.path.join(local_dir(), "calendar.json")
c = load_json(self.path)
if c is not None:
self.calendar = c
else:
result = save_json(self.calendar, self.path)
if result == "ok":
print("Created new calendar file at '{}'".format(self.path))
return True
else:
eprint("Couldn't create '{}': {}. No more idea...".format(self.path, result))
return True
def reload_calendar(self):
if not self.popup or not self.popup.is_visible():
self.load_calendar()
def handle_keyboard(self, win, event):
if event.type == Gdk.EventType.KEY_RELEASE and event.keyval == Gdk.KEY_Escape:
self.popup.destroy()
def on_day_selected(self, cal):
y, m, d = cal.get_date()
y, m, d = str(y), str(m), str(d)
if y in self.calendar and m in self.calendar[y] and d in self.calendar[y][m]:
self.note_entry.set_text(self.calendar[y][m][d])
else:
self.note_entry.set_text("")
self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, self.note_entry.get_text())
self.note_box.show_all()
def on_note_changed(self, eb):
y, m, d = self.cal.get_date()
y, m, d = str(y), str(m), str(d)
if y not in self.calendar:
self.calendar[y] = {}
if m not in self.calendar[y]:
self.calendar[y][m] = {}
note = eb.get_text()
if note:
self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, True)
self.calendar[y][m][d] = eb.get_text()
elif d in self.calendar[y][m]:
self.note_entry.set_icon_sensitive(Gtk.EntryIconPosition.SECONDARY, False)
self.calendar[y][m].pop(d, None)
def on_note_icon_click(self, entry, icon, event):
entry.set_text("")