351 lines
14 KiB
Python
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("")
|