Compare commits
239 commits
Author | SHA1 | Date | |
---|---|---|---|
4df439018e | |||
d830281789 | |||
cd68d4c02b | |||
|
4faf625a70 | ||
4a7eb40bb1 | |||
eaa3de9d79 | |||
b7ede5a026 | |||
83f8f37447 | |||
30eb1e4cdd | |||
d3f46c820e | |||
89f9f6d8fc | |||
36f8c35170 | |||
79acea5668 | |||
8e4fbfdfd9 | |||
1e0f59739f | |||
5d881753b8 | |||
ae3b72c86a | |||
387fdb813f | |||
3309dde202 | |||
6bda730c61 | |||
016d022303 | |||
eeb9e03547 | |||
059fc84cb3 | |||
fdb4331aec | |||
464f489ebd | |||
2269bcf3fa | |||
0200116d88 | |||
ad79095a9d | |||
b58357927f | |||
b74def5bbf | |||
2a36a9a6ce | |||
ba8c95ff67 | |||
ccf28e52b7 | |||
8652ef187c | |||
2bbb19b53e | |||
00a05a1a6e | |||
2a94c1fb90 | |||
|
d8a1c1c250 | ||
f53faee1a5 | |||
95bbc8fb59 | |||
9b1ceba95e | |||
aa34b8bc4f | |||
1a62d9d929 | |||
|
d768712181 | ||
f1cc271e3b | |||
08d30bf7d7 | |||
d4df70ce39 | |||
62dd01fb00 | |||
|
f22f4b825f | ||
|
61fd7af37a | ||
3bd7b55010 | |||
1a50ff5cca | |||
3bac199949 | |||
f417fdbe3e | |||
4d3c2cfbd7 | |||
51d4e20553 | |||
083f4bbf6f | |||
710255604a | |||
d2fa6d8fd7 | |||
a7c7a3e553 | |||
b01942b39d | |||
99929d63a4 | |||
1e8d02dc41 | |||
8211b9829e | |||
07c2484849 | |||
5dcae2d435 | |||
28938b45f8 | |||
7ead3c39c1 | |||
6504851aa1 | |||
69f12de1e5 | |||
64b704c10f | |||
08cc2b7daa | |||
6c4eb5c220 | |||
69d1041f98 | |||
76495bd16c | |||
16f38a9102 | |||
d431dae196 | |||
29eddbbd18 | |||
843163dbda | |||
460c1e8a54 | |||
54a53316cc | |||
|
a6ee7fffa1 | ||
383c7b7121 | |||
b0e5e931f3 | |||
1390f3630a | |||
426291f828 | |||
ce73624443 | |||
d2c7428c62 | |||
b6691de9e8 | |||
bf99ecd8e0 | |||
7526c47735 | |||
95cee6287e | |||
6bb0fe247b | |||
f88a4bf702 | |||
f8eddb66d5 | |||
2ea9b09954 | |||
f7a589354f | |||
077d841cab | |||
c09a5d51c4 | |||
0e0803a5e6 | |||
5b858bc121 | |||
904c50224c | |||
63b702cbb4 | |||
8c629051b5 | |||
bad6d280da | |||
c33a34f9d8 | |||
242e7acd09 | |||
188b0497cb | |||
8552874e7d | |||
f8fac5f2d3 | |||
bdd443eea8 | |||
09464dd07d | |||
8ab6826aaf | |||
61a41138fa | |||
002f20504d | |||
06bffa1aea | |||
320a79a869 | |||
43c5fafdba | |||
317a6765f3 | |||
720f053d6a | |||
e6fa543406 | |||
b82bfa2aa7 | |||
bb6f121c42 | |||
1c796f0cc0 | |||
7fd2771efb | |||
956c4a300c | |||
260f9bd70a | |||
b1a796079b | |||
bd69088e0b | |||
d7137ac9c1 | |||
5cb04d9dcf | |||
d206b5e1c6 | |||
c8365ecc63 | |||
009f3cd08b | |||
168d52f86e | |||
2d79ac26fd | |||
33a2e9b2fe | |||
82188fd3af | |||
fdc0197da1 | |||
a6aebf1c37 | |||
0a523d5de9 | |||
5ec99c31bf | |||
6b726ac6b6 | |||
367218ed67 | |||
f0d8507818 | |||
0b665f4976 | |||
4d0b7009cd | |||
7d0b38c479 | |||
efdcf57df0 | |||
ef634bad7c | |||
0df90b39bc | |||
39fa0bce21 | |||
25f2288bdd | |||
4720774011 | |||
a7605e3473 | |||
e9a1142443 | |||
fd1edb02b7 | |||
3bf7bc0205 | |||
10af39c985 | |||
fd14986b7c | |||
eb23b7236f | |||
5ada365bbc | |||
d525683a27 | |||
7decd58eee | |||
49f50dc944 | |||
4ca32e49ab | |||
b10de72b14 | |||
a5bfd90b5e | |||
c48af815b9 | |||
c9fc0341c4 | |||
05f0412b94 | |||
d9b83cca9d | |||
2cebf0a60d | |||
913313245f | |||
56ee85b60f | |||
fdd35a157f | |||
e86de45c63 | |||
2763abeeb4 | |||
66b5b95f74 | |||
a9a639ed2b | |||
0dd1ca9b45 | |||
d7f485cba6 | |||
afd94ea10f | |||
4b656ee819 | |||
22242d894f | |||
9e67af6adc | |||
3e11ec4df0 | |||
6a37255b57 | |||
c93210356a | |||
9519e3aaed | |||
f2581e57a6 | |||
757a66eb6e | |||
1ff09a5591 | |||
a4be256b50 | |||
a65bfddeeb | |||
45dd474bf3 | |||
5faa0c5d1c | |||
bd03a2b604 | |||
225963aaeb | |||
1c56323ff0 | |||
eaeea2083e | |||
ab9e34980a | |||
6146fc7c2d | |||
b01969d2da | |||
59d2401f81 | |||
9e08553232 | |||
ddb5f727d9 | |||
4ef90e14b3 | |||
bef88f0b85 | |||
319e6947b1 | |||
94591ec51e | |||
6dd6de16ce | |||
019d150e78 | |||
86c64eeba5 | |||
99bba341a9 | |||
bcca8a8f8a | |||
88ace91239 | |||
c854e7eec1 | |||
544c23954d | |||
0e1b6bef57 | |||
571846ae39 | |||
3a2f145e22 | |||
4b1cbed0f7 | |||
d838ee37d0 | |||
ba7c437a13 | |||
7073d5bc5d | |||
11ffdeac13 | |||
4cfe34ed93 | |||
139c59bfab | |||
dcbaa4893e | |||
3e63f5ff8b | |||
5838734c9f | |||
672d7058d8 | |||
f7c5f6b05e | |||
8686637271 | |||
b1705941e8 | |||
c0a6ebe33f | |||
27f2bd5eef | |||
c99bb1538b |
4
.gitignore
vendored
|
@ -15,3 +15,7 @@
|
||||||
/Calendar/CalendarInterface.pyc
|
/Calendar/CalendarInterface.pyc
|
||||||
/Calendar/CalendarEvent.pyc
|
/Calendar/CalendarEvent.pyc
|
||||||
/Calendar/design_exported_old.png
|
/Calendar/design_exported_old.png
|
||||||
|
/Calendar/settings_dev.py
|
||||||
|
/Calendar/settings_pers.py
|
||||||
|
/.vscode
|
||||||
|
/Calendar/images/
|
101
Calendar/AgendaListDesign.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from Assets import defaultfontsize, colors, defaultfont, path
|
||||||
|
from datetime import datetime, date, timedelta
|
||||||
|
from TableDesign import TableDesign
|
||||||
|
from PIL import ImageDraw, ImageFont
|
||||||
|
from TextFormatter import date_summary_str, event_prefix_str
|
||||||
|
from settings import line_thickness
|
||||||
|
|
||||||
|
separator_width = line_thickness
|
||||||
|
|
||||||
|
|
||||||
|
class AgendaListDesign (DesignEntity):
|
||||||
|
'''Lists upcoming events in chronological order and groups them by days'''
|
||||||
|
|
||||||
|
def __init__(self, size, calendar, line_spacing=0, col_spacing=8, text_size=defaultfontsize, start_date=date.today(), always_add_start_row=True, day_limit_foresight=91):
|
||||||
|
super(AgendaListDesign, self).__init__(size)
|
||||||
|
self.calendar = calendar
|
||||||
|
self.line_spacing = line_spacing
|
||||||
|
self.col_spacing = col_spacing
|
||||||
|
self.text_size = text_size
|
||||||
|
self.day_limit_foresight = day_limit_foresight
|
||||||
|
self.start_dt = date(start_date.year, start_date.month, start_date.day)
|
||||||
|
self.always_add_start_row = always_add_start_row
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
self.__calculate_parameter__()
|
||||||
|
self.__create_infos_events__()
|
||||||
|
self.__draw_infos__()
|
||||||
|
self.__draw_lines__()
|
||||||
|
|
||||||
|
def __calculate_parameter__(self):
|
||||||
|
self.__line_height__ = self.line_spacing + self.__get_text_height__()
|
||||||
|
self.__event_number__ = int(int(self.size[1]) // self.__line_height__)
|
||||||
|
self.__date_fontsize__ = self.text_size
|
||||||
|
self.__date_linespace__ = self.line_spacing
|
||||||
|
|
||||||
|
def __create_infos_events__(self):
|
||||||
|
self.infos = []
|
||||||
|
self.cell_props = []
|
||||||
|
fetch_day = self.start_dt
|
||||||
|
days_foresight = 0
|
||||||
|
while len(self.infos) < self.__event_number__ and days_foresight < self.day_limit_foresight:
|
||||||
|
day_events = self.calendar.get_day_events(fetch_day)
|
||||||
|
fetch_day_added_once = False
|
||||||
|
for event in day_events:
|
||||||
|
row = [""]
|
||||||
|
if fetch_day_added_once is False:
|
||||||
|
row.append(date_summary_str(fetch_day))
|
||||||
|
fetch_day_added_once = True
|
||||||
|
else:
|
||||||
|
row.append("")
|
||||||
|
|
||||||
|
row.append(event_prefix_str(event, fetch_day))
|
||||||
|
row.append(event.title)
|
||||||
|
self.cell_props.append(self.__get_row_props__(event))
|
||||||
|
|
||||||
|
self.infos.append(row)
|
||||||
|
fetch_day = fetch_day + timedelta(1)
|
||||||
|
days_foresight = days_foresight + 1
|
||||||
|
|
||||||
|
if self.infos[0][1] != date_summary_str(self.start_dt) and self.always_add_start_row:
|
||||||
|
row = ["", date_summary_str(self.start_dt), "", ""]
|
||||||
|
props = self.__get_row_props__()
|
||||||
|
self.infos.insert(0, row)
|
||||||
|
self.cell_props.insert(0, props)
|
||||||
|
|
||||||
|
def __draw_infos__(self):
|
||||||
|
table = TableDesign(self.size, self.infos, fontsize=self.__date_fontsize__,
|
||||||
|
line_spacing=self.__date_linespace__, col_spacing=self.col_spacing, cell_properties=self.cell_props)
|
||||||
|
self.draw_design(table)
|
||||||
|
|
||||||
|
def __draw_lines__(self):
|
||||||
|
for i, (_, date, _, _) in enumerate(self.infos[1:]):
|
||||||
|
if date is not "":
|
||||||
|
self.__draw_line__(i + 1)
|
||||||
|
|
||||||
|
def __draw_line__(self, index):
|
||||||
|
ypos = index * self.__line_height__ - self.line_spacing / 2
|
||||||
|
pos = (0, ypos)
|
||||||
|
positions = [pos, (self.size[0], ypos)]
|
||||||
|
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
positions, fill=colors["fg"], width=separator_width)
|
||||||
|
|
||||||
|
def __get_row_props__(self, event=None):
|
||||||
|
color = colors["fg"]
|
||||||
|
bg_color = colors["bg"]
|
||||||
|
default_cell = {
|
||||||
|
"color": color,
|
||||||
|
"background_color": bg_color
|
||||||
|
}
|
||||||
|
if event is not None and event.highlight:
|
||||||
|
color = colors["hl"]
|
||||||
|
cell = {
|
||||||
|
"color": color,
|
||||||
|
"background_color": bg_color
|
||||||
|
}
|
||||||
|
return [default_cell, default_cell, cell, cell]
|
||||||
|
|
||||||
|
def __get_text_height__(self):
|
||||||
|
return ImageFont.truetype(path + defaultfont, self.text_size).font.height
|
90
Calendar/AgendaListPanel.py
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from AgendaListDesign import AgendaListDesign
|
||||||
|
from WeatherHeaderDesign import WeatherHeaderDesign
|
||||||
|
from settings import general_settings, line_thickness
|
||||||
|
from PIL import ImageDraw
|
||||||
|
from Assets import colors
|
||||||
|
from RssPostListDesign import RssPostListDesign
|
||||||
|
from CryptoListDesign import CryptoListDesign
|
||||||
|
|
||||||
|
agenda_ypadding = 5
|
||||||
|
weatherheader_height = 0.113
|
||||||
|
seperator_width = line_thickness
|
||||||
|
infolist_size = (1, 0.24)
|
||||||
|
infolist_padding = 0
|
||||||
|
|
||||||
|
|
||||||
|
class AgendaListPanel (PanelDesign):
|
||||||
|
'''Lists upcoming events in chronological order and groups them by days'''
|
||||||
|
|
||||||
|
def __init__(self, size):
|
||||||
|
super(AgendaListPanel, self).__init__(size)
|
||||||
|
self.weather_size = (0, 0)
|
||||||
|
self.info_size = (0, 0)
|
||||||
|
if general_settings["weather-info"]:
|
||||||
|
self.weather_size = (
|
||||||
|
self.size[0], self.size[1] * weatherheader_height)
|
||||||
|
|
||||||
|
def add_weather(self, weather):
|
||||||
|
self.weather = weather
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
|
self.calendar = calendar
|
||||||
|
|
||||||
|
def add_rssfeed(self, rss):
|
||||||
|
if general_settings["info-area"] != "rss":
|
||||||
|
return
|
||||||
|
|
||||||
|
self.info_size = self.__abs_pos__(infolist_size)
|
||||||
|
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
|
||||||
|
|
||||||
|
list = RssPostListDesign(self.info_size, rss)
|
||||||
|
list.pos = pos
|
||||||
|
self.draw_design(list)
|
||||||
|
|
||||||
|
self.__draw_seperator__(1-infolist_size[1], colors["fg"])
|
||||||
|
|
||||||
|
def add_tasks(self, tasks):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_crypto(self, crypto):
|
||||||
|
if general_settings["info-area"] != "crypto":
|
||||||
|
return
|
||||||
|
|
||||||
|
self.info_size = self.__abs_pos__(infolist_size)
|
||||||
|
pos = (0, self.size[1] - self.info_size[1] + infolist_padding)
|
||||||
|
|
||||||
|
list = CryptoListDesign(self.info_size, crypto)
|
||||||
|
height = list.get_estimated_height()
|
||||||
|
list.pos = (pos[0], pos[1] + (self.info_size[1] - height))
|
||||||
|
self.draw_design(list)
|
||||||
|
|
||||||
|
self.info_size = (self.size[0], height)
|
||||||
|
self.__draw_seperator__(list.pos[1] / self.size[1], colors["fg"])
|
||||||
|
|
||||||
|
def __finish_panel__(self):
|
||||||
|
self.__draw_calendar__()
|
||||||
|
if general_settings["weather-info"]:
|
||||||
|
self.__draw_weather__()
|
||||||
|
|
||||||
|
def __draw_seperator__(self, height, color):
|
||||||
|
ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
|
||||||
|
(0, height)), self.__abs_pos__((1, height))], fill=color, width=seperator_width)
|
||||||
|
|
||||||
|
def __abs_pos__(self, pos, size=None):
|
||||||
|
if size is None:
|
||||||
|
size = self.size
|
||||||
|
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
||||||
|
|
||||||
|
def __draw_calendar__(self):
|
||||||
|
size = (self.size[0], self.size[1] - self.weather_size[1] -
|
||||||
|
self.info_size[1] - agenda_ypadding)
|
||||||
|
|
||||||
|
agenda = AgendaListDesign(size, self.calendar)
|
||||||
|
agenda.pos = (0, agenda_ypadding + self.weather_size[1])
|
||||||
|
self.draw_design(agenda)
|
||||||
|
|
||||||
|
def __draw_weather__(self):
|
||||||
|
header = WeatherHeaderDesign(self.weather_size, self.weather)
|
||||||
|
self.draw_design(header)
|
||||||
|
self.__draw_seperator__(weatherheader_height, colors["hl"])
|
|
@ -1,44 +1,95 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from PIL import Image, ImageFont
|
from PIL import Image, ImageFont
|
||||||
from settings import *
|
from settings import font_boldness, font_size
|
||||||
|
import os
|
||||||
im_open = Image.open
|
im_open = Image.open
|
||||||
|
|
||||||
path = ''
|
path = os.path.dirname(os.path.abspath(__file__)).replace("\\", "/")
|
||||||
wpath = path+'weather-icons/'
|
if path != "" and path[-1] != "/":
|
||||||
opath = path+'other/'
|
path += "/"
|
||||||
fpath = path+'fonts/'
|
|
||||||
|
|
||||||
tempicon = im_open(opath+'temperature.jpeg')
|
wpath = path+'weather-icons/'
|
||||||
humicon = im_open(opath+'humidity.jpeg')
|
opath = path+'other/'
|
||||||
no_response= im_open(opath+'cloud-no-response.jpeg')
|
fpath = 'fonts/'
|
||||||
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
|
|
||||||
sunseticon = im_open(opath+'wi-sunset.jpeg')
|
tempicon = im_open(opath+'temperature.jpeg')
|
||||||
windicon = im_open(opath+'wi-strong-wind.jpeg')
|
humicon = im_open(opath+'humidity.jpeg')
|
||||||
|
no_response = im_open(opath+'cloud-no-response.jpeg')
|
||||||
|
sunriseicon = im_open(opath+'wi-sunrise.jpeg')
|
||||||
|
sunseticon = im_open(opath+'wi-sunset.jpeg')
|
||||||
|
windicon = im_open(opath+'wi-strong-wind.jpeg')
|
||||||
|
|
||||||
fonts = {
|
fonts = {
|
||||||
"extralight" : fpath + "Assistant-ExtraLight.otf",
|
"extralight": fpath + "Assistant-ExtraLight.otf",
|
||||||
"light" : fpath + "Assistant-Light.otf",
|
"light": fpath + "Assistant-Light.otf",
|
||||||
"regular" : fpath + "Assistant-Regular.otf",
|
"regular": fpath + "Assistant-Regular.otf",
|
||||||
"semibold" : fpath + "Assistant-SemiBold.otf",
|
"semibold": fpath + "Assistant-SemiBold.otf",
|
||||||
"bold" : fpath + "Assistant-Bold.otf",
|
"bold": fpath + "Assistant-Bold.otf",
|
||||||
"extrabold" : fpath + "Assistant-ExtraBold.otf"
|
"extrabold": fpath + "Assistant-ExtraBold.otf"
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultfont = fonts[font_boldness]
|
defaultfont = fonts[font_boldness]
|
||||||
|
defaultfontsize = int(font_size)
|
||||||
datetime_locals = {
|
|
||||||
"de" : "de_DE.UTF-8",
|
|
||||||
"en" : "en_US.UTF-8",
|
|
||||||
"zh_TW" : "zh_TW.UTF-8"
|
|
||||||
}
|
|
||||||
|
|
||||||
weathericons = {
|
weathericons = {
|
||||||
'01d': 'wi-day-sunny', '02d':'wi-day-cloudy', '03d': 'wi-cloudy',
|
'01d': 'wi-day-sunny', '02d': 'wi-day-cloudy', '03d': 'wi-cloudy',
|
||||||
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d':'wi-rain',
|
'04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d': 'wi-rain',
|
||||||
'11d':'wi-thunderstorm', '13d':'wi-snow', '50d': 'wi-fog',
|
'11d': 'wi-thunderstorm', '13d': 'wi-snow', '50d': 'wi-fog',
|
||||||
'01n': 'wi-night-clear', '02n':'wi-night-cloudy',
|
'01n': 'wi-night-clear', '02n': 'wi-night-cloudy',
|
||||||
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
|
'03n': 'wi-night-cloudy', '04n': 'wi-night-cloudy',
|
||||||
'09n': 'wi-night-showers', '10n':'wi-night-rain',
|
'09n': 'wi-night-showers', '10n': 'wi-night-rain',
|
||||||
'11n':'wi-night-thunderstorm', '13n':'wi-night-snow',
|
'11n': 'wi-night-thunderstorm', '13n': 'wi-night-snow',
|
||||||
'50n': 'wi-night-alt-cloudy-windy'}
|
'50n': 'wi-night-alt-cloudy-windy'}
|
||||||
|
|
||||||
|
colors = {
|
||||||
|
"hl": "red",
|
||||||
|
"fg": "black",
|
||||||
|
"bg": "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
supported_img_formats = [
|
||||||
|
"BMP",
|
||||||
|
"DIB",
|
||||||
|
"EPS",
|
||||||
|
"GIF",
|
||||||
|
"ICNS",
|
||||||
|
"ICO",
|
||||||
|
"IM",
|
||||||
|
"JPG",
|
||||||
|
"JPEG",
|
||||||
|
"J2K",
|
||||||
|
"J2P",
|
||||||
|
"JPX",
|
||||||
|
"MSP",
|
||||||
|
"PCX",
|
||||||
|
"PNG",
|
||||||
|
"PPM",
|
||||||
|
"SGI",
|
||||||
|
"SPI",
|
||||||
|
"TGA",
|
||||||
|
"TIFF",
|
||||||
|
"WEBP",
|
||||||
|
"XBM",
|
||||||
|
"BLP",
|
||||||
|
"CUR",
|
||||||
|
"DCX",
|
||||||
|
"DDS",
|
||||||
|
"FLI",
|
||||||
|
"FLC",
|
||||||
|
"FPX",
|
||||||
|
"FTEX",
|
||||||
|
"GBR",
|
||||||
|
"GD",
|
||||||
|
"IMT",
|
||||||
|
"IPTC",
|
||||||
|
"NAA",
|
||||||
|
"MCIDAS",
|
||||||
|
"MIC",
|
||||||
|
"MPO",
|
||||||
|
"PCD",
|
||||||
|
"PIXAR",
|
||||||
|
"PSD",
|
||||||
|
"WAL",
|
||||||
|
"XPM",
|
||||||
|
]
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from PIL import ImageDraw, ImageOps
|
from PIL import ImageDraw, ImageOps
|
||||||
|
|
||||||
|
|
||||||
class BoxDesign (DesignEntity):
|
class BoxDesign (DesignEntity):
|
||||||
"""Redefinition of ImageDraw.Draw.Rectangle"""
|
"""Redefinition of ImageDraw.Draw.Rectangle"""
|
||||||
|
|
||||||
def __init__(self, size, fill=None, outline=None, width=0):
|
def __init__(self, size, fill=None, outline=None, width=0):
|
||||||
super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True)
|
super(BoxDesign, self).__init__((size[0]+1, size[1]+1), mask=True)
|
||||||
self.size = size
|
self.size = size
|
||||||
|
@ -12,9 +14,10 @@ class BoxDesign (DesignEntity):
|
||||||
self.width = width
|
self.width = width
|
||||||
|
|
||||||
def __define_corners__(self):
|
def __define_corners__(self):
|
||||||
topleft = (0,0)
|
topleft = (0, 0)
|
||||||
bottomright = self.size
|
bottomright = self.size
|
||||||
self.corners = [topleft, bottomright]
|
self.corners = [topleft, bottomright]
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
ImageDraw.Draw(self.__image__).rectangle(self.corners, fill=self.fill, outline=self.outline, width=self.width)
|
ImageDraw.Draw(self.__image__).rectangle(
|
||||||
|
self.corners, fill=self.fill, outline=self.outline, width=self.width)
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
class CalendarEvent (object):
|
class CalendarEvent (object):
|
||||||
"""Defines a calendar event, independent of any implementation"""
|
"""Defines a calendar event, independent of any implementation"""
|
||||||
def __init__ (self):
|
|
||||||
|
def __init__(self):
|
||||||
self.begin_datetime = None
|
self.begin_datetime = None
|
||||||
self.end_datetime = None
|
self.end_datetime = None
|
||||||
self.duration = None
|
self.duration = None
|
||||||
self.all_day = None
|
self.allday = None
|
||||||
|
self.multiday = None
|
||||||
|
self.rrule = None
|
||||||
|
|
||||||
self.title = None
|
self.title = None
|
||||||
self.description = None
|
self.description = None
|
||||||
|
@ -12,9 +15,10 @@ class CalendarEvent (object):
|
||||||
self.highlight = None
|
self.highlight = None
|
||||||
|
|
||||||
self.calendar_name = None
|
self.calendar_name = None
|
||||||
|
self.calendar_url = None
|
||||||
|
|
||||||
self.location = None
|
self.location = None
|
||||||
self.fetch_datetime = None
|
self.fetch_datetime = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.title
|
return self.title
|
||||||
|
|
|
@ -1,44 +1,182 @@
|
||||||
from DataSourceInterface import DataSourceInterface
|
from DataSourceInterface import DataSourceInterface
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta, date
|
||||||
|
from dateutil.rrule import rrulestr
|
||||||
|
from dateutil.parser import parse
|
||||||
|
import calendar
|
||||||
|
from CalendarEvent import CalendarEvent
|
||||||
|
|
||||||
|
|
||||||
class CalendarInterface (DataSourceInterface):
|
class CalendarInterface (DataSourceInterface):
|
||||||
"""Interface for fetching and processing calendar event information."""
|
"""Interface for fetching and processing calendar event information."""
|
||||||
def __init__(self):
|
|
||||||
self.loaded_events = self.__get_events__()
|
|
||||||
self.__sort_events__()
|
|
||||||
|
|
||||||
def __sort_events__(self):
|
def __init__(self):
|
||||||
self.loaded_events.sort(key=lambda x : x.begin_datetime)
|
self.events = []
|
||||||
|
self.excluded_urls = []
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.is_available() == False:
|
||||||
|
return
|
||||||
|
self.events = self.__get_events__()
|
||||||
|
self.events = self.__sort_events__(self.events)
|
||||||
|
|
||||||
|
def exclude_calendars(self, urls=[]):
|
||||||
|
self.excluded_urls = urls
|
||||||
|
|
||||||
|
def __sort_events__(self, events):
|
||||||
|
events.sort(key=lambda x: x.begin_datetime)
|
||||||
|
return events
|
||||||
|
|
||||||
|
def __sort_event_types__(self, events):
|
||||||
|
multiday = [ev for ev in events if ev.multiday]
|
||||||
|
allday = [ev for ev in events if ev.allday and ev.multiday == False]
|
||||||
|
timed = [ev for ev in events if ev.allday ==
|
||||||
|
False and ev.multiday == False]
|
||||||
|
return multiday + allday + timed
|
||||||
|
|
||||||
def __get_events__(self):
|
def __get_events__(self):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def get_upcoming_events(self):
|
def get_upcoming_events(self, timespan=None, start_time=None):
|
||||||
return self.__get_events_to_filter__(lambda x : (x.begin_datetime - datetime.now(timezone.utc)) > timedelta(days=-1))
|
if timespan is None:
|
||||||
|
timespan = timedelta(31)
|
||||||
|
if start_time == None:
|
||||||
|
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||||
|
start_time = datetime.now(local_tzinfo)
|
||||||
|
return self.__get_events_in_range__(start_time, timespan)
|
||||||
|
|
||||||
def get_today_events(self):
|
def get_today_events(self):
|
||||||
return self.get_day_events(datetime.now(timezone.utc))
|
return self.get_day_events(date.today())
|
||||||
|
|
||||||
def get_day_events(self, date):
|
def get_day_events(self, day):
|
||||||
return self.__get_events_to_filter__(lambda x : x.begin_datetime.strftime('%d-%m-%y') == date.strftime('%d-%m-%y'))
|
if type(day) is not type(date.today()):
|
||||||
|
raise TypeError(
|
||||||
|
"get_day_events only takes date-objects as parameters, not \"%s\"" % str(type(day)))
|
||||||
|
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||||
|
day_start = datetime(day.year, day.month, day.day,
|
||||||
|
0, 0, 0, 0, local_tzinfo)
|
||||||
|
return self.__get_events_in_range__(day_start, timedelta(1))
|
||||||
|
|
||||||
def get_month_events(self, month = -1):
|
def get_month_events(self, month=-1, year=-1):
|
||||||
if month < 0:
|
if month < 0:
|
||||||
month = datetime.now().month
|
month = datetime.now().month
|
||||||
return self.__get_events_to_filter__(lambda x : x.begin_datetime.month == month)
|
if year < 0:
|
||||||
|
year = datetime.now().year
|
||||||
|
|
||||||
def get_week_events(self, week = -1):
|
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||||
if week < 0 and week_starts_on == "Monday":
|
month_start = datetime(year, month, 1, 0, 0, 0, 0, local_tzinfo)
|
||||||
week = int(datetime.now().strftime('%W')) + 1
|
month_days = calendar.monthrange(
|
||||||
elif week < 0:
|
month_start.year, month_start.month)[1]
|
||||||
week = int(datetime.now().strftime('%U')) + 1
|
return self.__get_events_in_range__(month_start, timedelta(month_days))
|
||||||
|
|
||||||
if week_starts_on == "Monday":
|
def __get_events_in_range__(self, start, duration):
|
||||||
return self.__get_events_to_filter__(lambda x : int(x.begin_datetime.strftime('%W')) + 1 == week)
|
if self.events is None:
|
||||||
else:
|
|
||||||
return self.__get_events_to_filter__(lambda x : int(x.begin_datetime.strftime('%U')) + 1 == week)
|
|
||||||
|
|
||||||
def __get_events_to_filter__(self, event_filter):
|
|
||||||
if self.loaded_events is None:
|
|
||||||
return []
|
return []
|
||||||
return [event for event in self.loaded_events if event_filter(event)]
|
|
||||||
|
if start.tzinfo is None:
|
||||||
|
raise TypeError("start datetime needs to be timezone-aware")
|
||||||
|
|
||||||
|
events_in_range = []
|
||||||
|
for event in self.events:
|
||||||
|
# Is excluded?
|
||||||
|
if event.calendar_url in self.excluded_urls:
|
||||||
|
continue
|
||||||
|
|
||||||
|
event_occurrence = self.__get_if_event_in_range__(
|
||||||
|
event, start, duration)
|
||||||
|
if event_occurrence:
|
||||||
|
events_in_range.extend(event_occurrence)
|
||||||
|
|
||||||
|
events_in_range = self.__sort_events__(events_in_range)
|
||||||
|
return self.__sort_event_types__(events_in_range)
|
||||||
|
|
||||||
|
def __get_if_event_in_range__(self, event, start, duration):
|
||||||
|
'''Returns list or None'''
|
||||||
|
if event is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if event.rrule is None:
|
||||||
|
return self.__is_onetime_in_range__(event, start, duration)
|
||||||
|
else:
|
||||||
|
return self.__is_repeating_in_range__(event, start, duration)
|
||||||
|
|
||||||
|
def __is_onetime_in_range__(self, event, start, duration):
|
||||||
|
if event.begin_datetime > start:
|
||||||
|
first_start = start
|
||||||
|
first_duration = duration
|
||||||
|
second_start = event.begin_datetime
|
||||||
|
else:
|
||||||
|
first_start = event.begin_datetime
|
||||||
|
first_duration = event.duration
|
||||||
|
second_start = start
|
||||||
|
|
||||||
|
if (second_start - first_start) < first_duration:
|
||||||
|
return [event]
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def __is_repeating_in_range__(self, event, start, duration):
|
||||||
|
end = start + duration
|
||||||
|
occurrences = []
|
||||||
|
|
||||||
|
try:
|
||||||
|
r_string = ""
|
||||||
|
r_string = self.__add_timezoneawarness__(event.rrule)
|
||||||
|
rule = rrulestr(r_string, dtstart=event.begin_datetime)
|
||||||
|
for occurrence in rule:
|
||||||
|
if occurrence - end > timedelta(0):
|
||||||
|
return occurrences
|
||||||
|
merged_event = self.__merge_event_data__(
|
||||||
|
event, start=occurrence)
|
||||||
|
if self.__is_onetime_in_range__(merged_event, start, duration):
|
||||||
|
occurrences.append(merged_event)
|
||||||
|
return occurrences
|
||||||
|
except Exception as ex:
|
||||||
|
print("\"is_repeating_in_range\" failed while processing: dtstart="+str(event.begin_datetime) +
|
||||||
|
" dtstart.tzinfo="+str(event.begin_datetime.tzinfo)+" rrule="+r_string)
|
||||||
|
raise ex
|
||||||
|
|
||||||
|
def __merge_event_data__(self, event, start=None):
|
||||||
|
merged_event = CalendarEvent()
|
||||||
|
|
||||||
|
merged_event.begin_datetime = event.begin_datetime
|
||||||
|
merged_event.end_datetime = event.end_datetime
|
||||||
|
merged_event.duration = event.duration
|
||||||
|
merged_event.allday = event.allday
|
||||||
|
merged_event.multiday = event.multiday
|
||||||
|
merged_event.rrule = event.rrule
|
||||||
|
|
||||||
|
merged_event.title = event.title
|
||||||
|
merged_event.description = event.description
|
||||||
|
merged_event.attendees = event.attendees
|
||||||
|
merged_event.highlight = event.highlight
|
||||||
|
|
||||||
|
merged_event.calendar_name = event.calendar_name
|
||||||
|
merged_event.calendar_url = event.calendar_url
|
||||||
|
|
||||||
|
merged_event.location = event.location
|
||||||
|
merged_event.fetch_datetime = event.fetch_datetime
|
||||||
|
|
||||||
|
if start is not None:
|
||||||
|
merged_event.begin_datetime = start
|
||||||
|
merged_event.end_datetime = start + event.duration
|
||||||
|
|
||||||
|
return merged_event
|
||||||
|
|
||||||
|
def __add_timezoneawarness__(self, rrule):
|
||||||
|
"""UNTIL must be specified in UTC when DTSTART is timezone-aware (which it is)"""
|
||||||
|
if "UNTIL" not in rrule:
|
||||||
|
return rrule
|
||||||
|
|
||||||
|
timezone_str = "T000000Z"
|
||||||
|
until_template = "UNTIL=YYYYMMDD"
|
||||||
|
|
||||||
|
until_index = rrule.index("UNTIL")
|
||||||
|
|
||||||
|
tz_index = until_index + len(until_template)
|
||||||
|
if until_index < 0 or (tz_index < len(rrule) and rrule[tz_index] is "T"):
|
||||||
|
return rrule
|
||||||
|
|
||||||
|
if tz_index == len(rrule):
|
||||||
|
return rrule + timezone_str
|
||||||
|
else:
|
||||||
|
return rrule[:tz_index] + timezone_str + rrule[tz_index:]
|
||||||
|
|
10
Calendar/CryptoCoin.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
class CryptoCoin(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.name = None
|
||||||
|
self.symbol = None
|
||||||
|
self.price = None
|
||||||
|
self.day_change = None
|
||||||
|
self.currency = None
|
||||||
|
self.datetime = None
|
||||||
|
|
||||||
|
self.fetch_datetime = None
|
17
Calendar/CryptoInterface.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from DataSourceInterface import DataSourceInterface
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoInterface(DataSourceInterface):
|
||||||
|
def __init__(self):
|
||||||
|
self.crypto_coins = []
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.is_available() == False:
|
||||||
|
return
|
||||||
|
self.crypto_coins = self.__get_coins__()
|
||||||
|
|
||||||
|
def __get_coins__(self):
|
||||||
|
raise NotImplementedError("Function needs to be implemented")
|
||||||
|
|
||||||
|
def get_coins(self):
|
||||||
|
return self.crypto_coins
|
39
Calendar/CryptoListDesign.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from TableDesign import TableDesign
|
||||||
|
from Assets import defaultfontsize
|
||||||
|
from GeckoCrypto import GeckoCrypto
|
||||||
|
from settings import crypto_coins
|
||||||
|
|
||||||
|
xpadding = 5
|
||||||
|
|
||||||
|
|
||||||
|
class CryptoListDesign (DesignEntity):
|
||||||
|
def __init__(self, size, crypto, text_size=defaultfontsize):
|
||||||
|
super(CryptoListDesign, self).__init__(size)
|
||||||
|
self.crypto = crypto
|
||||||
|
self.text_size = text_size
|
||||||
|
self.matrix = self.__get_matrix__()
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
col_spacing = 10
|
||||||
|
if len(self.matrix) > 0:
|
||||||
|
col_spacing = (self.size[0] / len(self.matrix[0])) * 0.5
|
||||||
|
|
||||||
|
table_design = TableDesign(self.size, matrix=self.matrix, col_spacing=col_spacing,
|
||||||
|
fontsize=self.text_size, mask=False, truncate_rows=True)
|
||||||
|
table_design.pos = (xpadding, 0)
|
||||||
|
self.draw_design(table_design)
|
||||||
|
|
||||||
|
def __get_matrix__(self):
|
||||||
|
matrix = []
|
||||||
|
coins = self.crypto.get_coins()
|
||||||
|
for coin in coins:
|
||||||
|
row = [coin.symbol.upper(), coin.name, coin.currency + " " +
|
||||||
|
str(coin.price), "% " + str(coin.day_change)]
|
||||||
|
matrix.append(row)
|
||||||
|
return matrix
|
||||||
|
|
||||||
|
def get_estimated_height(self):
|
||||||
|
line_height = self.text_size * 1.25
|
||||||
|
height = line_height * len(self.matrix)
|
||||||
|
return height
|
|
@ -1,4 +1,8 @@
|
||||||
class DataSourceInterface (object):
|
class DataSourceInterface (object):
|
||||||
"""Interface for child interfaces that fetch data."""
|
"""Interface for child interfaces that fetch data."""
|
||||||
def is_available (self):
|
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
def is_available(self):
|
||||||
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
33
Calendar/DayBoxDesign.py
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||||
|
from TextDesign import TextDesign
|
||||||
|
|
||||||
|
header_height = 0.2
|
||||||
|
|
||||||
|
|
||||||
|
class DayBoxDesign (DesignEntity):
|
||||||
|
"""Represents a day with its events in a box."""
|
||||||
|
|
||||||
|
def __init__(self, size, date):
|
||||||
|
super(DayBoxDesign, self).__init__(size)
|
||||||
|
self.date = date
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
|
self.calendar = calendar
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
self.__draw_header__()
|
||||||
|
self.__draw_events__()
|
||||||
|
|
||||||
|
def __draw_header__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __draw_events__(self):
|
||||||
|
events = self.calendar.get_day_events(self.date)
|
||||||
|
|
||||||
|
pos = (0, self.size[1] * header_height)
|
||||||
|
size = (self.size[0], self.size[1] - pos[1])
|
||||||
|
|
||||||
|
event_list = SingelDayEventListDesign(size, events)
|
||||||
|
event_list.pos = pos
|
||||||
|
self.draw_design(event_list)
|
151
Calendar/DayFocusListPanel.py
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
from datetime import date, datetime, timedelta, timezone
|
||||||
|
from settings import line_thickness, general_settings
|
||||||
|
from DayHeaderDesign import DayHeaderDesign
|
||||||
|
from HourListDesign import HourListDesign
|
||||||
|
from DayRowDesign import DayRowDesign
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from Assets import colors
|
||||||
|
from PIL import ImageDraw
|
||||||
|
|
||||||
|
HEADER_SIZE = (1, 0.2)
|
||||||
|
HOURLIST_HEIGHT = 0.3
|
||||||
|
HOURLIST_SIZE = (1, HOURLIST_HEIGHT)
|
||||||
|
DAYLIST_YPOS = HEADER_SIZE[1] + HOURLIST_SIZE[1]
|
||||||
|
DAYLIST_HEIGHT = 1 - HEADER_SIZE[1] - HOURLIST_SIZE[1]
|
||||||
|
DAYLIST_SIZE = (1, DAYLIST_HEIGHT)
|
||||||
|
HOURS_COUNT = 6
|
||||||
|
DAYROW_MIN_FORMAT = 40 / 384
|
||||||
|
DAYROW_MAX_FORMAT = 60 / 384
|
||||||
|
PANEL_LINE_THICKNESS = line_thickness
|
||||||
|
|
||||||
|
|
||||||
|
class DayFocusListPanel (PanelDesign):
|
||||||
|
"""Shows Day-View for today and a short Day-List for
|
||||||
|
the upcoming days."""
|
||||||
|
|
||||||
|
def __init__(self, size):
|
||||||
|
super(DayFocusListPanel, self).__init__(size)
|
||||||
|
self.hours_count = HOURS_COUNT
|
||||||
|
self.__init_modules__()
|
||||||
|
|
||||||
|
def __abs_co__(self, coordinates):
|
||||||
|
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||||
|
|
||||||
|
def __init_modules__(self):
|
||||||
|
self.__init_header__()
|
||||||
|
self.__init_hourlist__()
|
||||||
|
self.__init_daylist__()
|
||||||
|
|
||||||
|
def __init_header__(self):
|
||||||
|
self.__header__ = DayHeaderDesign(
|
||||||
|
self.__abs_co__(HEADER_SIZE), date.today())
|
||||||
|
self.__header__.pos = (0, 0)
|
||||||
|
|
||||||
|
def __init_hourlist__(self):
|
||||||
|
start, end = self.__get_current_hour_range__()
|
||||||
|
size = self.__abs_co__(HOURLIST_SIZE)
|
||||||
|
|
||||||
|
self.__hourlist__ = HourListDesign(size, start, end)
|
||||||
|
self.__hourlist__.pos = (0, self.__header__.size[1])
|
||||||
|
|
||||||
|
def __init_daylist__(self):
|
||||||
|
self.__daylist_rows__ = []
|
||||||
|
self.__calc_dayrow_size__()
|
||||||
|
self.__create_day_rows__()
|
||||||
|
|
||||||
|
def __calc_dayrow_size__(self):
|
||||||
|
max_area_height = DAYLIST_HEIGHT * self.size[1]
|
||||||
|
max_row_number = max_area_height / (DAYROW_MIN_FORMAT * self.size[0])
|
||||||
|
min_row_number = max_area_height / (DAYROW_MAX_FORMAT * self.size[0])
|
||||||
|
average_row_number = (max_row_number + min_row_number) / 2
|
||||||
|
self.dayrow_count = round(average_row_number)
|
||||||
|
row_height = max_area_height / self.dayrow_count
|
||||||
|
self.dayrow_size = (1, row_height / self.size[1])
|
||||||
|
|
||||||
|
def __create_day_rows__(self):
|
||||||
|
following_days = self.__get_following_days__()
|
||||||
|
for i, date in enumerate(following_days):
|
||||||
|
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
|
||||||
|
row.pos = self.__get_day_row_pos__(i)
|
||||||
|
self.__daylist_rows__.append(row)
|
||||||
|
|
||||||
|
def __get_following_days__(self):
|
||||||
|
following_days = []
|
||||||
|
for i in range(self.dayrow_count):
|
||||||
|
following_days.append(date.today() + timedelta(days=i + 1))
|
||||||
|
return following_days
|
||||||
|
|
||||||
|
def __get_day_row_pos__(self, i):
|
||||||
|
ypos = self.size[1] * DAYLIST_YPOS
|
||||||
|
down_shift = i * self.dayrow_size[1] * self.size[1]
|
||||||
|
return (0, int(ypos + down_shift))
|
||||||
|
|
||||||
|
def __finish_panel__(self):
|
||||||
|
self.draw_design(self.__header__)
|
||||||
|
self.draw_design(self.__hourlist__)
|
||||||
|
|
||||||
|
for row in self.__daylist_rows__:
|
||||||
|
self.draw_design(row)
|
||||||
|
self.__draw_daylist_lines__()
|
||||||
|
|
||||||
|
def __draw_daylist_lines__(self):
|
||||||
|
positions = []
|
||||||
|
for i in range(len(self.__daylist_rows__)):
|
||||||
|
positions.append(self.__get_day_row_pos__(i)[1])
|
||||||
|
for ypos in positions:
|
||||||
|
line_start = (0, ypos)
|
||||||
|
line_end = (self.size[0], ypos)
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["fg"], width=PANEL_LINE_THICKNESS)
|
||||||
|
|
||||||
|
def __get_current_hour_range__(self):
|
||||||
|
start_hour = datetime.now().hour
|
||||||
|
additional_hours = self.hours_count - 1
|
||||||
|
|
||||||
|
if start_hour + additional_hours > 23:
|
||||||
|
start_hour = 23 - additional_hours
|
||||||
|
|
||||||
|
return start_hour, start_hour + additional_hours
|
||||||
|
|
||||||
|
def add_weather(self, weather):
|
||||||
|
self.__header__.add_weather(weather)
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
|
allday_ev, timed_ev = self.__split_events__(
|
||||||
|
calendar.get_today_events())
|
||||||
|
self.__header__.add_events(allday_ev)
|
||||||
|
self.__hourlist__.add_events(timed_ev)
|
||||||
|
|
||||||
|
self.__add_calendar_daylist__(calendar)
|
||||||
|
|
||||||
|
def __split_events__(self, events):
|
||||||
|
allday_ev = []
|
||||||
|
timed_ev = []
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if event.allday:
|
||||||
|
allday_ev.append(event)
|
||||||
|
elif event.multiday:
|
||||||
|
if self.__is_today__(event.begin_datetime):
|
||||||
|
timed_ev.append(event)
|
||||||
|
elif self.__is_today__(event.end_datetime):
|
||||||
|
timed_ev.append(event)
|
||||||
|
else:
|
||||||
|
allday_ev.append(event)
|
||||||
|
else:
|
||||||
|
timed_ev.append(event)
|
||||||
|
return allday_ev, timed_ev
|
||||||
|
|
||||||
|
def __is_today__(self, dt):
|
||||||
|
today = date.today()
|
||||||
|
return dt.day == today.day and \
|
||||||
|
dt.month == today.month and \
|
||||||
|
dt.year == today.year
|
||||||
|
|
||||||
|
def __add_calendar_daylist__(self, calendar):
|
||||||
|
calendar.exclude_calendars(general_settings["extra-excluded-urls"])
|
||||||
|
|
||||||
|
for row in self.__daylist_rows__:
|
||||||
|
row.add_calendar(calendar)
|
||||||
|
|
||||||
|
calendar.exclude_calendars()
|
|
@ -2,116 +2,153 @@ from DesignEntity import DesignEntity
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from WeatherColumnDesign import WeatherColumnDesign
|
from WeatherColumnDesign import WeatherColumnDesign
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta, datetime, timezone
|
||||||
from SingelDayEventListDesign import SingelDayEventListDesign
|
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||||
from Assets import fonts
|
from Assets import fonts, colors, defaultfontsize
|
||||||
|
from settings import general_settings
|
||||||
|
from BoxDesign import BoxDesign
|
||||||
|
|
||||||
numberbox_ypos = 0.15
|
numberbox_ypos = 0.15
|
||||||
numberbox_height = 1 - 2 * numberbox_ypos
|
numberbox_height = 1 - 2 * numberbox_ypos
|
||||||
number_height = numberbox_height * 0.83
|
number_height = numberbox_height * 0.83
|
||||||
|
number_boxypos = 0.17
|
||||||
month_height = numberbox_height / 4
|
month_height = numberbox_height / 4
|
||||||
monthbox_xpadding = 0.013
|
monthbox_xpadding = 0.013
|
||||||
|
monthbox_ypadding = -0.05
|
||||||
monthbox_width = 1 - numberbox_ypos - monthbox_xpadding
|
monthbox_width = 1 - numberbox_ypos - monthbox_xpadding
|
||||||
weekday_height = numberbox_height * 0.19
|
|
||||||
weekday_ypadding = 0.02
|
|
||||||
weathercolumn_y_size = (0.4, 1)
|
weathercolumn_y_size = (0.4, 1)
|
||||||
eventlist_y_fontsize = 0.093
|
weekday_height = numberbox_height * 0.19
|
||||||
eventlist_padding = monthbox_xpadding
|
weekdaybox_height = (weekday_height / numberbox_height) * 1.5
|
||||||
|
eventlist_static_fontsize = defaultfontsize
|
||||||
|
eventlist_xpadding = monthbox_xpadding
|
||||||
|
eventlist_ypadding = 0.01
|
||||||
|
|
||||||
numberbox_font_color = "white"
|
numberbox_font_color = colors["bg"]
|
||||||
numberbox_background_color = "red"
|
numberbox_background_color = colors["hl"]
|
||||||
general_text_color = "black"
|
|
||||||
background_color = "white"
|
|
||||||
highlight_color = "red"
|
|
||||||
weekday_font = fonts["bold"]
|
weekday_font = fonts["bold"]
|
||||||
|
|
||||||
|
|
||||||
class DayHeaderDesign (DesignEntity):
|
class DayHeaderDesign (DesignEntity):
|
||||||
"""Detailed and big view of a given date."""
|
"""Detailed and big view of a given date."""
|
||||||
def __init__ (self, size, date):
|
|
||||||
|
def __init__(self, size, date):
|
||||||
super(DayHeaderDesign, self).__init__(size)
|
super(DayHeaderDesign, self).__init__(size)
|
||||||
self.__init_image__(color=background_color)
|
self.weather_column_width = 0
|
||||||
self.date = date
|
self.date = date
|
||||||
|
|
||||||
def add_weather (self, weather):
|
def add_weather(self, weather):
|
||||||
forecast = weather.get_forecast_in_days(self.date.day - date.today().day)
|
if general_settings["weather-info"] == False:
|
||||||
size = (weathercolumn_y_size[0] * self.size[1], weathercolumn_y_size[1] * self.size[1])
|
return
|
||||||
|
|
||||||
|
forecast = weather.get_forecast_in_days(
|
||||||
|
self.date.day - date.today().day)
|
||||||
|
self.weather_column_width = weathercolumn_y_size[0] * self.size[1]
|
||||||
|
size = (self.weather_column_width,
|
||||||
|
weathercolumn_y_size[1] * self.size[1])
|
||||||
pos = (self.size[0] - size[0], 0)
|
pos = (self.size[0] - size[0], 0)
|
||||||
|
|
||||||
design = WeatherColumnDesign(size, forecast)
|
design = WeatherColumnDesign(size, forecast)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def add_calendar (self, calendar):
|
def add_calendar(self, calendar):
|
||||||
self.__draw_event_list__(calendar)
|
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||||
|
now = datetime.now(local_tzinfo)
|
||||||
|
time_until_tomorrow = (datetime(
|
||||||
|
now.year, now.month, now.day, 0, 0, 0, 0, local_tzinfo) + timedelta(1)) - now
|
||||||
|
self.__draw_event_list__(
|
||||||
|
calendar.get_upcoming_events(time_until_tomorrow, now))
|
||||||
|
|
||||||
def add_rssfeed (self, rss):
|
def add_events(self, events):
|
||||||
|
self.__draw_event_list__(events)
|
||||||
|
|
||||||
|
def add_rssfeed(self, rss):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def add_crypto(self, crypto):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
self.__draw_number_square__()
|
self.__draw_number_square__()
|
||||||
self.__draw_month__()
|
self.__draw_month__()
|
||||||
self.__draw_weekday__()
|
|
||||||
|
|
||||||
def __draw_event_list__ (self, calendar):
|
def __draw_event_list__(self, events):
|
||||||
box_ypos = numberbox_ypos * self.size[1]
|
box_ypos = numberbox_ypos * self.size[1]
|
||||||
box_xpos = numberbox_ypos * self.size[1]
|
box_xpos = numberbox_ypos * self.size[1]
|
||||||
box_height = numberbox_height * self.size[1]
|
box_height = numberbox_height * self.size[1]
|
||||||
padding = eventlist_padding * self.size[0]
|
xpadding = eventlist_xpadding * self.size[0]
|
||||||
month_height = weekday_height * self.size[1]
|
ypadding = eventlist_ypadding * self.size[1]
|
||||||
weather_width = weathercolumn_y_size[0] * self.size[1]
|
monthbox_height = (monthbox_ypadding + month_height) * self.size[1]
|
||||||
pos = (box_xpos + box_height + padding, box_ypos + month_height + padding)
|
pos = (box_xpos + box_height + xpadding,
|
||||||
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1] - box_ypos)
|
box_ypos + monthbox_height + ypadding)
|
||||||
fontsize = eventlist_y_fontsize * self.size[1]
|
size = (self.size[0] - pos[0] - self.weather_column_width,
|
||||||
|
self.size[1] - pos[1] - box_ypos)
|
||||||
|
fontsize = eventlist_static_fontsize
|
||||||
|
|
||||||
event_list = SingelDayEventListDesign(size, calendar, self.date, fontsize, general_color=general_text_color, background_color=background_color, highlight_color=highlight_color)
|
rel_dates = [self.date for _ in range(len(events))]
|
||||||
|
event_list = SingelDayEventListDesign(
|
||||||
|
size, events, fontsize, event_prefix_rel_dates=rel_dates)
|
||||||
event_list.pos = pos
|
event_list.pos = pos
|
||||||
self.draw_design(event_list)
|
self.draw_design(event_list)
|
||||||
|
|
||||||
def __draw_weekday__ (self):
|
def __draw_month__(self):
|
||||||
font_size = int(weekday_height * self.size[1])
|
|
||||||
padding = int(weekday_ypadding * self.size[1])
|
|
||||||
box_ypos = int((numberbox_ypos) * self.size[1]) + padding
|
|
||||||
box_xpos = int(numberbox_ypos * self.size[1])
|
|
||||||
box_height = numberbox_height * self.size[1]
|
|
||||||
box_pos = (box_xpos, box_ypos)
|
|
||||||
box_size = (int(numberbox_height * self.size[1]), box_height)
|
|
||||||
|
|
||||||
week_day_name = self.date.strftime("%A")
|
|
||||||
week_day = TextDesign(box_size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", font=weekday_font)
|
|
||||||
week_day.pos = box_pos
|
|
||||||
self.draw_design(week_day)
|
|
||||||
|
|
||||||
def __draw_month__ (self):
|
|
||||||
font_size = int(month_height * self.size[1])
|
font_size = int(month_height * self.size[1])
|
||||||
padding = int(monthbox_xpadding * self.size[0])
|
xpadding = int(monthbox_xpadding * self.size[0])
|
||||||
|
ypadding = int(monthbox_ypadding * self.size[1])
|
||||||
box_ypos = int(numberbox_ypos * self.size[1])
|
box_ypos = int(numberbox_ypos * self.size[1])
|
||||||
box_height = int(numberbox_height * self.size[1])
|
box_height = int(numberbox_height * self.size[1])
|
||||||
box_pos = (box_ypos + box_height + padding, box_ypos)
|
box_pos = (box_ypos + box_height + xpadding, box_ypos + ypadding)
|
||||||
box_size = (int(monthbox_width * self.size[0]), box_height)
|
box_size = (int(monthbox_width * self.size[0]), box_height)
|
||||||
|
|
||||||
month_name = self.date.strftime("%B")
|
month_name = self.date.strftime("%B")
|
||||||
month = TextDesign(box_size, text=month_name, fontsize=font_size, color=general_text_color, background_color=background_color)
|
month = TextDesign(box_size, text=month_name, fontsize=font_size)
|
||||||
month.pos = box_pos
|
month.pos = box_pos
|
||||||
self.draw_design(month)
|
self.draw_design(month)
|
||||||
|
|
||||||
def __draw_number_square__ (self):
|
def __draw_number_square__(self):
|
||||||
box_ypos = int(numberbox_ypos * self.size[1])
|
box_height = numberbox_height * self.size[1]
|
||||||
box_height = int(numberbox_height * self.size[1])
|
box_ypos = numberbox_ypos * self.size[1]
|
||||||
box_topleft = (box_ypos,box_ypos)
|
box_pos = (box_ypos, box_ypos)
|
||||||
box_bottomright = (box_topleft[0] + box_height, box_topleft[1] + box_height)
|
|
||||||
ImageDraw.Draw(self.__image__).rectangle([ box_topleft, box_bottomright ], fill=numberbox_background_color)
|
|
||||||
|
|
||||||
font_size = int(number_height * self.size[1])
|
|
||||||
box_size = (box_height, box_height)
|
box_size = (box_height, box_height)
|
||||||
|
|
||||||
|
box = BoxDesign(box_size, fill=numberbox_background_color)
|
||||||
|
box.pos = box_pos
|
||||||
|
self.draw_design(box)
|
||||||
|
|
||||||
|
self.__draw_today_number__()
|
||||||
|
self.__draw_weekday__()
|
||||||
|
|
||||||
|
def __draw_today_number__(self):
|
||||||
|
font_size = number_height * self.size[1]
|
||||||
|
box_height = numberbox_height * self.size[1]
|
||||||
|
box_ypos = numberbox_ypos * self.size[1]
|
||||||
|
ypadding = number_boxypos * box_height
|
||||||
|
size = (box_height, box_height - ypadding)
|
||||||
|
pos = (box_ypos, box_ypos + ypadding)
|
||||||
|
|
||||||
day_text = self.__get_day_text__()
|
day_text = self.__get_day_text__()
|
||||||
number = TextDesign(box_size, text=day_text, background_color=numberbox_background_color, color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
|
number = TextDesign(size, text=day_text, background_color=numberbox_background_color,
|
||||||
number.pos = box_topleft
|
color=numberbox_font_color, fontsize=font_size, horizontalalignment="center", verticalalignment="center")
|
||||||
|
number.pos = pos
|
||||||
|
number.mask = False
|
||||||
self.draw_design(number)
|
self.draw_design(number)
|
||||||
|
|
||||||
def __abs_co__ (self, coordinates):
|
def __draw_weekday__(self):
|
||||||
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1]))
|
font_size = weekday_height * self.size[1]
|
||||||
|
box_height = numberbox_height * self.size[1]
|
||||||
|
size = (box_height, weekdaybox_height * box_height)
|
||||||
|
box_ypos = numberbox_ypos * self.size[1]
|
||||||
|
pos = (box_ypos, box_ypos)
|
||||||
|
|
||||||
def __get_day_text__ (self):
|
week_day_name = self.date.strftime("%A")
|
||||||
if self.date.day is 14 and self.date.month is 3: #PI-Day
|
week_day = TextDesign(size, text=week_day_name, background_color=numberbox_background_color, color=numberbox_font_color,
|
||||||
return "π"
|
fontsize=font_size, horizontalalignment="center", verticalalignment="center", font=weekday_font)
|
||||||
return str(self.date.day)
|
week_day.pos = pos
|
||||||
|
week_day.mask = False
|
||||||
|
self.draw_design(week_day)
|
||||||
|
|
||||||
|
def __abs_co__(self, coordinates):
|
||||||
|
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||||
|
|
||||||
|
def __get_day_text__(self):
|
||||||
|
return str(self.date.day)
|
||||||
|
|
|
@ -1,78 +1,106 @@
|
||||||
from PanelDesign import PanelDesign
|
from PanelDesign import PanelDesign
|
||||||
from Assets import *
|
from Assets import colors
|
||||||
from settings import *
|
from settings import general_settings
|
||||||
import calendar as callib
|
import calendar as callib
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
from PIL import ImageDraw
|
from PIL import ImageDraw
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from DayHeaderDesign import DayHeaderDesign
|
from DayHeaderDesign import DayHeaderDesign
|
||||||
from DayRowDesign import DayRowDesign
|
from DayRowDesign import DayRowDesign
|
||||||
from RssPostListDesign import RssPostListDesign
|
from RssPostListDesign import RssPostListDesign
|
||||||
|
from CryptoListDesign import CryptoListDesign
|
||||||
|
from settings import line_thickness
|
||||||
|
from math import ceil
|
||||||
|
|
||||||
todayheader_pos = (0,0)
|
todayheader_pos = (0, 0)
|
||||||
todayheader_size = (1,0.25)
|
todayheader_size = (1, 0.25)
|
||||||
line_color = "black"
|
lines_thickness = line_thickness
|
||||||
lines_thickness = 1
|
|
||||||
infoarea_replacedrowscount = 3
|
infoarea_replacedrowscount = 3
|
||||||
|
|
||||||
dayrowsarea_ypos = todayheader_size[1]
|
dayrowsarea_ypos = todayheader_size[1]
|
||||||
dayrowsarea_height = 1 - todayheader_size[1]
|
dayrowsarea_height = 1 - todayheader_size[1]
|
||||||
dayrow_min_format = 50 / 384
|
dayrow_min_format = 50 / 384
|
||||||
dayrow_max_format = 70 / 384
|
dayrow_max_format = 70 / 384
|
||||||
rss_x_fontsize = 14 / 384
|
|
||||||
rss_y_padding = 5
|
rss_y_padding = 5
|
||||||
|
crypto_y_padding = 5
|
||||||
|
|
||||||
|
|
||||||
class DayListPanel (PanelDesign):
|
class DayListPanel (PanelDesign):
|
||||||
"""Overview that focuses on the current day and
|
"""Overview that focuses on the current day and
|
||||||
lists following days in a list below."""
|
lists following days in a list below."""
|
||||||
def __init__ (self, size):
|
|
||||||
|
def __init__(self, size):
|
||||||
super(DayListPanel, self).__init__(size)
|
super(DayListPanel, self).__init__(size)
|
||||||
self.__day_rows__ = []
|
self.__day_rows__ = []
|
||||||
self.__calc_dayrow_size__()
|
self.__calc_dayrow_size__()
|
||||||
self.__first_render__()
|
self.__first_render__()
|
||||||
|
|
||||||
def __first_render__ (self):
|
def __first_render__(self):
|
||||||
self.__draw_today_header__()
|
self.__draw_today_header__()
|
||||||
self.__draw_day_rows__()
|
self.__draw_day_rows__()
|
||||||
|
|
||||||
def add_weather (self, weather):
|
def add_weather(self, weather):
|
||||||
for row in self.__day_rows__:
|
for row in self.__day_rows__:
|
||||||
row.add_weather(weather)
|
row.add_weather(weather)
|
||||||
|
|
||||||
def add_calendar (self, calendar):
|
def add_calendar(self, calendar):
|
||||||
for row in self.__day_rows__:
|
for row in self.__day_rows__:
|
||||||
row.add_calendar(calendar)
|
row.add_calendar(calendar)
|
||||||
|
|
||||||
def add_rssfeed (self, rss):
|
def add_rssfeed(self, rss):
|
||||||
for row in self.__day_rows__:
|
for row in self.__day_rows__:
|
||||||
row.add_rssfeed(rss)
|
row.add_rssfeed(rss)
|
||||||
if general_settings["info-area"] is "rss":
|
if general_settings["info-area"] is "rss":
|
||||||
|
self.__day_rows__ = self.__day_rows__[:-infoarea_replacedrowscount]
|
||||||
self.__draw_rss_infoarea__(rss)
|
self.__draw_rss_infoarea__(rss)
|
||||||
|
|
||||||
def __draw_rss_infoarea__ (self, rss):
|
def add_crypto(self, crypto):
|
||||||
height = infoarea_replacedrowscount * self.dayrow_size[1] * self.size[1] - rss_y_padding
|
if general_settings["info-area"] is "crypto":
|
||||||
|
self.__draw_crypto_infoarea__(crypto)
|
||||||
|
|
||||||
|
def add_tasks(self, tasks):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __draw_rss_infoarea__(self, rss):
|
||||||
|
height = infoarea_replacedrowscount * \
|
||||||
|
self.dayrow_size[1] * self.size[1] - rss_y_padding
|
||||||
ypos = self.size[1] - height
|
ypos = self.size[1] - height
|
||||||
size = (self.size[0], height)
|
size = (self.size[0], height)
|
||||||
fontsize = rss_x_fontsize * size[0]
|
|
||||||
pos = (0, ypos)
|
pos = (0, ypos)
|
||||||
|
|
||||||
design = RssPostListDesign(size, rss, fontsize)
|
design = RssPostListDesign(size, rss)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def __draw_day_rows__ (self):
|
def __draw_crypto_infoarea__(self, crypto):
|
||||||
|
height = infoarea_replacedrowscount * \
|
||||||
|
self.dayrow_size[1] * self.size[1] - crypto_y_padding
|
||||||
|
ypos = self.size[1] - height
|
||||||
|
size = (self.size[0], height)
|
||||||
|
pos = (0, ypos)
|
||||||
|
|
||||||
|
design = CryptoListDesign(size, crypto)
|
||||||
|
acutal_height = design.get_estimated_height()
|
||||||
|
design.pos = (pos[0], pos[1] + (height - acutal_height))
|
||||||
|
self.draw_design(design)
|
||||||
|
|
||||||
|
replaced_rows = ceil(
|
||||||
|
acutal_height / (self.dayrow_size[1] * self.size[1]))
|
||||||
|
self.__day_rows__ = self.__day_rows__[:-replaced_rows]
|
||||||
|
|
||||||
|
def __draw_day_rows__(self):
|
||||||
following_days = self.__get_following_days__()
|
following_days = self.__get_following_days__()
|
||||||
for i, date in enumerate(following_days):
|
for i, date in enumerate(following_days):
|
||||||
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
|
row = DayRowDesign(self.__abs_co__(self.dayrow_size), date)
|
||||||
row.pos = self.__get_day_row_pos__(i)
|
row.pos = self.__get_day_row_pos__(i)
|
||||||
self.__day_rows__.append(row)
|
self.__day_rows__.append(row)
|
||||||
|
|
||||||
def __get_day_row_pos__ (self, i):
|
def __get_day_row_pos__(self, i):
|
||||||
ypos = self.size[1] * dayrowsarea_ypos
|
ypos = self.size[1] * dayrowsarea_ypos
|
||||||
down_shift = i * self.dayrow_size[1] * self.size[1]
|
down_shift = i * self.dayrow_size[1] * self.size[1]
|
||||||
return (0, int(ypos + down_shift))
|
return (0, int(ypos + down_shift))
|
||||||
|
|
||||||
def __calc_dayrow_size__ (self):
|
def __calc_dayrow_size__(self):
|
||||||
max_area_height = dayrowsarea_height * self.size[1]
|
max_area_height = dayrowsarea_height * self.size[1]
|
||||||
max_row_number = max_area_height / (dayrow_min_format * self.size[0])
|
max_row_number = max_area_height / (dayrow_min_format * self.size[0])
|
||||||
min_row_number = max_area_height / (dayrow_max_format * self.size[0])
|
min_row_number = max_area_height / (dayrow_max_format * self.size[0])
|
||||||
|
@ -81,33 +109,32 @@ class DayListPanel (PanelDesign):
|
||||||
row_height = max_area_height / self.dayrow_count
|
row_height = max_area_height / self.dayrow_count
|
||||||
self.dayrow_size = (1, row_height / self.size[1])
|
self.dayrow_size = (1, row_height / self.size[1])
|
||||||
|
|
||||||
if general_settings["info-area"] in ["rss"]:
|
|
||||||
self.dayrow_count -= infoarea_replacedrowscount
|
|
||||||
|
|
||||||
def __get_following_days__(self):
|
def __get_following_days__(self):
|
||||||
following_days = []
|
following_days = []
|
||||||
for i in range(self.dayrow_count):
|
for i in range(self.dayrow_count):
|
||||||
following_days.append(date.today() + timedelta(days=i + 1))
|
following_days.append(date.today() + timedelta(days=i + 1))
|
||||||
return following_days
|
return following_days
|
||||||
|
|
||||||
def __draw_today_header__ (self):
|
def __draw_today_header__(self):
|
||||||
header = DayHeaderDesign(self.__abs_co__(todayheader_size), date.today())
|
header = DayHeaderDesign(self.__abs_co__(
|
||||||
|
todayheader_size), date.today())
|
||||||
header.pos = self.__abs_co__(todayheader_pos)
|
header.pos = self.__abs_co__(todayheader_pos)
|
||||||
self.__day_rows__.append(header)
|
self.__day_rows__.append(header)
|
||||||
|
|
||||||
def __draw_lines__(self):
|
def __draw_lines__(self):
|
||||||
positions = []
|
positions = []
|
||||||
for i in range(self.dayrow_count + 1):
|
for i in range(len(self.__day_rows__)):
|
||||||
positions.append(self.__get_day_row_pos__(i)[1])
|
positions.append(self.__get_day_row_pos__(i)[1])
|
||||||
for ypos in positions:
|
for ypos in positions:
|
||||||
line_start = (0, ypos)
|
line_start = (0, ypos)
|
||||||
line_end = (self.size[0], ypos)
|
line_end = (self.size[0], ypos)
|
||||||
ImageDraw.Draw(self.__image__).line([line_start, line_end], fill=line_color, width=lines_thickness)
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["fg"], width=lines_thickness)
|
||||||
|
|
||||||
def __finish_image__(self):
|
def __finish_panel__(self):
|
||||||
for design in self.__day_rows__:
|
for design in self.__day_rows__:
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
self.__draw_lines__()
|
self.__draw_lines__()
|
||||||
|
|
||||||
def __abs_co__(self, coordinates):
|
def __abs_co__(self, coordinates):
|
||||||
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1]))
|
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||||
|
|
|
@ -1,62 +1,66 @@
|
||||||
from PIL import ImageDraw, Image
|
from PIL import ImageDraw, Image
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from settings import week_starts_on, owm_paid_subscription
|
from settings import week_starts_on, owm_paid_subscription, general_settings
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from Assets import weathericons, wpath, fonts
|
from Assets import weathericons, wpath, fonts, colors, defaultfontsize
|
||||||
from SingelDayEventListDesign import SingelDayEventListDesign
|
from SingelDayEventListDesign import SingelDayEventListDesign
|
||||||
|
|
||||||
daynumber_y_size = (1, 0.65)
|
daynumber_y_size = (1, 0.60)
|
||||||
weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1])
|
weekday_y_size = (daynumber_y_size[0], 1 - daynumber_y_size[1])
|
||||||
weekday_ypos = daynumber_y_size[1]
|
weekday_ypos = daynumber_y_size[1]
|
||||||
daynumber_fontsize = daynumber_y_size[1] * 0.8
|
daynumber_fontsize = daynumber_y_size[1] * 0.85
|
||||||
weekday_fontsize = weekday_y_size[1] * 0.75
|
daynumber_ypadding = 0.1
|
||||||
|
weekday_fontsize = weekday_y_size[1] * 0.65
|
||||||
weathericon_ypos = 0.1
|
weathericon_ypos = 0.1
|
||||||
weathericon_height = 1 - 2 * weathericon_ypos
|
weathericon_height = 1 - 2 * weathericon_ypos
|
||||||
eventlist_xpadding = 5
|
eventlist_xpadding = 5
|
||||||
eventlist_ypos = 0.1
|
eventlist_ypos = 0.02
|
||||||
eventlist_y_fontsize = 0.2
|
eventlist_y_fontsize = 0.2
|
||||||
|
|
||||||
general_text_color = "black"
|
font = fonts["light"]
|
||||||
highlight_text_color = "red"
|
|
||||||
background_color = "white"
|
|
||||||
font = fonts["regular"]
|
|
||||||
|
|
||||||
class DayRowDesign (DesignEntity):
|
class DayRowDesign (DesignEntity):
|
||||||
"""Detailed view of a given date."""
|
"""Detailed view of a given date."""
|
||||||
def __init__ (self, size, date):
|
|
||||||
|
def __init__(self, size, date):
|
||||||
super(DayRowDesign, self).__init__(size)
|
super(DayRowDesign, self).__init__(size)
|
||||||
self.__init_image__(color=background_color)
|
self.__init_image__()
|
||||||
self.date = date
|
self.date = date
|
||||||
|
|
||||||
def add_weather (self, weather):
|
def add_weather(self, weather):
|
||||||
if weather.is_available is False:
|
if weather.is_available is False:
|
||||||
return
|
return
|
||||||
self.__draw_forecast__(weather)
|
self.__draw_forecast__(weather)
|
||||||
|
|
||||||
def add_calendar (self, calendar):
|
def add_calendar(self, calendar):
|
||||||
self.__draw_event_list__(calendar)
|
self.__draw_event_list__(calendar)
|
||||||
|
|
||||||
def add_rssfeed (self, rss):
|
def add_rssfeed(self, rss):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __draw_event_list__ (self, calendar):
|
def __draw_event_list__(self, calendar):
|
||||||
number_width = daynumber_y_size[0] * self.size[1]
|
number_width = daynumber_y_size[0] * self.size[1]
|
||||||
ypos = eventlist_ypos * self.size[1]
|
ypos = eventlist_ypos * self.size[1]
|
||||||
weather_width = 0
|
weather_width = 0
|
||||||
if owm_paid_subscription:
|
if owm_paid_subscription and general_settings["weather-info"]:
|
||||||
weather_width = weathericon_height * self.size[1]
|
weather_width = weathericon_height * self.size[1]
|
||||||
pos = (number_width + eventlist_xpadding, ypos)
|
pos = (number_width + eventlist_xpadding, ypos)
|
||||||
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1])
|
size = (self.size[0] - pos[0] - weather_width, self.size[1] - pos[1])
|
||||||
fontsize = eventlist_y_fontsize * self.size[1]
|
fontsize = eventlist_y_fontsize * self.size[1]
|
||||||
|
|
||||||
event_list = SingelDayEventListDesign(size, calendar, self.date, fontsize, line_spacing=0, general_color=general_text_color, background_color=background_color, highlight_color=highlight_text_color)
|
events = calendar.get_day_events(self.date)
|
||||||
|
rel_dates = [self.date for _ in range(len(events))]
|
||||||
|
event_list = SingelDayEventListDesign(
|
||||||
|
size, events, fontsize, event_prefix_rel_dates=rel_dates)
|
||||||
event_list.pos = pos
|
event_list.pos = pos
|
||||||
self.draw_design(event_list)
|
self.draw_design(event_list)
|
||||||
|
|
||||||
def __draw_forecast__ (self, weather):
|
def __draw_forecast__(self, weather):
|
||||||
forecast = weather.get_forecast_in_days(self.date.day - datetime.today().day)
|
forecast = weather.get_forecast_in_days(
|
||||||
|
self.date.day - datetime.today().day)
|
||||||
|
|
||||||
if forecast is None:
|
if forecast is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -68,46 +72,52 @@ class DayRowDesign (DesignEntity):
|
||||||
resized_icon = icon.resize(size, resample=Image.LANCZOS)
|
resized_icon = icon.resize(size, resample=Image.LANCZOS)
|
||||||
self.draw(resized_icon, pos)
|
self.draw(resized_icon, pos)
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
self.__draw_day_number__()
|
|
||||||
self.__draw_weekday__()
|
self.__draw_weekday__()
|
||||||
|
self.__draw_day_number__()
|
||||||
|
|
||||||
def __draw_weekday__ (self):
|
def __draw_weekday__(self):
|
||||||
font_size = int(weekday_fontsize * self.size[1])
|
font_size = int(weekday_fontsize * self.size[1])
|
||||||
size = (weekday_y_size[0] * self.size[1], weekday_y_size[1] * self.size[1])
|
size = (weekday_y_size[0] * self.size[1],
|
||||||
|
weekday_y_size[1] * self.size[1])
|
||||||
ypos = weekday_ypos * self.size[1]
|
ypos = weekday_ypos * self.size[1]
|
||||||
pos = (0, ypos)
|
pos = (0, ypos)
|
||||||
|
|
||||||
color = self.__get_day_color__()
|
color = self.__get_day_color__()
|
||||||
week_day_name = self.date.strftime("%a")
|
week_day_name = self.date.strftime("%a")
|
||||||
|
|
||||||
week_day = TextDesign(size, text=week_day_name, font=font, background_color=background_color, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="top")
|
week_day = TextDesign(size, text=week_day_name, font=font, color=color,
|
||||||
|
fontsize=font_size, horizontalalignment="center", verticalalignment="top")
|
||||||
week_day.pos = pos
|
week_day.pos = pos
|
||||||
|
week_day.mask = False
|
||||||
self.draw_design(week_day)
|
self.draw_design(week_day)
|
||||||
|
|
||||||
def __draw_day_number__ (self):
|
def __draw_day_number__(self):
|
||||||
font_size = int(daynumber_fontsize * self.size[1])
|
font_size = int(daynumber_fontsize * self.size[1])
|
||||||
size = (daynumber_y_size[0] * self.size[1], daynumber_y_size[1] * self.size[1])
|
ypadding = daynumber_ypadding * self.size[1]
|
||||||
pos = (0, 0)
|
size = (daynumber_y_size[0] * self.size[1],
|
||||||
|
daynumber_y_size[1] * self.size[1])
|
||||||
|
pos = (0, ypadding)
|
||||||
|
|
||||||
day_text = self.__get_day_text__()
|
day_text = self.__get_day_text__()
|
||||||
color = self.__get_day_color__()
|
color = self.__get_day_color__()
|
||||||
|
|
||||||
number = TextDesign(size, text=day_text, font=font, background_color=background_color, color=color, fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
|
number = TextDesign(size, text=day_text, font=font, color=color,
|
||||||
|
fontsize=font_size, horizontalalignment="center", verticalalignment="bottom")
|
||||||
number.pos = pos
|
number.pos = pos
|
||||||
self.draw_design(number)
|
self.draw_design(number)
|
||||||
|
|
||||||
def __abs_co__ (self, coordinates):
|
def __abs_co__(self, coordinates):
|
||||||
return (int(coordinates[0] * self.size[0]),int(coordinates[1] * self.size[1]))
|
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||||
|
|
||||||
def __get_day_text__ (self):
|
def __get_day_text__(self):
|
||||||
return str(self.date.day)
|
return str(self.date.day)
|
||||||
|
|
||||||
def __get_day_color__ (self):
|
def __get_day_color__(self):
|
||||||
"""Depending on week_starts_on"""
|
"""Depending on week_starts_on"""
|
||||||
if week_starts_on == "Monday" and self.date.strftime("%w") == "0":
|
if week_starts_on == "Monday" and self.date.strftime("%w") == "0":
|
||||||
return highlight_text_color
|
return colors["hl"]
|
||||||
elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6":
|
elif week_starts_on == "Sunday" and self.date.strftime("%w") == "6":
|
||||||
return highlight_text_color
|
return colors["hl"]
|
||||||
else:
|
else:
|
||||||
return general_text_color
|
return colors["fg"]
|
||||||
|
|
155
Calendar/DayViewPanel.py
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from datetime import datetime, timedelta, date
|
||||||
|
from DayHeaderDesign import DayHeaderDesign
|
||||||
|
from HourListDesign import HourListDesign
|
||||||
|
from settings import general_settings
|
||||||
|
from RssPostListDesign import RssPostListDesign
|
||||||
|
from PIL import ImageDraw
|
||||||
|
from Assets import colors
|
||||||
|
from EventListDesign import EventListDesign
|
||||||
|
from CryptoListDesign import CryptoListDesign
|
||||||
|
|
||||||
|
|
||||||
|
header_size = (1, 0.2)
|
||||||
|
hourlist_size = (1, 1 - header_size[1])
|
||||||
|
default_shownhours_count = 12
|
||||||
|
|
||||||
|
infoarea_replaced_hours = 4
|
||||||
|
infoarea_borderline_width = 1
|
||||||
|
infoarea_padding = 5
|
||||||
|
|
||||||
|
|
||||||
|
class DayViewPanel (PanelDesign):
|
||||||
|
"""Overview that focuses on the current day and
|
||||||
|
shows a timeline split into hours."""
|
||||||
|
|
||||||
|
def __init__(self, size):
|
||||||
|
super(DayViewPanel, self).__init__(size)
|
||||||
|
self.shownhours_count = default_shownhours_count
|
||||||
|
if general_settings["info-area"] not in ["", "empty"]:
|
||||||
|
self.shownhours_count -= infoarea_replaced_hours
|
||||||
|
self.__first_render__()
|
||||||
|
|
||||||
|
def __first_render__(self):
|
||||||
|
self.__init_header__()
|
||||||
|
self.__init_hourlist__()
|
||||||
|
|
||||||
|
def add_weather(self, weather):
|
||||||
|
self.__header__.add_weather(weather)
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
|
allday_ev, timed_ev = self.__split_events__(
|
||||||
|
calendar.get_today_events())
|
||||||
|
self.__header__.add_events(allday_ev)
|
||||||
|
self.__hourlist__.add_events(timed_ev)
|
||||||
|
|
||||||
|
if general_settings["info-area"] == "events":
|
||||||
|
self.__draw_event_list__(calendar)
|
||||||
|
self.__draw_infoarea_line__()
|
||||||
|
|
||||||
|
def add_rssfeed(self, rss):
|
||||||
|
if general_settings["info-area"] == "rss":
|
||||||
|
self.__draw_rss_feed__(rss)
|
||||||
|
self.__draw_infoarea_line__()
|
||||||
|
|
||||||
|
def add_crypto(self, crypto):
|
||||||
|
if general_settings["info-area"] == "crypto":
|
||||||
|
self.__draw_crypto_feed__(crypto)
|
||||||
|
self.__draw_infoarea_line__()
|
||||||
|
|
||||||
|
def __draw_infoarea_line__(self):
|
||||||
|
height = infoarea_replaced_hours * self.__hourlist__.row_size[1]
|
||||||
|
ypos = self.size[1] - height
|
||||||
|
|
||||||
|
line_start = (0, ypos)
|
||||||
|
line_end = (self.size[0], ypos)
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["fg"], width=infoarea_borderline_width)
|
||||||
|
|
||||||
|
def __draw_rss_feed__(self, rss):
|
||||||
|
height = infoarea_replaced_hours * \
|
||||||
|
self.__hourlist__.row_size[1] - infoarea_padding
|
||||||
|
size = (self.size[0], height)
|
||||||
|
pos = (0, self.size[1] - size[1])
|
||||||
|
|
||||||
|
rss = RssPostListDesign(size, rss)
|
||||||
|
rss.pos = pos
|
||||||
|
self.draw_design(rss)
|
||||||
|
|
||||||
|
def __draw_crypto_feed__(self, crypto):
|
||||||
|
height = infoarea_replaced_hours * \
|
||||||
|
self.__hourlist__.row_size[1] - infoarea_padding
|
||||||
|
size = (self.size[0], height)
|
||||||
|
pos = (0, self.size[1] - size[1])
|
||||||
|
|
||||||
|
crypto = CryptoListDesign(size, crypto)
|
||||||
|
acutal_height = crypto.get_estimated_height()
|
||||||
|
crypto.pos = (pos[0], pos[1] + (height - acutal_height))
|
||||||
|
self.draw_design(crypto)
|
||||||
|
|
||||||
|
def __draw_event_list__(self, calendar):
|
||||||
|
height = infoarea_replaced_hours * \
|
||||||
|
self.__hourlist__.row_size[1] - infoarea_padding
|
||||||
|
size = (self.size[0], height)
|
||||||
|
pos = (0, self.size[1] - size[1])
|
||||||
|
|
||||||
|
events = EventListDesign(size, calendar.get_upcoming_events())
|
||||||
|
events.pos = pos
|
||||||
|
self.draw_design(events)
|
||||||
|
|
||||||
|
def add_tasks(self, tasks):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __finish_panel__(self):
|
||||||
|
self.draw_design(self.__header__)
|
||||||
|
self.draw_design(self.__hourlist__)
|
||||||
|
|
||||||
|
def __init_header__(self):
|
||||||
|
self.__header__ = DayHeaderDesign(
|
||||||
|
self.__abs_co__(header_size), date.today())
|
||||||
|
self.__header__.pos = (0, 0)
|
||||||
|
|
||||||
|
def __init_hourlist__(self):
|
||||||
|
start, end = self.__get_current_hour_range__()
|
||||||
|
size = self.__abs_co__(hourlist_size)
|
||||||
|
size = (size[0], size[1] * self.shownhours_count /
|
||||||
|
default_shownhours_count)
|
||||||
|
|
||||||
|
self.__hourlist__ = HourListDesign(size, start, end)
|
||||||
|
self.__hourlist__.pos = (0, self.__header__.size[1])
|
||||||
|
|
||||||
|
def __get_current_hour_range__(self):
|
||||||
|
start_hour = datetime.now().hour
|
||||||
|
additional_hours = self.shownhours_count - 1
|
||||||
|
|
||||||
|
if start_hour + additional_hours > 23:
|
||||||
|
start_hour = 23 - additional_hours
|
||||||
|
|
||||||
|
return start_hour, start_hour + additional_hours
|
||||||
|
|
||||||
|
def __abs_co__(self, coordinates):
|
||||||
|
return (int(coordinates[0] * self.size[0]), int(coordinates[1] * self.size[1]))
|
||||||
|
|
||||||
|
def __split_events__(self, events):
|
||||||
|
allday_ev = []
|
||||||
|
timed_ev = []
|
||||||
|
|
||||||
|
for event in events:
|
||||||
|
if event.allday:
|
||||||
|
allday_ev.append(event)
|
||||||
|
elif event.multiday:
|
||||||
|
if self.__is_today__(event.begin_datetime):
|
||||||
|
timed_ev.append(event)
|
||||||
|
elif self.__is_today__(event.end_datetime):
|
||||||
|
timed_ev.append(event)
|
||||||
|
else:
|
||||||
|
allday_ev.append(event)
|
||||||
|
else:
|
||||||
|
timed_ev.append(event)
|
||||||
|
return allday_ev, timed_ev
|
||||||
|
|
||||||
|
def __is_today__(self, dt):
|
||||||
|
today = date.today()
|
||||||
|
return dt.day == today.day and \
|
||||||
|
dt.month == today.month and \
|
||||||
|
dt.year == today.year
|
|
@ -1,13 +1,21 @@
|
||||||
from DebugInterface import DebugInterface
|
from DebugInterface import DebugInterface
|
||||||
from Assets import weathericons
|
from Assets import weathericons
|
||||||
|
from datetime import datetime
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
|
||||||
class DebugConsole (DebugInterface):
|
class DebugConsole (DebugInterface):
|
||||||
"""Defines concrete console export of debug objects"""
|
"""Defines concrete console export of debug objects"""
|
||||||
def print_event (self, event):
|
|
||||||
|
def print_event(self, event):
|
||||||
print("\nCalendarEvent:")
|
print("\nCalendarEvent:")
|
||||||
print("---------------------")
|
print("---------------------")
|
||||||
print('Begin datetime: ' + str(event.begin_datetime))
|
print('Begin datetime: ' + str(event.begin_datetime))
|
||||||
print('End datetime: ' + str(event.end_datetime))
|
print('End datetime: ' + str(event.end_datetime))
|
||||||
|
print('Duration: ' + str(event.duration))
|
||||||
|
print('All day: ' + str(event.allday))
|
||||||
|
print('Multi-day: ' + str(event.multiday))
|
||||||
|
print('RRULE: ' + str(event.rrule))
|
||||||
print('Title: ' + str(event.title))
|
print('Title: ' + str(event.title))
|
||||||
print('Description: ' + str(event.description))
|
print('Description: ' + str(event.description))
|
||||||
print('Attendees: ' + str(event.attendees))
|
print('Attendees: ' + str(event.attendees))
|
||||||
|
@ -16,7 +24,7 @@ class DebugConsole (DebugInterface):
|
||||||
print('Location: ' + str(event.location))
|
print('Location: ' + str(event.location))
|
||||||
print('Fetch datetime: ' + str(event.fetch_datetime))
|
print('Fetch datetime: ' + str(event.fetch_datetime))
|
||||||
|
|
||||||
def print_forecast (self, forecast):
|
def print_forecast(self, forecast):
|
||||||
print("\nWeatherForecast:")
|
print("\nWeatherForecast:")
|
||||||
print("---------------------")
|
print("---------------------")
|
||||||
print('Air temperature: ' + str(forecast.air_temperature))
|
print('Air temperature: ' + str(forecast.air_temperature))
|
||||||
|
@ -39,7 +47,19 @@ class DebugConsole (DebugInterface):
|
||||||
print('Location: ' + str(forecast.location))
|
print('Location: ' + str(forecast.location))
|
||||||
print('Fetch datetime: ' + str(forecast.fetch_datetime))
|
print('Fetch datetime: ' + str(forecast.fetch_datetime))
|
||||||
|
|
||||||
def print_line (self, content):
|
def print_line(self, content):
|
||||||
if content is None:
|
if content is None:
|
||||||
return
|
return
|
||||||
print(str(content))
|
print(str(content))
|
||||||
|
|
||||||
|
def print_err(self, exception, msg=""):
|
||||||
|
if exception is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
content = "[ERR: "
|
||||||
|
content += datetime.now().strftime("")
|
||||||
|
content += "]\n" + str(exception)
|
||||||
|
content += "\n" + str(msg) + "\n"
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
self.print_line(str(content))
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
class DebugInterface (object):
|
class DebugInterface (object):
|
||||||
"""Defines general interface for debugging operations"""
|
"""Defines general interface for debugging operations"""
|
||||||
def print_event (self, event):
|
|
||||||
|
def print_event(self, event):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def print_forecast (self, forecast):
|
def print_forecast(self, forecast):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def print_line (self, content):
|
def print_line(self, content):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
|
def print_err(self, exception, msg=""):
|
||||||
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
from PIL import Image, ImageOps, ImageDraw
|
from PIL import Image, ImageOps, ImageDraw
|
||||||
|
from Assets import colors
|
||||||
|
|
||||||
|
masking_threshold = 200
|
||||||
|
|
||||||
masking_threshold = 100
|
|
||||||
|
|
||||||
class DesignEntity (object):
|
class DesignEntity (object):
|
||||||
"""General entity that can be drawn on to a panel design or
|
"""General entity that can be drawn on to a panel design or
|
||||||
other design entities."""
|
other design entities."""
|
||||||
def __init__ (self, size, mask=False, invert_mask=False, color_key=False):
|
|
||||||
|
def __init__(self, size, mask=False, invert_mask=False, color_key=False):
|
||||||
self.size = size
|
self.size = size
|
||||||
|
# Are dimensions >= 0?
|
||||||
|
if self.size[0] < 0:
|
||||||
|
self.size = (0, self.size[1])
|
||||||
|
if self.size[1] < 0:
|
||||||
|
self.size = (self.size[0], 0)
|
||||||
|
|
||||||
self.pos = (0, 0)
|
self.pos = (0, 0)
|
||||||
self.mask = mask
|
self.mask = mask
|
||||||
self.invert_mask = invert_mask
|
self.invert_mask = invert_mask
|
||||||
|
@ -14,36 +23,39 @@ class DesignEntity (object):
|
||||||
self.__finished_image__ = False
|
self.__finished_image__ = False
|
||||||
self.color_key = color_key
|
self.color_key = color_key
|
||||||
|
|
||||||
def __init_image__ (self, color = 'white'):
|
def __init_image__(self, color=colors["bg"]):
|
||||||
rounded_size = (int(self.size[0]),int(self.size[1]))
|
rounded_size = (int(self.size[0]), int(self.size[1]))
|
||||||
self.__image__ = Image.new('RGB', rounded_size, color=color)
|
self.__image__ = Image.new('RGBA', rounded_size, color=color)
|
||||||
|
|
||||||
def get_image (self):
|
def get_image(self):
|
||||||
if self.__finished_image__ is False:
|
if self.__finished_image__ is False:
|
||||||
self.__finish_image__()
|
self.__finish_image__()
|
||||||
self.__finished_image__ = True
|
self.__finished_image__ = True
|
||||||
return self.__image__
|
return self.__image__
|
||||||
|
|
||||||
def draw (self, subimage, pos, mask=False, invert_mask=False, color_key=False):
|
def draw(self, subimage, pos, mask=False, invert_mask=False, color_key=False):
|
||||||
rounded_pos = (int(pos[0]),int(pos[1]))
|
rounded_pos = (int(pos[0]), int(pos[1]))
|
||||||
img_mask = None
|
img_mask = None
|
||||||
if mask:
|
if mask:
|
||||||
img_mask = self.__get_mask__(subimage, invert_mask=invert_mask, color_key=color_key)
|
img_mask = self.__get_mask__(
|
||||||
|
subimage, invert_mask=invert_mask, color_key=color_key)
|
||||||
self.__image__.paste(subimage, rounded_pos, mask=img_mask)
|
self.__image__.paste(subimage, rounded_pos, mask=img_mask)
|
||||||
|
|
||||||
def draw_design (self, entity):
|
def draw_design(self, entity):
|
||||||
self.draw(entity.get_image(), entity.pos, entity.mask, entity.invert_mask, entity.color_key)
|
self.draw(entity.get_image(), entity.pos, entity.mask,
|
||||||
|
entity.invert_mask, entity.color_key)
|
||||||
|
|
||||||
def draw_image (self, path, pos):
|
def draw_image(self, path, pos):
|
||||||
self.draw(Image.open(path), pos)
|
self.draw(Image.open(path), pos)
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def __get_mask__ (self, image, invert_mask, color_key):
|
def __get_mask__(self, image, invert_mask, color_key):
|
||||||
mask = image.convert('L')
|
mask = image.convert('L')
|
||||||
if color_key:
|
if color_key:
|
||||||
mask = mask.point(lambda p : 255 if p >= masking_threshold else 0)
|
mask = mask.point(lambda p: 0 if p <
|
||||||
|
masking_threshold else 255, '1').convert('L')
|
||||||
if invert_mask:
|
if invert_mask:
|
||||||
mask = ImageOps.invert(mask)
|
mask = ImageOps.invert(mask)
|
||||||
return ImageOps.invert(mask)
|
return ImageOps.invert(mask)
|
||||||
|
|
99
Calendar/Dictionary.py
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
default_language = "en"
|
||||||
|
|
||||||
|
'''Characters following '*' are placeholders and will be replaced by some number/text/etc.'''
|
||||||
|
|
||||||
|
'''Events'''
|
||||||
|
more_events = {
|
||||||
|
'en': '+ *0 more',
|
||||||
|
'de': '+ *0 weitere'
|
||||||
|
}
|
||||||
|
multiday_events = {
|
||||||
|
'en': 'Multi-day',
|
||||||
|
'de': 'Mehrtägig'
|
||||||
|
}
|
||||||
|
allday_events = {
|
||||||
|
'en': 'All-day',
|
||||||
|
'de': 'Ganztägig'
|
||||||
|
}
|
||||||
|
|
||||||
|
'''Weather'''
|
||||||
|
rain_weather = {
|
||||||
|
'en': 'Rain',
|
||||||
|
'de': 'Regen'
|
||||||
|
}
|
||||||
|
clear_weather = {
|
||||||
|
'en': 'Clear',
|
||||||
|
'de': 'Klar'
|
||||||
|
}
|
||||||
|
clouds_weather = {
|
||||||
|
'en': 'Clouds',
|
||||||
|
'de': 'Wolken'
|
||||||
|
}
|
||||||
|
thunderstorm_weather = {
|
||||||
|
'en': 'Thunderstorm',
|
||||||
|
'de': 'Gewitter'
|
||||||
|
}
|
||||||
|
drizzle_weather = {
|
||||||
|
'en': 'Drizzle',
|
||||||
|
'de': 'Niesel'
|
||||||
|
}
|
||||||
|
snow_weather = {
|
||||||
|
'en': 'Snow',
|
||||||
|
'de': 'Schnee'
|
||||||
|
}
|
||||||
|
mist_weather = {
|
||||||
|
'en': 'Mist',
|
||||||
|
'de': 'Nebel'
|
||||||
|
}
|
||||||
|
smoke_weather = {
|
||||||
|
'en': 'Smoke',
|
||||||
|
'de': 'Rauch'
|
||||||
|
}
|
||||||
|
haze_weather = {
|
||||||
|
'en': 'Haze',
|
||||||
|
'de': 'Nebel'
|
||||||
|
}
|
||||||
|
dust_weather = {
|
||||||
|
'en': 'Dust',
|
||||||
|
'de': 'Staub'
|
||||||
|
}
|
||||||
|
fog_weather = {
|
||||||
|
'en': 'Fog',
|
||||||
|
'de': 'Nebel'
|
||||||
|
}
|
||||||
|
sand_weather = {
|
||||||
|
'en': 'Sand',
|
||||||
|
'de': 'Sand'
|
||||||
|
}
|
||||||
|
ash_weather = {
|
||||||
|
'en': 'Ash',
|
||||||
|
'de': 'Asche'
|
||||||
|
}
|
||||||
|
squall_weather = {
|
||||||
|
'en': 'Squall',
|
||||||
|
'de': 'Sturm'
|
||||||
|
}
|
||||||
|
tornado_weather = {
|
||||||
|
'en': 'Tornado',
|
||||||
|
'de': 'Tornado'
|
||||||
|
}
|
||||||
|
dictionary_collection = [
|
||||||
|
rain_weather,
|
||||||
|
clear_weather,
|
||||||
|
dust_weather,
|
||||||
|
squall_weather,
|
||||||
|
tornado_weather,
|
||||||
|
clouds_weather,
|
||||||
|
thunderstorm_weather,
|
||||||
|
smoke_weather,
|
||||||
|
ash_weather,
|
||||||
|
sand_weather,
|
||||||
|
fog_weather,
|
||||||
|
haze_weather,
|
||||||
|
mist_weather,
|
||||||
|
drizzle_weather,
|
||||||
|
snow_weather,
|
||||||
|
more_events,
|
||||||
|
allday_events,
|
||||||
|
multiday_events
|
||||||
|
]
|
29
Calendar/DictionaryMapper.py
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
from Dictionary import default_language
|
||||||
|
from settings import language
|
||||||
|
|
||||||
|
'''Takes a collection of phrases and outputs the necessary text
|
||||||
|
according to the language and inserts parameters.'''
|
||||||
|
|
||||||
|
|
||||||
|
def get_text(dictionary, *params):
|
||||||
|
text = dictionary[default_language]
|
||||||
|
if language in dictionary.keys():
|
||||||
|
text = dictionary[language]
|
||||||
|
|
||||||
|
return __insert_params__(text, params)
|
||||||
|
|
||||||
|
|
||||||
|
def __insert_params__(text, params):
|
||||||
|
index = 0
|
||||||
|
while '*%d' % index in text and index < len(params):
|
||||||
|
splitted = text.split('*%d' % index)
|
||||||
|
text = splitted[0] + str(params[index]) + splitted[1]
|
||||||
|
index += 1
|
||||||
|
while '*' in text:
|
||||||
|
splitted = text.split('*%d' % index)
|
||||||
|
if len(splitted) > 1:
|
||||||
|
text = splitted[0] + splitted[1].lstrip(' ')
|
||||||
|
else:
|
||||||
|
text = splitted[0].rsplit(' ')
|
||||||
|
index += 1
|
||||||
|
return text
|
|
@ -1,11 +1,12 @@
|
||||||
class DisplayAdapter (object):
|
class DisplayAdapter (object):
|
||||||
"""Interface for CalendarDesign output channels."""
|
"""Interface for CalendarDesign output channels."""
|
||||||
|
|
||||||
def __init__(self, width, height):
|
def __init__(self, width, height):
|
||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
|
||||||
def render (self, design):
|
def render(self, design):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def calibrate (self):
|
def calibrate(self):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
|
@ -9,24 +9,36 @@ Copyright by aceisace
|
||||||
"""
|
"""
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from Assets import datetime_locals
|
from Assets import path
|
||||||
from LoopTimer import LoopTimer
|
from LoopTimer import LoopTimer
|
||||||
import locale
|
import locale
|
||||||
from DebugConsole import DebugConsole
|
from DebugConsole import DebugConsole
|
||||||
from settings import *
|
from settings import datetime_encoding, language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours, crypto_coins, max_loop_count, run_on_hour
|
||||||
from MonthOvPanel import MonthOvPanel
|
from MonthOvPanel import MonthOvPanel
|
||||||
from DayListPanel import DayListPanel
|
from DayListPanel import DayListPanel
|
||||||
|
from DayViewPanel import DayViewPanel
|
||||||
|
from DayFocusListPanel import DayFocusListPanel
|
||||||
|
from MonthViewPanel import MonthViewPanel
|
||||||
|
from AgendaListPanel import AgendaListPanel
|
||||||
|
from ImageFramePanel import ImageFramePanel
|
||||||
import OwmForecasts
|
import OwmForecasts
|
||||||
import IcalEvents
|
import IcalEvents
|
||||||
import RssParserPosts
|
import RssParserPosts
|
||||||
|
import GeckoCrypto
|
||||||
|
|
||||||
|
all_locales = locale.locale_alias
|
||||||
|
if language.lower() not in all_locales.keys():
|
||||||
|
raise Exception(
|
||||||
|
"The locale for \"%s\" is currently not supported! If you need support, please open an issue on github." % language)
|
||||||
|
locale.setlocale(locale.LC_ALL, "%s.%s" % (
|
||||||
|
all_locales[language.lower()].split('.')[0], datetime_encoding))
|
||||||
|
|
||||||
locale.setlocale(locale.LC_ALL, datetime_locals[language])
|
|
||||||
debug = DebugConsole()
|
debug = DebugConsole()
|
||||||
output_adapters = []
|
output_adapters = []
|
||||||
|
|
||||||
if render_to_file:
|
if render_to_file:
|
||||||
import ImageFileAdapter
|
import ImageFileAdapter
|
||||||
epd = ImageFileAdapter.ImageFileAdapter()
|
epd = ImageFileAdapter.ImageFileAdapter(path)
|
||||||
output_adapters.append(epd)
|
output_adapters.append(epd)
|
||||||
|
|
||||||
if render_to_display:
|
if render_to_display:
|
||||||
|
@ -40,14 +52,28 @@ if render_to_display:
|
||||||
output_adapters.append(epd)
|
output_adapters.append(epd)
|
||||||
|
|
||||||
available_panels = {
|
available_panels = {
|
||||||
"day-list" : DayListPanel,
|
"day-list": DayListPanel,
|
||||||
"month-overview" : MonthOvPanel
|
"month-overview": MonthOvPanel,
|
||||||
|
"day-view": DayViewPanel,
|
||||||
|
"day-focus-list": DayFocusListPanel,
|
||||||
|
"agenda-list": AgendaListPanel,
|
||||||
|
"month-view": MonthViewPanel,
|
||||||
|
"image-frame": ImageFramePanel,
|
||||||
}
|
}
|
||||||
|
|
||||||
loop_timer = LoopTimer(update_interval, run_on_hour=True)
|
loop_timer = LoopTimer(
|
||||||
|
update_interval, run_on_hour=run_on_hour, max_loop_count=max_loop_count)
|
||||||
|
|
||||||
"""Main loop starts from here"""
|
"""Main loop starts from here"""
|
||||||
def main ():
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
owm = OwmForecasts.OwmForecasts(
|
||||||
|
location, api_key, paid_api=owm_paid_subscription)
|
||||||
|
events_cal = IcalEvents.IcalEvents(ical_urls, highlighted_ical_urls)
|
||||||
|
rss = RssParserPosts.RssParserPosts(rss_feeds)
|
||||||
|
crypto = GeckoCrypto.GeckoCrypto(crypto_coins)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
loop_timer.begin_loop()
|
loop_timer.begin_loop()
|
||||||
start_time = loop_timer.get_current()[0]
|
start_time = loop_timer.get_current()[0]
|
||||||
|
@ -57,36 +83,53 @@ def main ():
|
||||||
for output in output_adapters:
|
for output in output_adapters:
|
||||||
output.calibrate()
|
output.calibrate()
|
||||||
|
|
||||||
if choosen_design in available_panels.keys():
|
if choosen_design in available_panels.keys():
|
||||||
design = available_panels[choosen_design]((epd.width, epd.height))
|
design = available_panels[choosen_design]((epd.width, epd.height))
|
||||||
else:
|
else:
|
||||||
raise ImportError("choosen_design must be valid (" + choosen_design + ")")
|
raise ImportError(
|
||||||
|
"choosen_design must be valid (" + choosen_design + ")")
|
||||||
|
|
||||||
debug.print_line("Fetching weather information from open weather map")
|
debug.print_line("Fetching weather information from open weather map")
|
||||||
owm = OwmForecasts.OwmForecasts(location, api_key, paid_api=owm_paid_subscription)
|
owm.reload()
|
||||||
design.add_weather(owm)
|
design.add_weather(owm)
|
||||||
|
|
||||||
debug.print_line('Fetching events from your calendar')
|
debug.print_line('Fetching events from your calendar')
|
||||||
events_cal = IcalEvents.IcalEvents(ical_urls, highlighted_ical_urls)
|
events_cal.reload()
|
||||||
design.add_calendar(events_cal)
|
design.add_calendar(events_cal)
|
||||||
|
|
||||||
debug.print_line('Fetching posts from your rss-feeds')
|
debug.print_line('Fetching posts from your rss-feeds')
|
||||||
rss = RssParserPosts.RssParserPosts(rss_feeds)
|
rss.reload()
|
||||||
design.add_rssfeed(rss)
|
design.add_rssfeed(rss)
|
||||||
|
|
||||||
|
debug.print_line('Fetching crypto prices from coin gecko')
|
||||||
|
crypto.reload()
|
||||||
|
design.add_crypto(crypto)
|
||||||
|
|
||||||
debug.print_line("\nStarting to render")
|
debug.print_line("\nStarting to render")
|
||||||
for i, output in enumerate(output_adapters):
|
for i, output in enumerate(output_adapters):
|
||||||
output.render(design)
|
try:
|
||||||
debug.print_line(str(i + 1) + " of " + str(len(output_adapters)) + " rendered")
|
output.render(design)
|
||||||
|
debug.print_line(str(i + 1) + " of " +
|
||||||
|
str(len(output_adapters)) + " rendered")
|
||||||
|
except BaseException as ex:
|
||||||
|
debug.print_err(ex, "Failed to render output " +
|
||||||
|
str(i + 1) + " of " + str(len(output_adapters)))
|
||||||
|
|
||||||
debug.print_line("=> Finished rendering" + "\n")
|
debug.print_line("=> Finished rendering" + "\n")
|
||||||
|
|
||||||
loop_timer.end_loop()
|
loop_timer.end_loop()
|
||||||
sleep_time = loop_timer.time_until_next()
|
|
||||||
|
|
||||||
debug.print_line("This loop took " + str(loop_timer.get_last_duration()) + " to execute.")
|
if loop_timer.was_last_loop():
|
||||||
|
debug.print_line("Maximum loop count " +
|
||||||
|
str(loop_timer.loop_count) + " reached, exiting.")
|
||||||
|
return
|
||||||
|
|
||||||
|
sleep_time = loop_timer.time_until_next()
|
||||||
|
debug.print_line("This loop took " +
|
||||||
|
str(loop_timer.get_last_duration()) + " to execute.")
|
||||||
debug.print_line("Sleeping " + str(sleep_time) + " until next loop.")
|
debug.print_line("Sleeping " + str(sleep_time) + " until next loop.")
|
||||||
sleep(sleep_time.total_seconds())
|
sleep(sleep_time.total_seconds())
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|
|
@ -1,10 +1,14 @@
|
||||||
from BoxDesign import BoxDesign
|
from BoxDesign import BoxDesign
|
||||||
from PIL import ImageDraw, ImageOps
|
from PIL import ImageDraw, ImageOps
|
||||||
|
|
||||||
|
|
||||||
class EllipseDesign (BoxDesign):
|
class EllipseDesign (BoxDesign):
|
||||||
"""Redefinition of ImageDraw.Draw.Rectangle"""
|
"""Redefinition of ImageDraw.Draw.Rectangle"""
|
||||||
def __init__(self, size, fill=None, outline=None, width=0):
|
|
||||||
super(EllipseDesign, self).__init__(size, fill=fill, outline=outline, width=width)
|
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __init__(self, size, fill=None, outline=None, width=0):
|
||||||
ImageDraw.Draw(self.__image__).ellipse(self.corners, fill=self.fill, outline=self.outline, width=self.width)
|
super(EllipseDesign, self).__init__(
|
||||||
|
size, fill=fill, outline=outline, width=width)
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
ImageDraw.Draw(self.__image__).ellipse(
|
||||||
|
self.corners, fill=self.fill, outline=self.outline, width=self.width)
|
||||||
|
|
|
@ -2,11 +2,12 @@ from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
|
||||||
from settings import display_colours
|
from settings import display_colours
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
|
|
||||||
|
|
||||||
class Epd7in5Adapter (EpdAdapter):
|
class Epd7in5Adapter (EpdAdapter):
|
||||||
def __init__ (self):
|
def __init__(self):
|
||||||
super(Epd7in5Adapter, self).__init__(384, 640)
|
super(Epd7in5Adapter, self).__init__(384, 640)
|
||||||
|
|
||||||
def display_frame (self, frame_buffer):
|
def display_frame(self, frame_buffer):
|
||||||
self.send_command(DATA_START_TRANSMISSION_1)
|
self.send_command(DATA_START_TRANSMISSION_1)
|
||||||
for i in range(0, 30720):
|
for i in range(0, 30720):
|
||||||
temp1 = frame_buffer[i]
|
temp1 = frame_buffer[i]
|
||||||
|
@ -30,11 +31,11 @@ class Epd7in5Adapter (EpdAdapter):
|
||||||
self.delay_ms(100)
|
self.delay_ms(100)
|
||||||
self.wait_until_idle()
|
self.wait_until_idle()
|
||||||
|
|
||||||
def get_frame_buffer (self, image):
|
def get_frame_buffer(self, image):
|
||||||
buf = [0x00] * int(self.height * self.width / 8)
|
buf = [0x00] * int(self.height * self.width / 8)
|
||||||
# Set buffer to value of Python Imaging Library image.
|
# Set buffer to value of Python Imaging Library image.
|
||||||
# Image must be in mode 1.
|
# Image must be in mode 1.
|
||||||
image_monocolor = image.convert('1') #with ot withour dithering?
|
image_monocolor = image.convert('L') # with ot withour dithering?
|
||||||
imwidth, imheight = image_monocolor.size
|
imwidth, imheight = image_monocolor.size
|
||||||
if imwidth != self.height or imheight != self.width:
|
if imwidth != self.height or imheight != self.width:
|
||||||
raise ValueError('Image must be same dimensions as display \
|
raise ValueError('Image must be same dimensions as display \
|
||||||
|
@ -44,11 +45,11 @@ class Epd7in5Adapter (EpdAdapter):
|
||||||
for y in range(self.width):
|
for y in range(self.width):
|
||||||
for x in range(self.height):
|
for x in range(self.height):
|
||||||
# Set the bits for the column of pixels at the current position.
|
# Set the bits for the column of pixels at the current position.
|
||||||
if pixels[x, y] != 0:
|
if pixels[x, y] >= 240: # White
|
||||||
buf[int((x + y * self.height) / 8)] |= 0x80 >> (x % 8)
|
buf[int((x + y * self.height) / 8)] |= 0x80 >> (x % 8)
|
||||||
return buf
|
return buf
|
||||||
|
|
||||||
def calibrate (self):
|
def calibrate(self):
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
self.init_render()
|
self.init_render()
|
||||||
black = Image.new('1', (self.height, self.width), 'black')
|
black = Image.new('1', (self.height, self.width), 'black')
|
||||||
|
@ -61,4 +62,4 @@ class Epd7in5Adapter (EpdAdapter):
|
||||||
print('calibrating white...')
|
print('calibrating white...')
|
||||||
self.display_frame(self.get_frame_buffer(white))
|
self.display_frame(self.get_frame_buffer(white))
|
||||||
self.sleep()
|
self.sleep()
|
||||||
print('Calibration complete')
|
print('Calibration complete')
|
||||||
|
|
|
@ -2,17 +2,19 @@ from EpdAdapter import EpdAdapter, DISPLAY_REFRESH, DATA_START_TRANSMISSION_1
|
||||||
from settings import display_colours
|
from settings import display_colours
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
from math import sqrt, pow
|
from math import sqrt, pow
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
class Epd7in5bAdapter (EpdAdapter):
|
class Epd7in5bAdapter (EpdAdapter):
|
||||||
def __init__ (self):
|
def __init__(self):
|
||||||
super(Epd7in5bAdapter, self).__init__(384, 640)
|
super(Epd7in5bAdapter, self).__init__(384, 640)
|
||||||
|
|
||||||
def display_frame (self, frame_buffer):
|
def display_frame(self, frame_buffer):
|
||||||
self.send_command(DATA_START_TRANSMISSION_1)
|
self.send_command(DATA_START_TRANSMISSION_1)
|
||||||
for i in range(0, int(self.height / 4 * self.width)):
|
for i in range(0, int(self.height / 4 * self.width)):
|
||||||
#the above line had to be modified due to python2 -> python3
|
# the above line had to be modified due to python2 -> python3
|
||||||
#the issue lies in division, which returns integers in python2
|
# the issue lies in division, which returns integers in python2
|
||||||
#but floats in python3
|
# but floats in python3
|
||||||
temp1 = frame_buffer[i]
|
temp1 = frame_buffer[i]
|
||||||
j = 0
|
j = 0
|
||||||
while (j < 4):
|
while (j < 4):
|
||||||
|
@ -38,50 +40,38 @@ class Epd7in5bAdapter (EpdAdapter):
|
||||||
self.delay_ms(100)
|
self.delay_ms(100)
|
||||||
self.wait_until_idle()
|
self.wait_until_idle()
|
||||||
|
|
||||||
def get_frame_buffer (self, image):
|
def get_frame_buffer(self, image):
|
||||||
buf = [ 0x00 ] * int(self.height * self.width / 4)
|
buf = [0x00] * int(self.height * self.width / 4)
|
||||||
image_rgb = image
|
imwidth, imheight = image.size
|
||||||
imwidth, imheight = image_rgb.size
|
|
||||||
if imwidth != self.height or imheight != self.width:
|
if imwidth != self.height or imheight != self.width:
|
||||||
raise ValueError('Image must be same dimensions as display \
|
raise ValueError('Image must be same dimensions as display \
|
||||||
({0}x{1}).' .format(self.height, self.width))
|
({0}x{1}).' .format(self.height, self.width))
|
||||||
|
|
||||||
for y in range(self.width):
|
image_buf = self.__prepare_image__(image)
|
||||||
for x in range(self.height):
|
for x in range(self.width):
|
||||||
|
for y in range(self.height):
|
||||||
# Set the bits for the column of pixels at the current
|
# Set the bits for the column of pixels at the current
|
||||||
# position.
|
# position.
|
||||||
pixel = image_rgb.getpixel((x, y))
|
if image_buf[x, y, 1] == 255: # White
|
||||||
color = self.__get_color__(pixel)
|
buf[int((y + x * self.height) / 4)] |= 0xC0 >> (y % 4 * 2)
|
||||||
if color is 'white':
|
elif image_buf[x, y, 0] == 0: # Black
|
||||||
buf[int((x + y * self.height) / 4)] |= 0xC0 >> (x % 4 * 2)
|
buf[int((y + x * self.height) / 4)
|
||||||
elif color is 'black':
|
] &= ~(0xC0 >> (y % 4 * 2))
|
||||||
buf[int((x + y * self.height) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
else: # Red
|
||||||
if color is 'red':
|
buf[int((y + x * self.height) / 4)
|
||||||
buf[int((x + y * self.height) / 4)] &= ~(0xC0 >> (x % 4 * 2))
|
] &= ~(0xC0 >> (y % 4 * 2))
|
||||||
buf[int((x + y * self.height) / 4)] |= 0x40 >> (x % 4 * 2)
|
buf[int((y + x * self.height) / 4)] |= 0x40 >> (y % 4 * 2)
|
||||||
return buf #due to python2 -> python3, int had to be added in 'get_frame
|
return buf
|
||||||
#_buffer
|
|
||||||
|
|
||||||
def __get_color__ (self, pixel):
|
def __prepare_image__(self, image):
|
||||||
color_percent = self.__get_color_percentage__(pixel)
|
buffer = np.array(image)
|
||||||
brightness = self.__brightness__(pixel)
|
r, g = buffer[:, :, 0], buffer[:, :, 1]
|
||||||
if brightness > 240 or (brightness > 220 and color_percent[0] > 40):
|
buffer[np.logical_and(r > 220, g > 220)] = [255, 255, 255]
|
||||||
return 'white'
|
buffer[r > g] = [255, 0, 0]
|
||||||
elif color_percent[0] > 35:
|
buffer[r != 255] = [0, 0, 0]
|
||||||
return 'red'
|
return buffer
|
||||||
else:
|
|
||||||
return 'black'
|
|
||||||
|
|
||||||
def __get_color_percentage__ (self, pixel):
|
def calibrate(self):
|
||||||
sum = pixel[0] + pixel[1] + pixel[2]
|
|
||||||
if sum is 0:
|
|
||||||
return (0,0,0)
|
|
||||||
return (pixel[0] / sum * 100, pixel[1] / sum * 100, pixel[2] / sum * 100)
|
|
||||||
|
|
||||||
def __brightness__ (self, pixel):
|
|
||||||
return (pixel[0] + pixel[1] + pixel[2]) / 3
|
|
||||||
|
|
||||||
def calibrate (self):
|
|
||||||
for _ in range(2):
|
for _ in range(2):
|
||||||
self.init_render()
|
self.init_render()
|
||||||
black = Image.new('RGB', (self.height, self.width), 'black')
|
black = Image.new('RGB', (self.height, self.width), 'black')
|
||||||
|
@ -99,4 +89,4 @@ class Epd7in5bAdapter (EpdAdapter):
|
||||||
print('calibrating white...')
|
print('calibrating white...')
|
||||||
self.display_frame(self.get_frame_buffer(white))
|
self.display_frame(self.get_frame_buffer(white))
|
||||||
self.sleep()
|
self.sleep()
|
||||||
print('Calibration complete')
|
print('Calibration complete')
|
||||||
|
|
|
@ -4,53 +4,55 @@ import RPi.GPIO as GPIO
|
||||||
import time
|
import time
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
RST_PIN = 17
|
RST_PIN = 17
|
||||||
DC_PIN = 25
|
DC_PIN = 25
|
||||||
CS_PIN = 8
|
CS_PIN = 8
|
||||||
BUSY_PIN = 24
|
BUSY_PIN = 24
|
||||||
|
|
||||||
# Commands
|
# Commands
|
||||||
PANEL_SETTING = 0x00
|
PANEL_SETTING = 0x00
|
||||||
POWER_SETTING = 0x01
|
POWER_SETTING = 0x01
|
||||||
POWER_OFF = 0x02
|
POWER_OFF = 0x02
|
||||||
POWER_OFF_SEQUENCE_SETTING = 0x03
|
POWER_OFF_SEQUENCE_SETTING = 0x03
|
||||||
POWER_ON = 0x04
|
POWER_ON = 0x04
|
||||||
POWER_ON_MEASURE = 0x05
|
POWER_ON_MEASURE = 0x05
|
||||||
BOOSTER_SOFT_START = 0x06
|
BOOSTER_SOFT_START = 0x06
|
||||||
DEEP_SLEEP = 0x07
|
DEEP_SLEEP = 0x07
|
||||||
DATA_START_TRANSMISSION_1 = 0x10
|
DATA_START_TRANSMISSION_1 = 0x10
|
||||||
DATA_STOP = 0x11
|
DATA_STOP = 0x11
|
||||||
DISPLAY_REFRESH = 0x12
|
DISPLAY_REFRESH = 0x12
|
||||||
IMAGE_PROCESS = 0x13
|
IMAGE_PROCESS = 0x13
|
||||||
LUT_FOR_VCOM = 0x20
|
LUT_FOR_VCOM = 0x20
|
||||||
LUT_BLUE = 0x21
|
LUT_BLUE = 0x21
|
||||||
LUT_WHITE = 0x22
|
LUT_WHITE = 0x22
|
||||||
LUT_GRAY_1 = 0x23
|
LUT_GRAY_1 = 0x23
|
||||||
LUT_GRAY_2 = 0x24
|
LUT_GRAY_2 = 0x24
|
||||||
LUT_RED_0 = 0x25
|
LUT_RED_0 = 0x25
|
||||||
LUT_RED_1 = 0x26
|
LUT_RED_1 = 0x26
|
||||||
LUT_RED_2 = 0x27
|
LUT_RED_2 = 0x27
|
||||||
LUT_RED_3 = 0x28
|
LUT_RED_3 = 0x28
|
||||||
LUT_XON = 0x29
|
LUT_XON = 0x29
|
||||||
PLL_CONTROL = 0x30
|
PLL_CONTROL = 0x30
|
||||||
TEMPERATURE_SENSOR_COMMAND = 0x40
|
TEMPERATURE_SENSOR_COMMAND = 0x40
|
||||||
TEMPERATURE_CALIBRATION = 0x41
|
TEMPERATURE_CALIBRATION = 0x41
|
||||||
TEMPERATURE_SENSOR_WRITE = 0x42
|
TEMPERATURE_SENSOR_WRITE = 0x42
|
||||||
TEMPERATURE_SENSOR_READ = 0x43
|
TEMPERATURE_SENSOR_READ = 0x43
|
||||||
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
|
VCOM_AND_DATA_INTERVAL_SETTING = 0x50
|
||||||
LOW_POWER_DETECTION = 0x51
|
LOW_POWER_DETECTION = 0x51
|
||||||
TCON_SETTING = 0x60
|
TCON_SETTING = 0x60
|
||||||
TCON_RESOLUTION = 0x61
|
TCON_RESOLUTION = 0x61
|
||||||
SPI_FLASH_CONTROL = 0x65
|
SPI_FLASH_CONTROL = 0x65
|
||||||
REVISION = 0x70
|
REVISION = 0x70
|
||||||
GET_STATUS = 0x71
|
GET_STATUS = 0x71
|
||||||
AUTO_MEASUREMENT_VCOM = 0x80
|
AUTO_MEASUREMENT_VCOM = 0x80
|
||||||
READ_VCOM_VALUE = 0x81
|
READ_VCOM_VALUE = 0x81
|
||||||
VCM_DC_SETTING = 0x82
|
VCM_DC_SETTING = 0x82
|
||||||
|
|
||||||
class EpdAdapter (DisplayAdapter):
|
|
||||||
|
class EpdAdapter (DisplayAdapter):
|
||||||
"""Generalized adapter for epd7in5 and epd7in5b"""
|
"""Generalized adapter for epd7in5 and epd7in5b"""
|
||||||
def __init__ (self, width, height):
|
|
||||||
|
def __init__(self, width, height):
|
||||||
super(EpdAdapter, self).__init__(width, height)
|
super(EpdAdapter, self).__init__(width, height)
|
||||||
|
|
||||||
self.reset_pin = RST_PIN
|
self.reset_pin = RST_PIN
|
||||||
|
@ -59,19 +61,19 @@ class EpdAdapter (DisplayAdapter):
|
||||||
|
|
||||||
self.epd_init()
|
self.epd_init()
|
||||||
|
|
||||||
def display_frame (self, frame_buffer):
|
def display_frame(self, frame_buffer):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def get_frame_buffer (self, image):
|
def get_frame_buffer(self, image):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def render (self, design):
|
def render(self, design):
|
||||||
self.init_render()
|
self.init_render()
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
print('Converting image to data and sending it to the display')
|
print('Converting image to data and sending it to the display')
|
||||||
print('This may take a while...' + '\n')
|
print('This may take a while...' + '\n')
|
||||||
prepared_image = design.get_image().rotate(270, expand=1)
|
prepared_image = design.get_image().rotate(270, expand=1).convert("RGB")
|
||||||
self.display_frame(self.get_frame_buffer(prepared_image))
|
self.display_frame(self.get_frame_buffer(prepared_image))
|
||||||
|
|
||||||
# Powering off the E-Paper until the next loop
|
# Powering off the E-Paper until the next loop
|
||||||
|
@ -79,7 +81,7 @@ class EpdAdapter (DisplayAdapter):
|
||||||
print('Data sent successfully')
|
print('Data sent successfully')
|
||||||
print('Powering off the E-Paper until the next loop' + '\n')
|
print('Powering off the E-Paper until the next loop' + '\n')
|
||||||
|
|
||||||
def init_render (self):
|
def init_render(self):
|
||||||
if (self.epd_init() != 0):
|
if (self.epd_init() != 0):
|
||||||
return -1
|
return -1
|
||||||
self.reset()
|
self.reset()
|
||||||
|
@ -113,30 +115,30 @@ class EpdAdapter (DisplayAdapter):
|
||||||
self.send_data(0x22)
|
self.send_data(0x22)
|
||||||
|
|
||||||
self.send_command(TCON_RESOLUTION)
|
self.send_command(TCON_RESOLUTION)
|
||||||
self.send_data(0x02) #source 640
|
self.send_data(0x02) # source 640
|
||||||
self.send_data(0x80)
|
self.send_data(0x80)
|
||||||
self.send_data(0x01) #gate 384
|
self.send_data(0x01) # gate 384
|
||||||
self.send_data(0x80)
|
self.send_data(0x80)
|
||||||
|
|
||||||
self.send_command(VCM_DC_SETTING)
|
self.send_command(VCM_DC_SETTING)
|
||||||
self.send_data(0x1E) #decide by LUT file
|
self.send_data(0x1E) # decide by LUT file
|
||||||
|
|
||||||
self.send_command(0xe5) #FLASH MODE
|
self.send_command(0xe5) # FLASH MODE
|
||||||
self.send_data(0x03)
|
self.send_data(0x03)
|
||||||
|
|
||||||
def digital_write (self, pin, value):
|
def digital_write(self, pin, value):
|
||||||
GPIO.output(pin, value)
|
GPIO.output(pin, value)
|
||||||
|
|
||||||
def digital_read (self, pin):
|
def digital_read(self, pin):
|
||||||
return GPIO.input(pin)
|
return GPIO.input(pin)
|
||||||
|
|
||||||
def delay_ms (self, delaytime):
|
def delay_ms(self, delaytime):
|
||||||
time.sleep(delaytime / 1000.0)
|
time.sleep(delaytime / 1000.0)
|
||||||
|
|
||||||
def spi_transfer (self, data):
|
def spi_transfer(self, data):
|
||||||
self.SPI.writebytes(data)
|
self.SPI.writebytes(data)
|
||||||
|
|
||||||
def epd_init (self):
|
def epd_init(self):
|
||||||
# SPI device, bus = 0, device = 0
|
# SPI device, bus = 0, device = 0
|
||||||
self.SPI = spidev.SpiDev(0, 0)
|
self.SPI = spidev.SpiDev(0, 0)
|
||||||
#self.SPI.no_cs = True
|
#self.SPI.no_cs = True
|
||||||
|
@ -151,30 +153,35 @@ class EpdAdapter (DisplayAdapter):
|
||||||
self.SPI.mode = 0b00
|
self.SPI.mode = 0b00
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def sleep (self):
|
def sleep(self):
|
||||||
self.send_command(POWER_OFF)
|
self.send_command(POWER_OFF)
|
||||||
self.wait_until_idle()
|
self.wait_until_idle()
|
||||||
self.send_command(DEEP_SLEEP)
|
self.send_command(DEEP_SLEEP)
|
||||||
self.send_data(0xa5)
|
self.send_data(0xa5)
|
||||||
|
|
||||||
def wait_until_idle (self):
|
|
||||||
while(self.digital_read(self.busy_pin) == 0): # 0: busy, 1: idle
|
|
||||||
self.delay_ms(100)
|
|
||||||
|
|
||||||
def reset (self):
|
def wait_until_idle(self, max_wait_seconds=60):
|
||||||
|
wait_ms = 100
|
||||||
|
count = 0
|
||||||
|
while(self.digital_read(self.busy_pin) == 0 and wait_ms * count < max_wait_seconds * 1000): # 0: busy, 1: idle
|
||||||
|
self.delay_ms(wait_ms)
|
||||||
|
count += 1
|
||||||
|
if wait_ms * count >= max_wait_seconds * 1000:
|
||||||
|
print("Skipped idle confirmation")
|
||||||
|
|
||||||
|
def reset(self):
|
||||||
self.digital_write(self.reset_pin, GPIO.LOW) # module reset
|
self.digital_write(self.reset_pin, GPIO.LOW) # module reset
|
||||||
self.delay_ms(200)
|
self.delay_ms(200)
|
||||||
self.digital_write(self.reset_pin, GPIO.HIGH)
|
self.digital_write(self.reset_pin, GPIO.HIGH)
|
||||||
self.delay_ms(200)
|
self.delay_ms(200)
|
||||||
|
|
||||||
def send_command (self, command):
|
def send_command(self, command):
|
||||||
self.digital_write(self.dc_pin, GPIO.LOW)
|
self.digital_write(self.dc_pin, GPIO.LOW)
|
||||||
# the parameter type is list but not int
|
# the parameter type is list but not int
|
||||||
# so use [command] instead of command
|
# so use [command] instead of command
|
||||||
self.spi_transfer([ command ])
|
self.spi_transfer([command])
|
||||||
|
|
||||||
def send_data (self, data):
|
def send_data(self, data):
|
||||||
self.digital_write(self.dc_pin, GPIO.HIGH)
|
self.digital_write(self.dc_pin, GPIO.HIGH)
|
||||||
# the parameter type is list but not int
|
# the parameter type is list but not int
|
||||||
# so use [data] instead of data
|
# so use [data] instead of data
|
||||||
self.spi_transfer([ data ])
|
self.spi_transfer([data])
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from TableTextDesign import TableTextDesign
|
from TableDesign import TableDesign
|
||||||
|
from Assets import defaultfontsize, colors
|
||||||
|
from TextFormatter import date_str
|
||||||
|
from DictionaryMapper import get_text
|
||||||
|
from Dictionary import more_events
|
||||||
|
|
||||||
|
|
||||||
class EventListDesign (DesignEntity):
|
class EventListDesign (DesignEntity):
|
||||||
"""Creates a TableTextDesign filled with event
|
"""Creates a TableDesign filled with event
|
||||||
begin date and title"""
|
begin date and title"""
|
||||||
def __init__ (self, size, calendar, text_size = 16, filter_date=None, line_spacing=2, col_spacing=10, event_prefix_func=None, font_family=None, general_color="black", background_color="white", highlight_color="red"):
|
|
||||||
|
def __init__(self, size, events, text_size=defaultfontsize, line_spacing=0, col_spacing=10, event_prefix_rel_dates=[], event_prefix_func=None, font_family=None, general_color=colors["fg"], background_color=colors["bg"], highlight_color=colors["hl"], show_more_info=False):
|
||||||
super(EventListDesign, self).__init__(size)
|
super(EventListDesign, self).__init__(size)
|
||||||
self.calendar = calendar
|
self.events = events
|
||||||
self.__event_matrix__ = []
|
self.__event_matrix__ = []
|
||||||
self.__props_matrix__ = []
|
self.__props_matrix__ = []
|
||||||
|
self.show_more_info = show_more_info
|
||||||
self.text_size = text_size
|
self.text_size = text_size
|
||||||
self.filter_date = filter_date
|
|
||||||
self.line_spacing = line_spacing
|
self.line_spacing = line_spacing
|
||||||
self.col_spacing = col_spacing
|
self.col_spacing = col_spacing
|
||||||
self.font_family = font_family
|
self.font_family = font_family
|
||||||
|
@ -18,45 +24,51 @@ class EventListDesign (DesignEntity):
|
||||||
self.background_color = background_color
|
self.background_color = background_color
|
||||||
self.highlight_color = highlight_color
|
self.highlight_color = highlight_color
|
||||||
self.event_prefix_func = event_prefix_func
|
self.event_prefix_func = event_prefix_func
|
||||||
|
self.event_prefix_rel_dates = event_prefix_rel_dates
|
||||||
if self.event_prefix_func is None:
|
if self.event_prefix_func is None:
|
||||||
self.event_prefix_func = lambda x : self.__remove_leading_zero__(x.begin_datetime.strftime('%d %b'))
|
self.event_prefix_func = lambda x, y: date_str(x.begin_datetime)
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
|
self.visible_event_count = int(int(
|
||||||
|
self.size[1] + self.line_spacing) // (self.line_spacing + int(self.text_size)))
|
||||||
self.__fill_event_matrix__()
|
self.__fill_event_matrix__()
|
||||||
|
|
||||||
col_hori_alignment = ['right', 'left']
|
col_hori_alignment = ['right', 'left']
|
||||||
|
table_design = TableDesign(self.size, background_color=self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing,
|
||||||
table_design = TableTextDesign(self.size, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing, text_matrix=self.__event_matrix__, fontsize = self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__)
|
matrix=self.__event_matrix__, fontsize=self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__)
|
||||||
self.draw_design(table_design)
|
self.draw_design(table_design)
|
||||||
|
|
||||||
def __get_formatted_event__ (self, event):
|
|
||||||
prefix = self.event_prefix_func(event)
|
|
||||||
return [ prefix, event.title ]
|
|
||||||
|
|
||||||
def __remove_leading_zero__(self, text):
|
def __get_formatted_event__(self, event, index):
|
||||||
while text[0] is '0':
|
rel_date = None if index < 0 or index >= len(
|
||||||
text = text[1:]
|
self.event_prefix_rel_dates) else self.event_prefix_rel_dates[index]
|
||||||
return text
|
prefix = self.event_prefix_func(event, rel_date)
|
||||||
|
return [prefix, event.title]
|
||||||
def __fill_event_matrix__ (self):
|
|
||||||
for event in self.__get_events__():
|
def __fill_event_matrix__(self):
|
||||||
row = self.__get_formatted_event__(event)
|
visible_events = self.events
|
||||||
|
if self.show_more_info and len(visible_events) > self.visible_event_count:
|
||||||
|
visible_events = visible_events[:self.visible_event_count - 1]
|
||||||
|
for i, event in enumerate(visible_events):
|
||||||
|
row = self.__get_formatted_event__(event, i)
|
||||||
self.__event_matrix__.append(row)
|
self.__event_matrix__.append(row)
|
||||||
self.__props_matrix__.append(self.__get_row_props__(event))
|
self.__props_matrix__.append(self.__get_row_props__(event))
|
||||||
|
|
||||||
def __get_events__(self):
|
if self.show_more_info is False:
|
||||||
upcoming = self.calendar.get_upcoming_events()
|
return
|
||||||
if self.filter_date is not None:
|
|
||||||
upcoming = [event for event in upcoming if event.begin_datetime.date() == self.filter_date]
|
|
||||||
return upcoming
|
|
||||||
|
|
||||||
def __get_row_props__(self, event):
|
additional_events_count = len(self.events) - len(visible_events)
|
||||||
|
more_text = " " + get_text(more_events, additional_events_count)
|
||||||
|
if additional_events_count > 0:
|
||||||
|
self.__event_matrix__.append(["", more_text])
|
||||||
|
self.__props_matrix__.append(self.__get_row_props__())
|
||||||
|
|
||||||
|
def __get_row_props__(self, event=None):
|
||||||
color = self.general_color
|
color = self.general_color
|
||||||
bg_color = self.background_color
|
bg_color = self.background_color
|
||||||
if event.highlight:
|
if event is not None and event.highlight:
|
||||||
color = self.highlight_color
|
color = self.highlight_color
|
||||||
cell = {
|
cell = {
|
||||||
"color" : color,
|
"color": color,
|
||||||
"background_color" : bg_color
|
"background_color": bg_color
|
||||||
}
|
}
|
||||||
return [cell, cell]
|
return [cell, cell]
|
||||||
|
|
68
Calendar/GeckoCrypto.py
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
from CryptoInterface import CryptoInterface
|
||||||
|
from datetime import datetime
|
||||||
|
from CryptoCoin import CryptoCoin
|
||||||
|
from urllib.request import urlopen
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
|
||||||
|
api_test_url = "https://api.coingecko.com/api/v3/ping"
|
||||||
|
api_url = "https://api.coingecko.com/api/v3/"
|
||||||
|
api_metadata_url = api_url + "coins/list"
|
||||||
|
api_price_url = api_url + "simple/price"
|
||||||
|
price_currency = "usd"
|
||||||
|
price_currency_sign = "$"
|
||||||
|
|
||||||
|
|
||||||
|
class GeckoCrypto(CryptoInterface):
|
||||||
|
def __init__(self, coins):
|
||||||
|
self.coin_ids = coins
|
||||||
|
self.metadata = None
|
||||||
|
super(GeckoCrypto, self).__init__()
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
try:
|
||||||
|
urlopen(api_test_url)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __get_coins__(self):
|
||||||
|
if len(self.coin_ids) == 0:
|
||||||
|
return []
|
||||||
|
|
||||||
|
self.__prepare_metadata__()
|
||||||
|
coins = []
|
||||||
|
for id in self.coin_ids:
|
||||||
|
try:
|
||||||
|
data = urlopen(api_price_url + "?include_24hr_change=true&ids=" +
|
||||||
|
self.metadata[id]['id'] + "&vs_currencies=" + price_currency).read()
|
||||||
|
dataJSON = json.loads(data.decode('utf-8'))
|
||||||
|
raw = dataJSON[id][price_currency]
|
||||||
|
price = math.ceil(raw*100) / 100
|
||||||
|
change = dataJSON[id]['usd_24h_change']
|
||||||
|
|
||||||
|
coins.append(self.__build_coin__(id, price, change))
|
||||||
|
except:
|
||||||
|
print("Gecko-Error [" + id + "]")
|
||||||
|
return coins
|
||||||
|
|
||||||
|
def __build_coin__(self, id, value, change):
|
||||||
|
coin = CryptoCoin()
|
||||||
|
|
||||||
|
coin.name = self.metadata[id]['name']
|
||||||
|
coin.day_change = round(change, 2)
|
||||||
|
coin.price = value
|
||||||
|
|
||||||
|
coin.datetime = datetime.now()
|
||||||
|
coin.fetch_datetime = datetime.now()
|
||||||
|
coin.currency = price_currency_sign
|
||||||
|
coin.symbol = self.metadata[id]['symbol']
|
||||||
|
|
||||||
|
return coin
|
||||||
|
|
||||||
|
def __prepare_metadata__(self):
|
||||||
|
self.metadata = None
|
||||||
|
data = urlopen(api_metadata_url).read()
|
||||||
|
dataJSON = json.loads(data.decode('utf-8'))
|
||||||
|
self.metadata = {coin['id'].lower(
|
||||||
|
): coin for coin in dataJSON if coin['id'].lower() in self.coin_ids}
|
199
Calendar/HourListDesign.py
Normal file
|
@ -0,0 +1,199 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from settings import hours, language, line_thickness
|
||||||
|
from TextDesign import TextDesign
|
||||||
|
from PIL import ImageDraw
|
||||||
|
from Assets import colors, defaultfontsize, fonts
|
||||||
|
from BoxDesign import BoxDesign
|
||||||
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
|
hourbox_y_width = 1
|
||||||
|
hour_box_fontsize = 0.85
|
||||||
|
hour_ypadding = 0.1
|
||||||
|
hoursubtext_fontsize = 0.7
|
||||||
|
hoursubtext_height = 0.45
|
||||||
|
event_title_fontsize = defaultfontsize
|
||||||
|
event_title_xpadding = 3
|
||||||
|
event_title_ypadding = 5
|
||||||
|
line_thickness = line_thickness
|
||||||
|
currenttimeline_thickness = line_thickness
|
||||||
|
|
||||||
|
event_title_font = fonts['bold']
|
||||||
|
|
||||||
|
|
||||||
|
class HourListDesign (DesignEntity):
|
||||||
|
"""Hours of a day are listed vertically and
|
||||||
|
resemble a timeline."""
|
||||||
|
|
||||||
|
def __init__(self, size, first_hour=0, last_hour=23):
|
||||||
|
super(HourListDesign, self).__init__(size)
|
||||||
|
self.first_hour = first_hour
|
||||||
|
self.last_hour = last_hour
|
||||||
|
self.__calc_parameters__()
|
||||||
|
self.events = []
|
||||||
|
|
||||||
|
def add_events(self, events):
|
||||||
|
self.events.extend(events)
|
||||||
|
self.events.sort(key=lambda x: x.begin_datetime)
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
self.number_columns = self.__get_max_num_simultaneous_events__()
|
||||||
|
self.__draw_lines__()
|
||||||
|
self.__draw_events__()
|
||||||
|
self.__draw_current_time_line__()
|
||||||
|
self.__draw_hour_rows__()
|
||||||
|
|
||||||
|
def __calc_parameters__(self):
|
||||||
|
self.hour_count = self.last_hour - self.first_hour + 1
|
||||||
|
self.row_size = (self.size[0], self.size[1] / self.hour_count)
|
||||||
|
|
||||||
|
def __get_hour_text__(self, hour):
|
||||||
|
if hour <= 12 or hours is "24":
|
||||||
|
return str(hour)
|
||||||
|
else:
|
||||||
|
short = hour - 12
|
||||||
|
return str(short) if short > 0 else "12"
|
||||||
|
|
||||||
|
def __get_ypos_for_time__(self, hour, minute=0):
|
||||||
|
return self.__get_height_for_duration__(hour, minute) - self.__get_height_for_duration__(self.first_hour)
|
||||||
|
|
||||||
|
def __get_height_for_duration__(self, hours, minutes=0):
|
||||||
|
row_height = self.row_size[1]
|
||||||
|
return row_height * (hours + minutes / 60)
|
||||||
|
|
||||||
|
def __draw_events__(self):
|
||||||
|
column_events = []
|
||||||
|
for _ in range(self.number_columns):
|
||||||
|
column_events.append(None)
|
||||||
|
for event in self.events:
|
||||||
|
column_events = self.__update_columns_events__(
|
||||||
|
column_events, event)
|
||||||
|
self.__draw_event__(event, column_events.index(event))
|
||||||
|
|
||||||
|
def __update_columns_events__(self, column_events, new_event):
|
||||||
|
current_time = new_event.begin_datetime
|
||||||
|
new_event_added = False
|
||||||
|
for index in range(len(column_events)):
|
||||||
|
if column_events[index] != None and column_events[index].end_datetime <= current_time:
|
||||||
|
column_events[index] = None
|
||||||
|
if new_event_added == False and column_events[index] == None:
|
||||||
|
column_events[index] = new_event
|
||||||
|
new_event_added = True
|
||||||
|
return column_events
|
||||||
|
|
||||||
|
def __draw_hour_rows__(self):
|
||||||
|
for hour in range(self.first_hour, self.last_hour + 1):
|
||||||
|
self.__draw_row__(hour)
|
||||||
|
|
||||||
|
def __draw_row__(self, hour):
|
||||||
|
subtext_height = self.row_size[1] * hoursubtext_height
|
||||||
|
sub_fontsize = subtext_height * hoursubtext_fontsize
|
||||||
|
ypadding = hour_ypadding * self.row_size[1]
|
||||||
|
width = hourbox_y_width * self.row_size[1]
|
||||||
|
height = self.row_size[1] - subtext_height
|
||||||
|
size = (width, height)
|
||||||
|
pos = (0, self.__get_ypos_for_time__(hour) + ypadding)
|
||||||
|
fontsize = size[1] * hour_box_fontsize
|
||||||
|
|
||||||
|
txt = TextDesign(size, text=self.__get_hour_text__(
|
||||||
|
hour), fontsize=fontsize, verticalalignment="bottom", horizontalalignment="center")
|
||||||
|
txt.pos = pos
|
||||||
|
self.draw_design(txt)
|
||||||
|
|
||||||
|
sub = TextDesign((width, subtext_height), text=self.__get_hour_sub_text__(
|
||||||
|
hour), fontsize=sub_fontsize, verticalalignment="top", horizontalalignment="center")
|
||||||
|
sub.pos = (0, height + self.__get_ypos_for_time__(hour))
|
||||||
|
self.draw_design(sub)
|
||||||
|
|
||||||
|
def __draw_lines__(self):
|
||||||
|
for i in range(self.hour_count):
|
||||||
|
ypos = i * self.row_size[1]
|
||||||
|
line_start = (0, ypos)
|
||||||
|
line_end = (self.size[0], ypos)
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["fg"], width=line_thickness)
|
||||||
|
|
||||||
|
def __get_hour_sub_text__(self, hour):
|
||||||
|
if hours == "12":
|
||||||
|
return "AM" if hour < 12 else "PM"
|
||||||
|
elif language is "de":
|
||||||
|
return "Uhr"
|
||||||
|
elif language is "en":
|
||||||
|
return "o'c"
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def __draw_event__(self, event, column=0):
|
||||||
|
xoffset = hourbox_y_width * self.row_size[1]
|
||||||
|
column_width = (self.size[0] - xoffset) / self.number_columns
|
||||||
|
|
||||||
|
begin = event.begin_datetime
|
||||||
|
time_ypos = self.__get_ypos_for_time__(begin.hour, begin.minute)
|
||||||
|
hours = event.duration.total_seconds() / 3600
|
||||||
|
time_height = self.__get_height_for_duration__(hours)
|
||||||
|
|
||||||
|
yoffset_correction = 0
|
||||||
|
if time_ypos < 0:
|
||||||
|
yoffset_correction = time_ypos
|
||||||
|
|
||||||
|
pos = (xoffset + column_width * column, time_ypos - yoffset_correction)
|
||||||
|
size = (column_width, time_height + yoffset_correction)
|
||||||
|
|
||||||
|
if size[1] < 0:
|
||||||
|
return # Event not in shown time range
|
||||||
|
|
||||||
|
self.__draw_event_block__(pos, size, event)
|
||||||
|
|
||||||
|
def __draw_event_block__(self, pos, size, event):
|
||||||
|
box_color = colors["hl"] if event.highlight else colors["fg"]
|
||||||
|
box = BoxDesign(size, fill=box_color)
|
||||||
|
box.mask = False
|
||||||
|
box.pos = pos
|
||||||
|
self.draw_design(box)
|
||||||
|
|
||||||
|
text = event.title
|
||||||
|
text_color = colors["bg"]
|
||||||
|
textbox_size = (size[0] - event_title_xpadding,
|
||||||
|
size[1] - event_title_ypadding)
|
||||||
|
txt = TextDesign(textbox_size, text=text, font=event_title_font,
|
||||||
|
fontsize=event_title_fontsize, color=text_color, background_color=box_color, wrap=True)
|
||||||
|
txt.mask = False
|
||||||
|
txt.pos = (pos[0] + event_title_xpadding,
|
||||||
|
pos[1] + event_title_ypadding)
|
||||||
|
self.draw_design(txt)
|
||||||
|
|
||||||
|
half_ypadding = int(event_title_ypadding / 2)
|
||||||
|
line_start = (pos[0] + event_title_xpadding, pos[1] + half_ypadding)
|
||||||
|
line_end = (pos[0] + size[0] - event_title_xpadding,
|
||||||
|
pos[1] + half_ypadding)
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["bg"], width=1)
|
||||||
|
|
||||||
|
def __get_max_num_simultaneous_events__(self):
|
||||||
|
parallelity_count = 1
|
||||||
|
|
||||||
|
for index, event in enumerate(self.events):
|
||||||
|
current_parallelity = 1
|
||||||
|
# Assumption: Events are ordered chronologically
|
||||||
|
preceding = self.events[:index]
|
||||||
|
for pre_event in preceding:
|
||||||
|
if self.__are_simultaneous__(pre_event, event):
|
||||||
|
current_parallelity += 1
|
||||||
|
if parallelity_count < current_parallelity:
|
||||||
|
parallelity_count = current_parallelity
|
||||||
|
return parallelity_count
|
||||||
|
|
||||||
|
def __are_simultaneous__(self, ev_a, ev_b):
|
||||||
|
if ev_a.begin_datetime > ev_b.begin_datetime:
|
||||||
|
ev_a, ev_b = ev_b, ev_a
|
||||||
|
|
||||||
|
mes_dur = ev_b.begin_datetime - ev_a.begin_datetime
|
||||||
|
|
||||||
|
return mes_dur < ev_a.duration
|
||||||
|
|
||||||
|
def __draw_current_time_line__(self):
|
||||||
|
now = datetime.now()
|
||||||
|
ypos = self.__get_ypos_for_time__(now.hour, now.minute)
|
||||||
|
|
||||||
|
line_start = (0, ypos)
|
||||||
|
line_end = (self.size[0], ypos)
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[line_start, line_end], fill=colors["hl"], width=currenttimeline_thickness)
|
|
@ -2,19 +2,28 @@ from CalendarInterface import CalendarInterface
|
||||||
from CalendarEvent import CalendarEvent
|
from CalendarEvent import CalendarEvent
|
||||||
from ics import Calendar
|
from ics import Calendar
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
import re
|
||||||
from settings import week_starts_on
|
from settings import week_starts_on
|
||||||
try:
|
from urllib.request import urlopen
|
||||||
from urllib.request import urlopen
|
|
||||||
except Exception as e:
|
|
||||||
print("Something didn't work right, maybe you're offline?" + e.reason)
|
|
||||||
|
|
||||||
class IcalEvents(CalendarInterface):
|
class IcalEvents(CalendarInterface):
|
||||||
"""Fetches events from ical addresses."""
|
"""Fetches events from ical addresses."""
|
||||||
|
|
||||||
def __init__(self, urls, highlighted_urls=None):
|
def __init__(self, urls, highlighted_urls=None):
|
||||||
self.urls = urls
|
self.urls = urls
|
||||||
self.highlighted_urls = highlighted_urls
|
self.highlighted_urls = highlighted_urls
|
||||||
super(IcalEvents, self).__init__()
|
super(IcalEvents, self).__init__()
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
for url in self.urls + self.highlighted_urls:
|
||||||
|
try:
|
||||||
|
urlopen(url)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
def __get_events__(self):
|
def __get_events__(self):
|
||||||
events = self.__get_events_from_urls__(self.urls)
|
events = self.__get_events_from_urls__(self.urls)
|
||||||
|
|
||||||
|
@ -30,19 +39,21 @@ class IcalEvents(CalendarInterface):
|
||||||
|
|
||||||
def __get_events_from_urls__(self, urls):
|
def __get_events_from_urls__(self, urls):
|
||||||
loaded_events = []
|
loaded_events = []
|
||||||
try:
|
|
||||||
if urls is None:
|
|
||||||
return loaded_events
|
|
||||||
|
|
||||||
for calendar in urls:
|
if urls is None:
|
||||||
|
return loaded_events
|
||||||
|
|
||||||
|
for calendar in urls:
|
||||||
|
try:
|
||||||
decode = str(urlopen(calendar).read().decode())
|
decode = str(urlopen(calendar).read().decode())
|
||||||
fixed_decode = self.__fix_errors__(decode)
|
decode = self.__remove_alarms__(decode)
|
||||||
|
|
||||||
ical = Calendar(fixed_decode)
|
ical = Calendar(decode)
|
||||||
for event in ical.events:
|
for event in ical.events:
|
||||||
cal_event = CalendarEvent()
|
cal_event = CalendarEvent()
|
||||||
|
cal_event.calendar_url = calendar
|
||||||
|
|
||||||
cal_event.fetch_datetime = datetime.now()
|
cal_event.fetch_datetime = datetime.now(timezone.utc)
|
||||||
cal_event.begin_datetime = event.begin.datetime
|
cal_event.begin_datetime = event.begin.datetime
|
||||||
cal_event.end_datetime = event.end.datetime
|
cal_event.end_datetime = event.end.datetime
|
||||||
cal_event.duration = event.duration
|
cal_event.duration = event.duration
|
||||||
|
@ -50,16 +61,36 @@ class IcalEvents(CalendarInterface):
|
||||||
cal_event.description = event.description
|
cal_event.description = event.description
|
||||||
cal_event.location = event.location
|
cal_event.location = event.location
|
||||||
cal_event.allday = event.all_day
|
cal_event.allday = event.all_day
|
||||||
|
cal_event.rrule = self.__extract_rrule__(event)
|
||||||
|
|
||||||
|
cal_event.begin_datetime = cal_event.begin_datetime.astimezone(
|
||||||
|
None)
|
||||||
|
cal_event.end_datetime = cal_event.end_datetime.astimezone(
|
||||||
|
None)
|
||||||
|
|
||||||
|
if cal_event.allday:
|
||||||
|
cal_event = self.__fix_allday__(cal_event)
|
||||||
|
|
||||||
|
cal_event.multiday = self.__is_multiday__(cal_event)
|
||||||
|
|
||||||
loaded_events.append(cal_event)
|
loaded_events.append(cal_event)
|
||||||
return loaded_events
|
except BaseException as ex:
|
||||||
except:
|
print("ICal-Error [" + calendar + "]")
|
||||||
return loaded_events
|
print(ex)
|
||||||
|
return loaded_events
|
||||||
|
|
||||||
def __fix_errors__(self, decode):
|
def __fix_allday__(self, event):
|
||||||
decode = self.__remove_alarms__(decode)
|
local_tzinfo = datetime.now(timezone.utc).astimezone().tzinfo
|
||||||
return decode.replace('BEGIN:VALARM\r\nACTION:NONE','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:') \
|
begin_utc = event.begin_datetime.astimezone(timezone.utc)
|
||||||
.replace('BEGIN:VALARM\r\nACTION:EMAIL','BEGIN:VALARM\r\nACTION:DISPLAY\r\nDESCRIPTION:')
|
end_utc = event.end_datetime.astimezone(timezone.utc)
|
||||||
|
|
||||||
|
event.begin_datetime = datetime(
|
||||||
|
begin_utc.year, begin_utc.month, begin_utc.day, 0, 0, 0, 0, local_tzinfo)
|
||||||
|
event.end_datetime = datetime(
|
||||||
|
end_utc.year, end_utc.month, end_utc.day, 0, 0, 0, 0, local_tzinfo) - timedelta(1)
|
||||||
|
event.duration = event.end_datetime - event.begin_datetime
|
||||||
|
|
||||||
|
return event
|
||||||
|
|
||||||
def __remove_alarms__(self, decode):
|
def __remove_alarms__(self, decode):
|
||||||
alarm_begin = 'BEGIN:VALARM'
|
alarm_begin = 'BEGIN:VALARM'
|
||||||
|
@ -71,5 +102,21 @@ class IcalEvents(CalendarInterface):
|
||||||
beginAlarmIndex = decode.find(alarm_begin)
|
beginAlarmIndex = decode.find(alarm_begin)
|
||||||
if beginAlarmIndex >= 0:
|
if beginAlarmIndex >= 0:
|
||||||
endAlarmIndex = decode.find(alarm_end, beginAlarmIndex)
|
endAlarmIndex = decode.find(alarm_end, beginAlarmIndex)
|
||||||
decode = decode[:beginAlarmIndex] + decode[endAlarmIndex + len(alarm_end) + len(lineseparation):]
|
decode = decode[:beginAlarmIndex] + \
|
||||||
return decode
|
decode[endAlarmIndex +
|
||||||
|
len(alarm_end) + len(lineseparation):]
|
||||||
|
return decode
|
||||||
|
|
||||||
|
def __extract_rrule__(self, event):
|
||||||
|
if re.search('RRULE', str(event)) is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return re.search('RRULE:(.+?)\n', str(event)).group(1).rstrip()
|
||||||
|
|
||||||
|
def __is_multiday__(self, event):
|
||||||
|
if event.allday and event.duration == timedelta(1):
|
||||||
|
return False
|
||||||
|
|
||||||
|
return event.begin_datetime.day != event.end_datetime.day or \
|
||||||
|
event.begin_datetime.month != event.end_datetime.month or \
|
||||||
|
event.begin_datetime.year != event.end_datetime.year
|
||||||
|
|
86
Calendar/ImageDesign.py
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from Assets import path as application_path
|
||||||
|
from PIL import Image, ExifTags
|
||||||
|
|
||||||
|
|
||||||
|
class ImageDesign (DesignEntity):
|
||||||
|
"""Creates a TableDesign filled with rss post
|
||||||
|
date and title"""
|
||||||
|
|
||||||
|
# fill: "none" : original size, "stretch" : strech to fill, "scale" : scale to fill, "border" : scale until one side touches border
|
||||||
|
def __init__(self, size, path, fill="none", color="RGBA", dither=None):
|
||||||
|
super(ImageDesign, self).__init__(size)
|
||||||
|
self.set_path(path)
|
||||||
|
self.fill = fill
|
||||||
|
self.color = color
|
||||||
|
self.dither = dither
|
||||||
|
|
||||||
|
def set_path(self, path):
|
||||||
|
path = path.replace('\\', '/')
|
||||||
|
if path[0] != '/' and ':' not in path[0:3]:
|
||||||
|
path = application_path + '/' + path
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
img = Image.open(self.path)
|
||||||
|
img = img.convert(self.color, dither=self.dither)
|
||||||
|
|
||||||
|
img = self.__fix_orientation__(img)
|
||||||
|
img = self.__resize_image__(img)
|
||||||
|
pos = self.__get_centered_position__(img)
|
||||||
|
|
||||||
|
self.__init_image__("#00000000")
|
||||||
|
self.draw(img, pos)
|
||||||
|
|
||||||
|
def __resize_image__(self, img):
|
||||||
|
if self.fill is "none":
|
||||||
|
return img
|
||||||
|
|
||||||
|
if self.fill is "stretch":
|
||||||
|
img = img.resize(self.size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
if self.fill is "scale":
|
||||||
|
size = self.size
|
||||||
|
img_proportions = img.width / img.height
|
||||||
|
if img_proportions < size[0] / size[1]:
|
||||||
|
size = (size[0], int(size[0] * (1 / img_proportions)))
|
||||||
|
else:
|
||||||
|
size = (int(size[1] * img_proportions), size[1])
|
||||||
|
img = img.resize(size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
if self.fill is "border":
|
||||||
|
size = self.size
|
||||||
|
img_proportions = img.width / img.height
|
||||||
|
if img_proportions < size[0] / size[1]:
|
||||||
|
size = (int(size[1] * img_proportions), size[1])
|
||||||
|
else:
|
||||||
|
size = (size[0], int(size[0] * (1 / img_proportions)))
|
||||||
|
img = img.resize(size, resample=Image.LANCZOS)
|
||||||
|
|
||||||
|
return img
|
||||||
|
|
||||||
|
def __get_centered_position__(self, img):
|
||||||
|
screen_size = self.size
|
||||||
|
img_size = img.size
|
||||||
|
|
||||||
|
delta_size = [s - i for s, i in zip(screen_size, img_size)]
|
||||||
|
delta_center_pos = [s / 2 for s in delta_size]
|
||||||
|
|
||||||
|
return delta_center_pos
|
||||||
|
|
||||||
|
def __fix_orientation__(self, img):
|
||||||
|
if "parsed_exif" not in img.info.keys():
|
||||||
|
return img
|
||||||
|
|
||||||
|
for orientation in ExifTags.TAGS.keys():
|
||||||
|
if ExifTags.TAGS[orientation] == 'Orientation':
|
||||||
|
break
|
||||||
|
exif = img.info["parsed_exif"]
|
||||||
|
|
||||||
|
if exif[orientation] == 3:
|
||||||
|
img = img.rotate(180, expand=True)
|
||||||
|
elif exif[orientation] == 6:
|
||||||
|
img = img.rotate(270, expand=True)
|
||||||
|
elif exif[orientation] == 8:
|
||||||
|
img = img.rotate(90, expand=True)
|
||||||
|
return img
|
|
@ -1,13 +1,18 @@
|
||||||
from DisplayAdapter import DisplayAdapter
|
from DisplayAdapter import DisplayAdapter
|
||||||
|
from Assets import path
|
||||||
|
|
||||||
|
|
||||||
class ImageFileAdapter (DisplayAdapter):
|
class ImageFileAdapter (DisplayAdapter):
|
||||||
"""Saves design to an image file, can be used for debugging"""
|
"""Saves design to an image file, can be used for debugging"""
|
||||||
def __init__ (self, file_path = ""):
|
|
||||||
|
def __init__(self, file_path=""):
|
||||||
super(ImageFileAdapter, self).__init__(384, 640)
|
super(ImageFileAdapter, self).__init__(384, 640)
|
||||||
self.file_path = file_path
|
self.file_path = file_path
|
||||||
|
if self.file_path == "":
|
||||||
|
self.file_path = path
|
||||||
|
|
||||||
def render (self, design):
|
def render(self, design):
|
||||||
design.get_image().save(self.file_path + 'design_exported.png')
|
design.get_image().save(self.file_path + 'design_exported.png')
|
||||||
|
|
||||||
def calibrate (self):
|
def calibrate(self):
|
||||||
pass
|
pass
|
||||||
|
|
46
Calendar/ImageFramePanel.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from settings import general_settings
|
||||||
|
from Assets import supported_img_formats, path as application_path
|
||||||
|
from os import listdir
|
||||||
|
from os.path import isfile, join
|
||||||
|
from ImageDesign import ImageDesign
|
||||||
|
from random import choice
|
||||||
|
|
||||||
|
|
||||||
|
class ImageFramePanel (PanelDesign):
|
||||||
|
"""Converts the display into a digital frame and
|
||||||
|
shows a slide show of images, iterating on each update"""
|
||||||
|
|
||||||
|
def __init__(self, size):
|
||||||
|
super(ImageFramePanel, self).__init__(size)
|
||||||
|
self.overlay_path = self.__complete_path__(
|
||||||
|
general_settings["overlay-image"])
|
||||||
|
self.image_folder_path = self.__complete_path__(
|
||||||
|
general_settings["image-folder"])
|
||||||
|
self.images = self.__extract_valid_img_paths__()
|
||||||
|
self.__first_render__()
|
||||||
|
|
||||||
|
def __extract_valid_img_paths__(self):
|
||||||
|
images = []
|
||||||
|
for file in listdir(self.image_folder_path):
|
||||||
|
file_path = join(self.image_folder_path, file).replace('\\', '/')
|
||||||
|
if isfile(file_path) and self.overlay_path != file_path:
|
||||||
|
if file.split('.')[-1].upper() in supported_img_formats:
|
||||||
|
images.append(file_path)
|
||||||
|
return images
|
||||||
|
|
||||||
|
def __complete_path__(self, path):
|
||||||
|
path = path.replace('\\', '/')
|
||||||
|
if path[0] != '/' and ':' not in path[0:3]:
|
||||||
|
path = join(application_path, path)
|
||||||
|
return path
|
||||||
|
|
||||||
|
def __first_render__(self):
|
||||||
|
current_image = choice(self.images)
|
||||||
|
img = ImageDesign(self.size, current_image, fill="scale", color="1")
|
||||||
|
self.draw_design(img)
|
||||||
|
|
||||||
|
if self.overlay_path != "":
|
||||||
|
overlay = ImageDesign(self.size, self.overlay_path)
|
||||||
|
overlay.__finish_image__()
|
||||||
|
self.__image__.alpha_composite(overlay.__image__)
|
|
@ -1,22 +1,33 @@
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
|
||||||
class LoopTimer(object):
|
min_sleep_minutes = 0
|
||||||
|
max_history_entries = 25
|
||||||
|
|
||||||
|
|
||||||
|
class LoopTimer (object):
|
||||||
"""Manages loop times and sleeps until
|
"""Manages loop times and sleeps until
|
||||||
next loop."""
|
next loop."""
|
||||||
def __init__(self, loop_interval, run_on_hour=False):
|
|
||||||
self.interval = loop_interval
|
def __init__(self, loop_interval, run_on_hour=False, max_loop_count=0):
|
||||||
|
self.interval = int(str(loop_interval))
|
||||||
self.on_hour = run_on_hour
|
self.on_hour = run_on_hour
|
||||||
self.loop_history = []
|
self.loop_history = []
|
||||||
|
self.loop_count = 0
|
||||||
|
self.max_loop_count = int(str(max_loop_count))
|
||||||
|
|
||||||
def begin_loop(self):
|
def begin_loop(self):
|
||||||
begin_time = datetime.now()
|
begin_time = datetime.now()
|
||||||
print('\n_________Starting new loop___________')
|
print('\n__________Starting new loop [' + str(self.loop_count) + ']__________')
|
||||||
print('Date: ' + begin_time.strftime('%a %d %b %y') + ', time: ' + begin_time.strftime('%H:%M') + '\n')
|
print('Datetime: ' + str(begin_time) + '\n')
|
||||||
self.__add_beginning__(begin_time)
|
self.__add_beginning__(begin_time)
|
||||||
|
|
||||||
def __add_beginning__(self, time):
|
def __add_beginning__(self, time):
|
||||||
self.loop_history.append((time,))
|
self.loop_history.append((time,))
|
||||||
|
|
||||||
|
if len(self.loop_history) > max_history_entries:
|
||||||
|
dif = len(self.loop_history) - max_history_entries
|
||||||
|
self.loop_history = self.loop_history[dif:]
|
||||||
|
|
||||||
def __add_ending__(self, time):
|
def __add_ending__(self, time):
|
||||||
current = self.get_current()
|
current = self.get_current()
|
||||||
self.loop_history[-1] = (current[0], time)
|
self.loop_history[-1] = (current[0], time)
|
||||||
|
@ -24,6 +35,15 @@ class LoopTimer(object):
|
||||||
def end_loop(self):
|
def end_loop(self):
|
||||||
end_time = datetime.now()
|
end_time = datetime.now()
|
||||||
self.__add_ending__(end_time)
|
self.__add_ending__(end_time)
|
||||||
|
|
||||||
|
self.loop_count += 1
|
||||||
|
while self.loop_count > 86400:
|
||||||
|
self.loop_count -= 86400
|
||||||
|
|
||||||
|
def was_last_loop(self):
|
||||||
|
if self.max_loop_count == 0:
|
||||||
|
return False
|
||||||
|
return self.max_loop_count <= self.loop_count
|
||||||
|
|
||||||
def get_current(self):
|
def get_current(self):
|
||||||
return self.loop_history[-1]
|
return self.loop_history[-1]
|
||||||
|
@ -36,6 +56,9 @@ class LoopTimer(object):
|
||||||
time_until_hour = self.get_time_to_next_hour()
|
time_until_hour = self.get_time_to_next_hour()
|
||||||
if time_until_hour < sleep_time:
|
if time_until_hour < sleep_time:
|
||||||
return time_until_hour
|
return time_until_hour
|
||||||
|
|
||||||
|
if sleep_time < timedelta(0):
|
||||||
|
sleep_time = timedelta(0, 0, 0, 0, min_sleep_minutes)
|
||||||
return sleep_time
|
return sleep_time
|
||||||
|
|
||||||
def get_last_duration(self):
|
def get_last_duration(self):
|
||||||
|
@ -48,7 +71,7 @@ class LoopTimer(object):
|
||||||
cur = datetime.now()
|
cur = datetime.now()
|
||||||
rounded = datetime(cur.year, cur.month, cur.day, cur.hour)
|
rounded = datetime(cur.year, cur.month, cur.day, cur.hour)
|
||||||
next_hour_time = rounded + timedelta(hours=1)
|
next_hour_time = rounded + timedelta(hours=1)
|
||||||
return next_hour_time - datetime.now()
|
return next_hour_time - datetime.now()
|
||||||
|
|
||||||
def is_new_hour_loop(self):
|
def is_new_hour_loop(self):
|
||||||
if len(self.loop_history) < 2:
|
if len(self.loop_history) < 2:
|
||||||
|
@ -59,4 +82,4 @@ class LoopTimer(object):
|
||||||
if previous_loop[0].hour != current_loop[0].hour:
|
if previous_loop[0].hour != current_loop[0].hour:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -2,18 +2,21 @@ from DesignEntity import DesignEntity
|
||||||
import calendar as callib
|
import calendar as callib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from Assets import *
|
from Assets import colors
|
||||||
from settings import *
|
from settings import week_starts_on
|
||||||
from BoxDesign import BoxDesign
|
from BoxDesign import BoxDesign
|
||||||
|
|
||||||
daynumberboxsize = (0.143, 0.286)
|
daynumberboxsize = (0.143, 0.2)
|
||||||
dayhighlightboxsize = (0.143, 0.14)
|
dayhighlightboxsize = (0.143, 0.14)
|
||||||
daynumbersize = 25
|
daynumbersize = daynumberboxsize[0] * 0.45
|
||||||
|
day_number_ypadding = -0.002
|
||||||
|
|
||||||
|
|
||||||
class MonthBlockDesign (DesignEntity):
|
class MonthBlockDesign (DesignEntity):
|
||||||
"""Creates a view containing one week of the month in
|
"""Creates a view containing one week of the month in
|
||||||
one row"""
|
one row"""
|
||||||
def __init__(self, size, datetime_month, highlight_today = False):
|
|
||||||
|
def __init__(self, size, datetime_month, highlight_today=False):
|
||||||
super(MonthBlockDesign, self).__init__(size, mask=True)
|
super(MonthBlockDesign, self).__init__(size, mask=True)
|
||||||
self.month = datetime_month.month
|
self.month = datetime_month.month
|
||||||
self.year = datetime_month.year
|
self.year = datetime_month.year
|
||||||
|
@ -23,49 +26,53 @@ class MonthBlockDesign (DesignEntity):
|
||||||
def __finish_image__(self):
|
def __finish_image__(self):
|
||||||
self.__draw_month_overview__()
|
self.__draw_month_overview__()
|
||||||
|
|
||||||
def __draw_month_overview__ (self):
|
def __draw_month_overview__(self):
|
||||||
"""Using the built-in calendar function, draw icons for each
|
"""Using the built-in calendar function, draw icons for each
|
||||||
number of the month (1,2,3,...29,30,31)"""
|
number of the month (1,2,3,...29,30,31)"""
|
||||||
cal = callib.monthcalendar(self.year, self.month)
|
cal = callib.monthcalendar(self.year, self.month)
|
||||||
for week in cal:
|
for week in cal:
|
||||||
for numbers in week:
|
for numbers in week:
|
||||||
self.__draw_day_number__(numbers, self.get_day_pos(cal.index(week), week.index(numbers)))
|
self.__draw_day_number__(numbers, self.get_day_pos(
|
||||||
|
cal.index(week), week.index(numbers)))
|
||||||
if self.highlight_today:
|
|
||||||
self.__draw_highlight_box__(self.__abs_pos__(dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
|
|
||||||
|
|
||||||
def __draw_highlight_box__ (self, size, pos, color='black', width=1):
|
if self.highlight_today:
|
||||||
design = BoxDesign(size, outline=color, width = width)
|
self.__draw_highlight_box__(self.__abs_pos__(
|
||||||
|
dayhighlightboxsize), self.__get_today_box_pos__(), width=3)
|
||||||
|
|
||||||
|
def __draw_highlight_box__(self, size, pos, color=colors["fg"], width=1):
|
||||||
|
design = BoxDesign(size, outline=color, width=width)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def __draw_day_number__ (self, number, pos):
|
def __draw_day_number__(self, number, pos):
|
||||||
if number <= 0:
|
if number <= 0:
|
||||||
return
|
return
|
||||||
txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize, text=str(number), verticalalignment="center", horizontalalignment="center")
|
txt = TextDesign(self.__abs_pos__(daynumberboxsize), fontsize=daynumbersize *
|
||||||
txt.pos = pos
|
self.size[0], text=str(number), verticalalignment="center", horizontalalignment="center")
|
||||||
|
txt.pos = (pos[0], pos[1] + day_number_ypadding * self.size[1])
|
||||||
self.draw_design(txt)
|
self.draw_design(txt)
|
||||||
|
|
||||||
def get_day_pos (self, week_in_month, day_of_week, rel_pos=(0,0)):
|
def get_day_pos(self, week_in_month, day_of_week, rel_pos=(0, 0)):
|
||||||
maxwidth, maxheight = self.size
|
maxwidth, maxheight = self.size
|
||||||
partialwidth = maxwidth / 7
|
partialwidth = maxwidth / 7
|
||||||
partialheight = maxheight / 5
|
partialheight = maxheight / 5
|
||||||
return (int(rel_pos[0] + day_of_week * partialwidth), int(rel_pos[1] + week_in_month * partialheight))
|
return (int(rel_pos[0] + day_of_week * partialwidth), int(rel_pos[1] + week_in_month * partialheight))
|
||||||
|
|
||||||
def __get_today_box_pos__ (self):
|
def __get_today_box_pos__(self):
|
||||||
x, y = self.get_day_pos(self.__get_week_of_month__(datetime.now()), self.__get_day_of_week__(datetime.now()))
|
x, y = self.get_day_pos(self.__get_week_of_month__(
|
||||||
|
datetime.now()), self.__get_day_of_week__(datetime.now()))
|
||||||
return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2))
|
return (x, int(y + (self.__abs_pos__(daynumberboxsize)[1] - self.__abs_pos__(dayhighlightboxsize)[1]) / 2))
|
||||||
|
|
||||||
def __get_week_of_month__ (self, date):
|
def __get_week_of_month__(self, date):
|
||||||
for wof, week in enumerate(callib.monthcalendar(date.year, date.month)):
|
for wof, week in enumerate(callib.monthcalendar(date.year, date.month)):
|
||||||
if date.day in week:
|
if date.day in week:
|
||||||
return wof
|
return wof
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def __get_day_of_week__ (self, date):
|
def __get_day_of_week__(self, date):
|
||||||
return self.__week_days__.index(date.strftime("%a"))
|
return self.__week_days__.index(date.strftime("%a"))
|
||||||
|
|
||||||
def __get_week_days_ordered__ (self):
|
def __get_week_days_ordered__(self):
|
||||||
cur_weekday = datetime.now().weekday()
|
cur_weekday = datetime.now().weekday()
|
||||||
correction = -cur_weekday
|
correction = -cur_weekday
|
||||||
if week_starts_on == "Sunday":
|
if week_starts_on == "Sunday":
|
||||||
|
@ -73,17 +80,18 @@ class MonthBlockDesign (DesignEntity):
|
||||||
|
|
||||||
weekdays = []
|
weekdays = []
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
|
weekdays.append(
|
||||||
|
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
|
||||||
|
|
||||||
return weekdays
|
return weekdays
|
||||||
|
|
||||||
def __abs_pos__ (self, pos, size = None):
|
def __abs_pos__(self, pos, size=None):
|
||||||
if size is None:
|
if size is None:
|
||||||
size = self.size
|
size = self.size
|
||||||
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
||||||
|
|
||||||
def get_real_height (self):
|
def get_real_height(self):
|
||||||
weeks_in_month = callib.monthcalendar(self.year, self.month)
|
weeks_in_month = callib.monthcalendar(self.year, self.month)
|
||||||
num_size = self.__abs_pos__(daynumberboxsize)
|
num_size = self.__abs_pos__(daynumberboxsize)
|
||||||
num_pos = self.get_day_pos(len(weeks_in_month) - 1, 6)
|
num_pos = self.get_day_pos(len(weeks_in_month) - 1, 6)
|
||||||
return num_size[1] + num_pos[1]
|
return num_size[1] + num_pos[1]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
from PanelDesign import PanelDesign
|
from PanelDesign import PanelDesign
|
||||||
from Assets import *
|
from Assets import colors
|
||||||
from settings import *
|
from settings import general_settings, week_starts_on
|
||||||
import calendar as callib
|
import calendar as callib
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from WeatherHeaderDesign import WeatherHeaderDesign
|
from WeatherHeaderDesign import WeatherHeaderDesign
|
||||||
|
@ -11,31 +11,37 @@ from EllipseDesign import EllipseDesign
|
||||||
from MonthBlockDesign import MonthBlockDesign, daynumberboxsize
|
from MonthBlockDesign import MonthBlockDesign, daynumberboxsize
|
||||||
from EventListDesign import EventListDesign
|
from EventListDesign import EventListDesign
|
||||||
from RssPostListDesign import RssPostListDesign
|
from RssPostListDesign import RssPostListDesign
|
||||||
|
from settings import general_settings
|
||||||
|
from CryptoListDesign import CryptoListDesign
|
||||||
|
|
||||||
monthtextsize = 40
|
|
||||||
monthovsize = (1, 0.5)
|
weatherheadersize = (1, 0.113)
|
||||||
monthovposition = (0, 0.23)
|
|
||||||
weatherheadersize = (1,0.113)
|
|
||||||
seperatorplace = (0, 0.113)
|
|
||||||
monthplace = (0, 0.12)
|
|
||||||
monthboxsize = (1, 0.085)
|
monthboxsize = (1, 0.085)
|
||||||
weekdayrowpos = (0, 0.209)
|
monthtextsize = monthboxsize[1] * 0.75
|
||||||
|
monthplace = (0, 0.11 - weatherheadersize[1])
|
||||||
|
monthovsize = (1, 0.48)
|
||||||
|
monthovposition = (0, 0.25 - weatherheadersize[1])
|
||||||
|
seperatorplace = (0, 0.113)
|
||||||
|
weekdayrowpos = (0, 0.209 - weatherheadersize[1])
|
||||||
weekrowboxsize = (1, 0.044)
|
weekrowboxsize = (1, 0.044)
|
||||||
weekdaytextsize = 18
|
weekdaytextsize = 0.7 * weekrowboxsize[1]
|
||||||
|
weekdaytextpadding = -0.001
|
||||||
weekrownameboxsize = (0.143, 0.044)
|
weekrownameboxsize = (0.143, 0.044)
|
||||||
eventcirclehorizontalsize = 0.100
|
eventcirclehorizontalsize = 0.100
|
||||||
infolisthorizontalpos = 0.008
|
|
||||||
infolistsize = (1, 0.77)
|
|
||||||
infolisttextsizeize = 16
|
|
||||||
|
|
||||||
class MonthOvPanel (PanelDesign):
|
class MonthOvPanel (PanelDesign):
|
||||||
"""Overview that focuses on the current month and
|
"""Overview that focuses on the current month and
|
||||||
some additional information in the bottom."""
|
some additional information in the bottom."""
|
||||||
def __init__ (self, size):
|
|
||||||
|
def __init__(self, size):
|
||||||
super(MonthOvPanel, self).__init__(size)
|
super(MonthOvPanel, self).__init__(size)
|
||||||
|
self.weather_header_height = 0
|
||||||
|
if general_settings["weather-info"]:
|
||||||
|
self.weather_header_height = self.size[1] * weatherheadersize[1]
|
||||||
self.__first_render__()
|
self.__first_render__()
|
||||||
|
|
||||||
def __first_render__ (self):
|
def __first_render__(self):
|
||||||
if week_starts_on == "Monday":
|
if week_starts_on == "Monday":
|
||||||
callib.setfirstweekday(callib.MONDAY)
|
callib.setfirstweekday(callib.MONDAY)
|
||||||
elif week_starts_on == "Sunday":
|
elif week_starts_on == "Sunday":
|
||||||
|
@ -43,99 +49,140 @@ class MonthOvPanel (PanelDesign):
|
||||||
self.__week_days__ = self.__get_week_days_ordered__()
|
self.__week_days__ = self.__get_week_days_ordered__()
|
||||||
|
|
||||||
self.__draw_month_name__()
|
self.__draw_month_name__()
|
||||||
self.__draw_seperator__()
|
|
||||||
self.__draw_week_row__()
|
self.__draw_week_row__()
|
||||||
|
|
||||||
self.month_block = MonthBlockDesign(self.__abs_pos__(monthovsize), datetime.now(), highlight_today = True)
|
if general_settings["weather-info"]:
|
||||||
self.month_block.pos = self.__abs_pos__(monthovposition)
|
self.__draw_seperator__()
|
||||||
|
|
||||||
|
self.month_block = MonthBlockDesign(self.__abs_pos__(
|
||||||
|
monthovsize), datetime.now(), highlight_today=True)
|
||||||
|
pos = self.__abs_pos__(monthovposition)
|
||||||
|
pos = (pos[0], pos[1] + self.weather_header_height)
|
||||||
|
self.month_block.pos = pos
|
||||||
self.draw_design(self.month_block)
|
self.draw_design(self.month_block)
|
||||||
|
|
||||||
def add_weather (self, weather):
|
def add_weather(self, weather):
|
||||||
self.draw_design(WeatherHeaderDesign(self.__abs_pos__(weatherheadersize), weather))
|
if general_settings["weather-info"] == False:
|
||||||
|
return
|
||||||
|
self.draw_design(WeatherHeaderDesign(
|
||||||
|
self.__abs_pos__(weatherheadersize), weather))
|
||||||
|
|
||||||
def add_rssfeed (self, rss):
|
def add_rssfeed(self, rss):
|
||||||
if general_settings["info-area"] is "rss":
|
if general_settings["info-area"] is "rss":
|
||||||
self.__draw_rss_post_list_to_bottom__(rss)
|
self.__draw_rss_post_list_to_bottom__(rss)
|
||||||
|
|
||||||
def add_calendar (self, calendar):
|
def add_crypto(self, crypto):
|
||||||
|
if general_settings["info-area"] is "crypto":
|
||||||
|
self.__draw_crypto_post_list_to_bottom__(crypto)
|
||||||
|
|
||||||
|
def add_tasks(self, tasks):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
if general_settings["highlight-event-days"]:
|
if general_settings["highlight-event-days"]:
|
||||||
month_events = list(set([ (event.begin_datetime.day, event.begin_datetime.month, event.begin_datetime.year) for event in calendar.get_month_events()]))
|
month_events = list(set([(event.begin_datetime.day, event.begin_datetime.month,
|
||||||
|
event.begin_datetime.year) for event in calendar.get_month_events()]))
|
||||||
for event in month_events:
|
for event in month_events:
|
||||||
self.__draw_highlight_event_day__(event)
|
self.__draw_highlight_event_day__(event)
|
||||||
|
|
||||||
if general_settings["info-area"] is "events":
|
if general_settings["info-area"] is "events":
|
||||||
self.__draw_event_list_to_bottom__(calendar)
|
self.__draw_event_list_to_bottom__(calendar)
|
||||||
|
|
||||||
def __draw_rss_post_list_to_bottom__ (self, rss):
|
def __draw_rss_post_list_to_bottom__(self, rss):
|
||||||
month_pos = self.__abs_pos__(monthovposition)
|
month_pos = self.__abs_pos__(monthovposition)
|
||||||
month_height = self.month_block.get_real_height()
|
month_height = self.month_block.get_real_height()
|
||||||
size = self.__abs_pos__(infolistsize)
|
size = (self.size[0], self.size[1] - (month_pos[1] +
|
||||||
size = (size[0], size[1] - month_height)
|
month_height + self.weather_header_height))
|
||||||
info_list = RssPostListDesign(size, rss, text_size=infolisttextsizeize)
|
info_list = RssPostListDesign(size, rss)
|
||||||
info_list.pos = (int(month_pos[0] + infolisthorizontalpos * self.size[0]), int(month_pos[1] + month_height))
|
info_list.pos = (
|
||||||
|
int(month_pos[0]), month_pos[1] + month_height + self.weather_header_height)
|
||||||
self.draw_design(info_list)
|
self.draw_design(info_list)
|
||||||
|
|
||||||
def __draw_event_list_to_bottom__ (self, calendar):
|
def __draw_crypto_post_list_to_bottom__(self, crypto):
|
||||||
month_pos = self.__abs_pos__(monthovposition)
|
month_pos = self.__abs_pos__(monthovposition)
|
||||||
month_height = self.month_block.get_real_height()
|
month_height = self.month_block.get_real_height()
|
||||||
size = self.__abs_pos__(infolistsize)
|
size = (self.size[0], self.size[1] - (month_pos[1] +
|
||||||
size = (size[0], size[1] - month_height)
|
month_height + self.weather_header_height))
|
||||||
info_list = EventListDesign(size, calendar, text_size=infolisttextsizeize)
|
|
||||||
info_list.pos = (int(month_pos[0] + infolisthorizontalpos * self.size[0]), int(month_pos[1] + month_height))
|
info_list = CryptoListDesign(size, crypto)
|
||||||
|
list_height = info_list.get_estimated_height()
|
||||||
|
info_list.pos = (int(month_pos[0]), month_pos[1] + month_height +
|
||||||
|
self.weather_header_height + (size[1] - list_height))
|
||||||
self.draw_design(info_list)
|
self.draw_design(info_list)
|
||||||
|
|
||||||
def __draw_highlight_event_day__ (self, date):
|
def __draw_event_list_to_bottom__(self, calendar):
|
||||||
|
month_pos = self.__abs_pos__(monthovposition)
|
||||||
|
month_height = self.month_block.get_real_height()
|
||||||
|
size = (self.size[0], self.size[1] - (month_pos[1] +
|
||||||
|
month_height + self.weather_header_height))
|
||||||
|
|
||||||
|
events = calendar.get_upcoming_events()
|
||||||
|
info_list = EventListDesign(size, events)
|
||||||
|
info_list.pos = (int(month_pos[0]), int(
|
||||||
|
month_pos[1] + month_height + self.weather_header_height))
|
||||||
|
self.draw_design(info_list)
|
||||||
|
|
||||||
|
def __draw_highlight_event_day__(self, date):
|
||||||
first_month_week = datetime(date[2], date[1], 1).isocalendar()[1]
|
first_month_week = datetime(date[2], date[1], 1).isocalendar()[1]
|
||||||
cur_date = datetime(date[2], date[1], date[0])
|
cur_date = datetime(date[2], date[1], date[0])
|
||||||
|
|
||||||
side_length = int(eventcirclehorizontalsize * self.size[0])
|
side_length = int(eventcirclehorizontalsize * self.size[0])
|
||||||
circle_size = (side_length,side_length)
|
circle_size = (side_length, side_length)
|
||||||
pos = self.month_block.get_day_pos(cur_date.isocalendar()[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos = self.__abs_pos__(monthovposition))
|
pos = self.month_block.get_day_pos(cur_date.isocalendar(
|
||||||
place_size = (self.month_block.size[0] * daynumberboxsize[0], self.month_block.size[1] * daynumberboxsize[1])
|
)[1] - first_month_week, self.__get_day_of_week__(cur_date), rel_pos=self.month_block.pos)
|
||||||
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2), int(pos[1] + (place_size[1] - circle_size[1]) / 2))
|
place_size = (self.month_block.size[0] * daynumberboxsize[0],
|
||||||
|
self.month_block.size[1] * daynumberboxsize[1])
|
||||||
|
pos = (int(pos[0] + (place_size[0] - circle_size[0]) / 2),
|
||||||
|
int(pos[1] + (place_size[1] - circle_size[1]) / 2))
|
||||||
self.__draw_highlight_circle__(circle_size, pos, 'red', width=2)
|
self.__draw_highlight_circle__(circle_size, pos, 'red', width=2)
|
||||||
|
|
||||||
def __abs_pos__ (self, pos, size = None):
|
def __abs_pos__(self, pos, size=None):
|
||||||
if size is None:
|
if size is None:
|
||||||
size = self.size
|
size = self.size
|
||||||
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
return (int(pos[0] * size[0]), int(pos[1] * size[1]))
|
||||||
|
|
||||||
def __draw_seperator__ (self):
|
def __draw_seperator__(self):
|
||||||
"""Draw a line seperating the weather and Calendar section"""
|
"""Draw a line seperating the weather and Calendar section"""
|
||||||
ImageDraw.Draw(self.__image__).line([ self.__abs_pos__(seperatorplace), self.__abs_pos__((1, seperatorplace[1])) ], fill='red', width=5)
|
ImageDraw.Draw(self.__image__).line([self.__abs_pos__(
|
||||||
|
seperatorplace), self.__abs_pos__((1, seperatorplace[1]))], fill='red', width=5)
|
||||||
|
|
||||||
def __draw_month_name__ (self):
|
def __draw_month_name__(self):
|
||||||
"""Draw the icon with the current month's name"""
|
"""Draw the icon with the current month's name"""
|
||||||
month = datetime.now().strftime("%B")
|
month = datetime.now().strftime("%B")
|
||||||
txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize, text=month, verticalalignment="center", horizontalalignment="center")
|
txt = TextDesign(self.__abs_pos__(monthboxsize), fontsize=monthtextsize *
|
||||||
txt.pos = self.__abs_pos__(monthplace)
|
self.size[1], text=month, verticalalignment="center", horizontalalignment="center")
|
||||||
|
pos = self.__abs_pos__(monthplace)
|
||||||
|
txt.pos = (pos[0], pos[1] + self.weather_header_height)
|
||||||
self.draw_design(txt)
|
self.draw_design(txt)
|
||||||
|
|
||||||
def __draw_week_row__ (self):
|
def __draw_week_row__(self):
|
||||||
for day_of_week, day in enumerate(self.__week_days__):
|
for day_of_week, day in enumerate(self.__week_days__):
|
||||||
txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize, text=str(day), verticalalignment="center", horizontalalignment="center")
|
txt = TextDesign(self.__abs_pos__(weekrownameboxsize), fontsize=weekdaytextsize *
|
||||||
txt.pos = self.__get_week_day_pos__(day_of_week)
|
self.size[1], text=str(day), verticalalignment="center", horizontalalignment="center")
|
||||||
|
pos = self.__get_week_day_pos__(day_of_week)
|
||||||
|
txt.pos = (pos[0], pos[1] + weekdaytextpadding * self.size[1])
|
||||||
self.draw_design(txt)
|
self.draw_design(txt)
|
||||||
|
|
||||||
self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(self.__get_day_of_week__(datetime.now())), width=1)
|
|
||||||
|
|
||||||
def __get_week_day_pos__ (self, day_of_week):
|
self.__draw_highlight_box__(self.__abs_pos__(weekrownameboxsize), self.__get_week_day_pos__(
|
||||||
|
self.__get_day_of_week__(datetime.now())), width=1)
|
||||||
|
|
||||||
|
def __get_week_day_pos__(self, day_of_week):
|
||||||
maxwidth, _ = self.__abs_pos__(monthovsize)
|
maxwidth, _ = self.__abs_pos__(monthovsize)
|
||||||
partialwidth = maxwidth / 7
|
partialwidth = maxwidth / 7
|
||||||
posx, posy = self.__abs_pos__(weekdayrowpos)
|
posx, posy = self.__abs_pos__(weekdayrowpos)
|
||||||
return (int(posx + day_of_week * partialwidth), int(posy))
|
return (int(posx + day_of_week * partialwidth), int(posy + self.weather_header_height))
|
||||||
|
|
||||||
def __draw_highlight_box__ (self, size, pos, color = 'black', width = 1):
|
def __draw_highlight_box__(self, size, pos, color=colors["fg"], width=1):
|
||||||
design = BoxDesign(size, outline=color, width = width)
|
design = BoxDesign(size, outline=color, width=width)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def __draw_highlight_circle__ (self, size, pos, color = 'black', width = 1):
|
def __draw_highlight_circle__(self, size, pos, color=colors["fg"], width=1):
|
||||||
design = EllipseDesign(size, outline=color, width = width)
|
design = EllipseDesign(size, outline=color, width=width)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def __get_week_days_ordered__ (self):
|
def __get_week_days_ordered__(self):
|
||||||
cur_weekday = datetime.now().weekday()
|
cur_weekday = datetime.now().weekday()
|
||||||
correction = -cur_weekday
|
correction = -cur_weekday
|
||||||
if week_starts_on == "Sunday":
|
if week_starts_on == "Sunday":
|
||||||
|
@ -143,9 +190,10 @@ class MonthOvPanel (PanelDesign):
|
||||||
|
|
||||||
weekdays = []
|
weekdays = []
|
||||||
for i in range(7):
|
for i in range(7):
|
||||||
weekdays.append((datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
|
weekdays.append(
|
||||||
|
(datetime.now() + timedelta(days=(i + correction))).strftime("%a"))
|
||||||
|
|
||||||
return weekdays
|
return weekdays
|
||||||
|
|
||||||
def __get_day_of_week__ (self, date):
|
def __get_day_of_week__(self, date):
|
||||||
return self.__week_days__.index(date.strftime("%a"))
|
return self.__week_days__.index(date.strftime("%a"))
|
||||||
|
|
133
Calendar/MonthViewPanel.py
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
from PanelDesign import PanelDesign
|
||||||
|
from settings import general_settings, week_starts_on, line_thickness
|
||||||
|
from PIL import ImageDraw
|
||||||
|
from datetime import date
|
||||||
|
from Assets import colors
|
||||||
|
import calendar as callib
|
||||||
|
from TableDesign import TableDesign
|
||||||
|
from DayBoxDesign import DayBoxDesign
|
||||||
|
from RssPostListDesign import RssPostListDesign
|
||||||
|
from WeatherHeaderDesign import WeatherHeaderDesign
|
||||||
|
from CryptoListDesign import CryptoListDesign
|
||||||
|
|
||||||
|
weather_height = 0.113
|
||||||
|
info_height = 0.25
|
||||||
|
info_padding = 5
|
||||||
|
seperator_width = line_thickness
|
||||||
|
|
||||||
|
|
||||||
|
class MonthViewPanel (PanelDesign):
|
||||||
|
"""Displays a grid of the day of the current month
|
||||||
|
with detailed event descriptions."""
|
||||||
|
|
||||||
|
def __init__(self, size, month=None, year=None):
|
||||||
|
super(MonthViewPanel, self).__init__(size)
|
||||||
|
self.day_table = []
|
||||||
|
self.month = month
|
||||||
|
if self.month == None:
|
||||||
|
self.month = date.today().month
|
||||||
|
self.year = year
|
||||||
|
if self.year == None:
|
||||||
|
self.year = date.today().year
|
||||||
|
self.__init_sizes__()
|
||||||
|
self.__init_day_boxes__()
|
||||||
|
|
||||||
|
def __init_sizes__(self):
|
||||||
|
self.weather_height = 0
|
||||||
|
self.info_height = 0
|
||||||
|
if general_settings["info-area"] in ["events", "rss", "crypto"]:
|
||||||
|
self.info_height = info_height
|
||||||
|
if general_settings["weather-info"]:
|
||||||
|
self.weather_height = weather_height
|
||||||
|
self.day_area_height = 1 - self.weather_height - self.info_height
|
||||||
|
self.day_area_ypos = self.weather_height
|
||||||
|
|
||||||
|
self.week_count = self.__get_week_count__()
|
||||||
|
|
||||||
|
area_height = self.size[1] * self.day_area_height
|
||||||
|
area_width = self.size[0]
|
||||||
|
self.day_box_size = (area_width / 7, area_height / self.week_count)
|
||||||
|
|
||||||
|
def add_weather(self, weather):
|
||||||
|
if general_settings["weather-info"] == False:
|
||||||
|
return
|
||||||
|
size = (self.size[0], self.size[1] * self.weather_height)
|
||||||
|
|
||||||
|
header = WeatherHeaderDesign(size, weather)
|
||||||
|
self.draw_design(header)
|
||||||
|
self.__draw_seperator__(size[1], colors["hl"])
|
||||||
|
|
||||||
|
def add_calendar(self, calendar):
|
||||||
|
self.__add_calendar_to_days__(calendar)
|
||||||
|
|
||||||
|
def __add_calendar_to_days__(self, calendar):
|
||||||
|
for week in self.day_table:
|
||||||
|
for day in week:
|
||||||
|
if day != None:
|
||||||
|
day.add_calendar(calendar)
|
||||||
|
|
||||||
|
def add_rssfeed(self, rss):
|
||||||
|
if general_settings["info-area"] != "rss":
|
||||||
|
return
|
||||||
|
|
||||||
|
size = (self.size[0], self.size[1] * self.info_height)
|
||||||
|
pos = (0, self.size[1] - size[1] + info_padding)
|
||||||
|
|
||||||
|
rss = RssPostListDesign(size, rss)
|
||||||
|
rss.pos = pos
|
||||||
|
self.draw_design(rss)
|
||||||
|
|
||||||
|
def add_tasks(self, tasks):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add_crypto(self, crypto):
|
||||||
|
if general_settings["info-area"] == "crypto":
|
||||||
|
self.__draw_crypto__(crypto)
|
||||||
|
|
||||||
|
def __draw_crypto__(self, crypto):
|
||||||
|
size = (self.size[0], self.size[1] * self.info_height)
|
||||||
|
pos = (0, self.size[1] - size[1])
|
||||||
|
|
||||||
|
crypto = CryptoListDesign(size, crypto)
|
||||||
|
crypto.pos = (pos[0], pos[1] + (size[1] -
|
||||||
|
crypto.get_estimated_height()))
|
||||||
|
self.draw_design(crypto)
|
||||||
|
|
||||||
|
def __finish_panel__(self):
|
||||||
|
self.__draw_days__()
|
||||||
|
|
||||||
|
def __draw_days__(self):
|
||||||
|
size = (self.size[0], self.size[1] * self.day_area_height)
|
||||||
|
pos = (0, self.size[1] * self.day_area_ypos)
|
||||||
|
|
||||||
|
table = TableDesign(size, matrix=self.day_table)
|
||||||
|
table.pos = pos
|
||||||
|
self.draw_design(table)
|
||||||
|
|
||||||
|
def __draw_seperator__(self, height, color):
|
||||||
|
ImageDraw.Draw(self.__image__).line(
|
||||||
|
[(0, height * self.size[1]), (self.size[0], height * self.size[1])], fill=color, width=seperator_width)
|
||||||
|
|
||||||
|
def __init_day_boxes__(self):
|
||||||
|
if week_starts_on == "Monday":
|
||||||
|
callib.setfirstweekday(callib.MONDAY)
|
||||||
|
elif week_starts_on == "Sunday":
|
||||||
|
callib.setfirstweekday(callib.SUNDAY)
|
||||||
|
|
||||||
|
weeks = callib.monthcalendar(self.year, self.month)
|
||||||
|
for i, week in enumerate(weeks):
|
||||||
|
self.day_table.append([])
|
||||||
|
for day in week:
|
||||||
|
self.day_table[i].append(self.__create_day__(day))
|
||||||
|
|
||||||
|
def __create_day__(self, day):
|
||||||
|
if day == None or day == 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
design = DayBoxDesign(self.day_box_size, date(
|
||||||
|
self.year, self.month, int(day)))
|
||||||
|
|
||||||
|
return design
|
||||||
|
|
||||||
|
def __get_week_count__(self):
|
||||||
|
return len(callib.monthcalendar(self.year, self.month))
|
|
@ -2,72 +2,100 @@ from WeatherForecast import WeatherForecast
|
||||||
from WeatherInterface import WeatherInterface
|
from WeatherInterface import WeatherInterface
|
||||||
import pyowm
|
import pyowm
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from settings import units
|
from settings import units, language
|
||||||
|
from Translator import translate
|
||||||
|
|
||||||
|
|
||||||
class OwmForecasts (WeatherInterface):
|
class OwmForecasts (WeatherInterface):
|
||||||
"""Fetches weather through the Openweathermap-api."""
|
"""Fetches weather through the Openweathermap-api."""
|
||||||
def __init__ (self, location, api_key, paid_api=False):
|
|
||||||
subscription = "pro" if paid_api else None
|
def __init__(self, location, api_key, paid_api=False):
|
||||||
self.api = pyowm.OWM(api_key, subscription_type=subscription)
|
self.subscription = "pro" if paid_api else None
|
||||||
|
self.api_key = api_key
|
||||||
self.units = units
|
self.units = units
|
||||||
self.location = location
|
self.location = location
|
||||||
|
self.api = pyowm.OWM(
|
||||||
|
self.api_key, subscription_type=self.subscription, language=language)
|
||||||
|
|
||||||
def is_available (self):
|
def is_available(self):
|
||||||
try:
|
try:
|
||||||
return self.api.is_API_online()
|
return self.api.is_API_online()
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_today_forecast (self, location=None):
|
def reload(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_today_forecast(self, location=None):
|
||||||
if self.is_available() is False:
|
if self.is_available() is False:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
location = self.location if location is None else location
|
|
||||||
|
|
||||||
observation = self.api.weather_at_place(location)
|
try:
|
||||||
weather = observation.get_weather()
|
location = self.location if location is None else location
|
||||||
|
|
||||||
return self.__get_forecast_from_weather__(weather, location=location)
|
observation = self.api.weather_at_place(location)
|
||||||
|
weather = observation.get_weather()
|
||||||
|
|
||||||
def get_forecast_in_days (self, offset_by_days, location=None):
|
return self.__get_forecast_from_weather__(weather, location=location)
|
||||||
|
except:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_forecast_in_days(self, offset_by_days, location=None):
|
||||||
if offset_by_days is 0:
|
if offset_by_days is 0:
|
||||||
return self.get_today_forecast(location)
|
return self.get_today_forecast(location)
|
||||||
|
|
||||||
if self.is_available() is False:
|
if self.is_available() is False:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
location = self.location if location is None else location
|
location = self.location if location is None else location
|
||||||
try:
|
try:
|
||||||
forecast = self.api.daily_forecast(location, limit=offset_by_days)
|
forecast = self.api.daily_forecast(location, limit=offset_by_days)
|
||||||
target_weather = forecast.get_forecast().get_weathers()[-1]
|
target_weather = forecast.get_forecast().get_weathers()[-1]
|
||||||
|
|
||||||
return self.__get_forecast_from_weather__(target_weather, location=location)
|
return self.__get_forecast_from_weather__(target_weather, location=location)
|
||||||
except: # only allowed for paied membership
|
except: # only allowed for paid membership
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __get_forecast_from_weather__ (self, weather, location):
|
def __get_forecast_from_weather__(self, weather, location):
|
||||||
forecast_object = WeatherForecast()
|
forecast_object = WeatherForecast()
|
||||||
forecast_object.units = self.units
|
forecast_object.units = self.units
|
||||||
forecast_object.fetch_datetime = datetime.now()
|
forecast_object.fetch_datetime = datetime.now()
|
||||||
forecast_object.location = location
|
forecast_object.location = location
|
||||||
forecast_object.datetime = weather.get_reference_time(timeformat='date')
|
forecast_object.datetime = weather.get_reference_time(
|
||||||
|
timeformat='date')
|
||||||
|
|
||||||
forecast_object.icon = weather.get_weather_icon_name()
|
forecast_object.icon = weather.get_weather_icon_name()
|
||||||
forecast_object.air_humidity = str(weather.get_humidity())
|
forecast_object.air_humidity = str(weather.get_humidity())
|
||||||
forecast_object.clouds = str(weather.get_clouds())
|
forecast_object.clouds = str(weather.get_clouds())
|
||||||
forecast_object.short_description = str(weather.get_status())
|
forecast_object.short_description = translate(
|
||||||
forecast_object.detailed_description = str(weather.get_detailed_status())
|
str(weather.get_status()))
|
||||||
|
forecast_object.detailed_description = str(
|
||||||
|
weather.get_detailed_status())
|
||||||
forecast_object.air_pressure = str(weather.get_pressure()['press'])
|
forecast_object.air_pressure = str(weather.get_pressure()['press'])
|
||||||
|
if 'deg' in weather.get_wind().keys():
|
||||||
|
forecast_object.wind_deg = str(int(weather.get_wind()['deg']))
|
||||||
|
|
||||||
if forecast_object.units == "metric":
|
if forecast_object.units == "metric":
|
||||||
forecast_object.air_temperature = str(int(weather.get_temperature(unit='celsius')['temp']))
|
forecast_object.air_temperature = str(
|
||||||
forecast_object.wind_speed = str(int(weather.get_wind()['speed']))
|
int(weather.get_temperature(unit='celsius')['temp']))
|
||||||
|
forecast_object.wind_speed = str(
|
||||||
|
int(weather.get_wind()['speed'])) # kmh
|
||||||
|
|
||||||
|
if forecast_object.units == "aviation":
|
||||||
|
forecast_object.air_temperature = str(
|
||||||
|
int(weather.get_temperature(unit='celsius')['temp']))
|
||||||
|
forecast_object.wind_speed = str(
|
||||||
|
int(weather.get_wind()['speed'] * 1.94384)) # knots
|
||||||
|
|
||||||
if forecast_object.units == "imperial":
|
if forecast_object.units == "imperial":
|
||||||
forecast_object.air_temperature = str(int(weather.get_temperature('fahrenheit')['temp']))
|
forecast_object.air_temperature = str(
|
||||||
forecast_object.wind_speed = str(int(weather.get_wind()['speed'] * 0.621))
|
int(weather.get_temperature('fahrenheit')['temp']))
|
||||||
|
forecast_object.wind_speed = str(
|
||||||
|
int(weather.get_wind()['speed'] * 0.621)) # mph
|
||||||
|
|
||||||
forecast_object.sunrise = datetime.fromtimestamp(int(weather.get_sunrise_time(timeformat='unix')))
|
forecast_object.sunrise = datetime.fromtimestamp(
|
||||||
forecast_object.sunset = datetime.fromtimestamp(int(weather.get_sunset_time(timeformat='unix')))
|
int(weather.get_sunrise_time(timeformat='unix')))
|
||||||
|
forecast_object.sunset = datetime.fromtimestamp(
|
||||||
|
int(weather.get_sunset_time(timeformat='unix')))
|
||||||
|
|
||||||
return forecast_object
|
return forecast_object
|
||||||
|
|
|
@ -1,18 +1,39 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
|
from TechnicalDataDesign import TechnicalDataDesign
|
||||||
|
from settings import print_technical_data
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
class PanelDesign (DesignEntity):
|
class PanelDesign (DesignEntity):
|
||||||
"""Defined general interface for panel designs."""
|
"""Defined general interface for panel designs."""
|
||||||
def __init__ (self, size):
|
|
||||||
|
def __init__(self, size):
|
||||||
super(PanelDesign, self).__init__(size)
|
super(PanelDesign, self).__init__(size)
|
||||||
|
self.start_timestamp = datetime.now()
|
||||||
|
|
||||||
def add_weather (self, weather):
|
def add_weather(self, weather):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
pass
|
||||||
|
|
||||||
def add_calendar (self, calendar):
|
def add_calendar(self, calendar):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
pass
|
||||||
|
|
||||||
def add_rssfeed (self, rss):
|
def add_rssfeed(self, rss):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
pass
|
||||||
|
|
||||||
def add_taks (self, tasks):
|
def add_tasks(self, tasks):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
pass
|
||||||
|
|
||||||
|
def add_crypto(self, crypto):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __finish_panel__(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
self.__finish_panel__()
|
||||||
|
|
||||||
|
if print_technical_data:
|
||||||
|
td = TechnicalDataDesign(
|
||||||
|
self.size, self.start_timestamp, datetime.now())
|
||||||
|
td.mask = True
|
||||||
|
self.draw_design(td)
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
from DataSourceInterface import DataSourceInterface
|
from DataSourceInterface import DataSourceInterface
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
|
||||||
class RssInterface(DataSourceInterface):
|
class RssInterface(DataSourceInterface):
|
||||||
"""Interface for fetching and processing rss post information."""
|
"""Interface for fetching and processing rss post information."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
self.loaded_posts = []
|
||||||
|
|
||||||
|
def reload(self):
|
||||||
|
if self.is_available() == False:
|
||||||
|
return
|
||||||
self.loaded_posts = self.__get_posts__()
|
self.loaded_posts = self.__get_posts__()
|
||||||
self.__sort_posts__()
|
self.__sort_posts__()
|
||||||
|
|
||||||
|
@ -17,7 +24,7 @@ class RssInterface(DataSourceInterface):
|
||||||
return self.get_day_posts(datetime.now())
|
return self.get_day_posts(datetime.now())
|
||||||
|
|
||||||
def get_day_posts(self, day):
|
def get_day_posts(self, day):
|
||||||
return self.__get_posts_to_filter__(lambda x : x.datetime.strftime('%d-%m-%y') == date.strftime('%d-%m-%y'))
|
return self.__get_posts_to_filter__(lambda x: x.datetime.strftime('%d-%m-%y') == day.strftime('%d-%m-%y'))
|
||||||
|
|
||||||
def __get_posts_to_filter__(self, post_filter):
|
def __get_posts_to_filter__(self, post_filter):
|
||||||
if self.loaded_posts is None:
|
if self.loaded_posts is None:
|
||||||
|
@ -25,4 +32,4 @@ class RssInterface(DataSourceInterface):
|
||||||
return [post for post in self.loaded_posts if post_filter(post)]
|
return [post for post in self.loaded_posts if post_filter(post)]
|
||||||
|
|
||||||
def __sort_posts__(self):
|
def __sort_posts__(self):
|
||||||
self.loaded_posts.sort(key=lambda x : x.datetime, reverse=True)
|
self.loaded_posts.sort(key=lambda x: x.datetime, reverse=True)
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
from RssInterface import RssInterface
|
from RssInterface import RssInterface
|
||||||
from datetime import datetime, timedelta, date
|
from datetime import datetime, timedelta, date
|
||||||
import feedparser
|
import feedparser
|
||||||
import arrow
|
|
||||||
import RssPost
|
import RssPost
|
||||||
|
from urllib.request import urlopen
|
||||||
|
|
||||||
max_range_days = 14
|
max_range_days = 14
|
||||||
|
|
||||||
|
|
||||||
class RssParserPosts (RssInterface):
|
class RssParserPosts (RssInterface):
|
||||||
"""Fetches posts from url-addresses via rss parser."""
|
"""Fetches posts from url-addresses via rss parser."""
|
||||||
|
|
||||||
def __init__(self, urls):
|
def __init__(self, urls):
|
||||||
self.urls = urls
|
self.urls = urls
|
||||||
super(RssParserPosts, self).__init__()
|
super(RssParserPosts, self).__init__()
|
||||||
|
|
||||||
|
def is_available(self):
|
||||||
|
try:
|
||||||
|
testurl = ""
|
||||||
|
if self.urls:
|
||||||
|
testurl = self.urls[0]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
urlopen(testurl)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
def __get_posts__(self):
|
def __get_posts__(self):
|
||||||
posts = []
|
posts = []
|
||||||
|
|
||||||
|
@ -40,5 +54,4 @@ class RssParserPosts (RssInterface):
|
||||||
def __get_webpage__(self, link):
|
def __get_webpage__(self, link):
|
||||||
start_index = link.find('://') + 3
|
start_index = link.find('://') + 3
|
||||||
end_index = link[start_index:].find('/') + start_index
|
end_index = link[start_index:].find('/') + start_index
|
||||||
return link[start_index : end_index]
|
return link[start_index: end_index]
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
class RssPost(object):
|
class RssPost(object):
|
||||||
"""Defines a rss post, independent of any implementation"""
|
"""Defines a rss post, independent of any implementation"""
|
||||||
def __init__ (self):
|
|
||||||
|
def __init__(self):
|
||||||
self.title = None
|
self.title = None
|
||||||
self.description = None
|
self.description = None
|
||||||
self.source = None
|
self.source = None
|
||||||
|
|
||||||
self.datetime = None
|
self.datetime = None
|
||||||
self.fetch_datetime = None
|
self.fetch_datetime = None
|
||||||
|
|
|
@ -1,32 +1,36 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from TableTextDesign import TableTextDesign
|
from TableDesign import TableDesign
|
||||||
|
from Assets import defaultfontsize
|
||||||
|
|
||||||
|
|
||||||
class RssPostListDesign (DesignEntity):
|
class RssPostListDesign (DesignEntity):
|
||||||
"""Creates a TableTextDesign filled with rss post
|
"""Creates a TableDesign filled with rss post
|
||||||
date and title"""
|
date and title"""
|
||||||
def __init__ (self, size, rssfeed, text_size = 18):
|
|
||||||
|
def __init__(self, size, rssfeed, text_size=defaultfontsize):
|
||||||
super(RssPostListDesign, self).__init__(size)
|
super(RssPostListDesign, self).__init__(size)
|
||||||
self.rssfeed = rssfeed
|
self.rssfeed = rssfeed
|
||||||
self.__post_matrix__ = []
|
self.__post_matrix__ = []
|
||||||
self.text_size = text_size
|
self.text_size = text_size
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
self.__fill_post_matrix__()
|
self.__fill_post_matrix__()
|
||||||
|
|
||||||
table_design = TableTextDesign(self.size, line_spacing=5, col_spacing=3, text_matrix=self.__post_matrix__, fontsize = self.text_size, mask=False, truncate_cols=False, wrap=True)
|
table_design = TableDesign(self.size, line_spacing=2, col_spacing=3, matrix=self.__post_matrix__,
|
||||||
|
fontsize=self.text_size, mask=False, wrap=True, truncate_rows=True)
|
||||||
self.draw_design(table_design)
|
self.draw_design(table_design)
|
||||||
|
|
||||||
def __get_formatted_post__ (self, post):
|
def __get_formatted_post__(self, post):
|
||||||
date = post.datetime.strftime('%d %b')
|
date = post.datetime.strftime('%d %b')
|
||||||
date = self.__remove_leading_zero__(date)
|
date = self.__remove_leading_zero__(date)
|
||||||
return [ '•', post.title ]
|
return ['', '•', post.title]
|
||||||
|
|
||||||
def __remove_leading_zero__(self, text):
|
def __remove_leading_zero__(self, text):
|
||||||
while text[0] is '0':
|
while text[0] is '0':
|
||||||
text = text[1:]
|
text = text[1:]
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def __fill_post_matrix__ (self):
|
def __fill_post_matrix__(self):
|
||||||
for post in self.rssfeed.get_latest_posts():
|
for post in self.rssfeed.get_latest_posts():
|
||||||
row = self.__get_formatted_post__(post)
|
row = self.__get_formatted_post__(post)
|
||||||
self.__post_matrix__.append(row)
|
self.__post_matrix__.append(row)
|
||||||
|
|
|
@ -1,24 +1,15 @@
|
||||||
from EventListDesign import EventListDesign
|
from EventListDesign import EventListDesign
|
||||||
from settings import hours
|
from settings import hours
|
||||||
from Assets import fonts
|
from Assets import fonts, defaultfontsize, colors
|
||||||
|
from TextFormatter import event_prefix_str_sum
|
||||||
|
|
||||||
eventlist_allday_char = "•"
|
|
||||||
font = fonts["regular"]
|
font = fonts["regular"]
|
||||||
|
|
||||||
|
|
||||||
class SingelDayEventListDesign (EventListDesign):
|
class SingelDayEventListDesign (EventListDesign):
|
||||||
"""Specialized event list for day list design."""
|
"""Specialized event list for day list design."""
|
||||||
def __init__ (self, size, calendar, date, font_size = 16, line_spacing=2, col_spacing=5, general_color="black", background_color="white", highlight_color="red"):
|
|
||||||
prefix_func = lambda x : self.__get_event_prefix__(x)
|
|
||||||
super().__init__(size, calendar, filter_date=date, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_func=prefix_func, font_family=font)
|
|
||||||
|
|
||||||
def __get_event_prefix__ (self, event):
|
|
||||||
if event.allday:
|
|
||||||
return eventlist_allday_char
|
|
||||||
else:
|
|
||||||
return self.__get_time__(event.begin_datetime)
|
|
||||||
|
|
||||||
def __get_time__ (self, time):
|
def __init__(self, size, events, font_size=defaultfontsize, line_spacing=0, event_prefix_rel_dates=[], col_spacing=5, general_color=colors["fg"], background_color=colors["bg"], highlight_color=colors["hl"]):
|
||||||
if hours == "24":
|
def prefix_func(x, rel_date): return event_prefix_str_sum(x, rel_date)
|
||||||
return time.strftime('%H:%M')
|
super().__init__(size, events, text_size=font_size, line_spacing=line_spacing, col_spacing=col_spacing, event_prefix_rel_dates=event_prefix_rel_dates,
|
||||||
else:
|
event_prefix_func=prefix_func, font_family=font, show_more_info=True, general_color=general_color, background_color=background_color, highlight_color=highlight_color)
|
||||||
return time.strftime('%I:%M')
|
|
||||||
|
|
|
@ -1,17 +1,25 @@
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from TextWraper import wrap_text_with_font
|
from TextWraper import wrap_text_with_font
|
||||||
|
from Assets import defaultfontsize, colors
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
|
||||||
|
|
||||||
default_props = {
|
default_props = {
|
||||||
"color" : "black",
|
"color": colors["fg"],
|
||||||
"background_color" : "white"
|
"background_color": colors["bg"],
|
||||||
|
"font-size": defaultfontsize
|
||||||
}
|
}
|
||||||
|
|
||||||
class TableTextDesign (TextDesign):
|
|
||||||
"""Gets a matrix with text that is than
|
class TableDesign (TextDesign):
|
||||||
|
"""Gets a matrix with text or designs that is than
|
||||||
displayed in a table without borders."""
|
displayed in a table without borders."""
|
||||||
def __init__ (self, size, text_matrix, max_col_size = None, max_row_size = None, font = None, fontsize = 12, column_horizontal_alignments = [], mask = True, line_spacing = 0, col_spacing = 0, truncate_rows = True, truncate_cols = True, wrap = False, truncate_text=True, truncate_suffix="...", cell_properties=None):
|
|
||||||
super(TableTextDesign, self).__init__(size, font=font, fontsize=fontsize, mask=mask)
|
def __init__(self, size, matrix, max_col_size=None, max_row_size=None, font=None, fontsize=defaultfontsize, column_horizontal_alignments=[], mask=True, line_spacing=0, col_spacing=0, truncate_rows=True, truncate_cols=True, wrap=False, truncate_text=True, truncate_suffix="...", cell_properties=None, background_color=colors["bg"]):
|
||||||
self.matrix = text_matrix
|
super(TableDesign, self).__init__(
|
||||||
|
size, font=font, fontsize=fontsize, mask=mask)
|
||||||
|
self.__init_image__(background_color)
|
||||||
|
self.matrix = matrix
|
||||||
self.max_col_size = max_col_size
|
self.max_col_size = max_col_size
|
||||||
self.max_row_size = max_row_size
|
self.max_row_size = max_row_size
|
||||||
self.line_spacing = line_spacing
|
self.line_spacing = line_spacing
|
||||||
|
@ -25,8 +33,9 @@ class TableTextDesign (TextDesign):
|
||||||
self.truncate_text = truncate_text
|
self.truncate_text = truncate_text
|
||||||
self.truncate_suffix = truncate_suffix
|
self.truncate_suffix = truncate_suffix
|
||||||
self.cell_properties = cell_properties
|
self.cell_properties = cell_properties
|
||||||
|
default_props["font-size"] = fontsize
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
if len(self.matrix) is 0:
|
if len(self.matrix) is 0:
|
||||||
return
|
return
|
||||||
self.__reform_col_size__()
|
self.__reform_col_size__()
|
||||||
|
@ -35,48 +44,64 @@ class TableTextDesign (TextDesign):
|
||||||
self.max_col, self.max_row = self.__get_truncated_counts__()
|
self.max_col, self.max_row = self.__get_truncated_counts__()
|
||||||
self.__print_table__(self.matrix)
|
self.__print_table__(self.matrix)
|
||||||
|
|
||||||
def __reform_col_size__ (self):
|
def __reform_col_size__(self):
|
||||||
if self.max_col_size is not None:
|
if self.max_col_size is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
font = self.__get_font__()
|
|
||||||
col_sizes = []
|
col_sizes = []
|
||||||
for c in range(len(self.matrix[0])): #amout of columns
|
for c in range(len(self.matrix[0])): # amout of columns
|
||||||
for r in range(len(self.matrix)):
|
for r in range(len(self.matrix)):
|
||||||
row_col_size = font.getsize(self.matrix[r][c])[0] #get width of text in that row/col
|
row_col_size = self.__get_cell_size__(r, c)[0]
|
||||||
if len(col_sizes) - 1 < c:
|
if len(col_sizes) - 1 < c:
|
||||||
col_sizes.append(row_col_size)
|
col_sizes.append(row_col_size)
|
||||||
elif row_col_size > col_sizes[c]:
|
elif row_col_size > col_sizes[c]:
|
||||||
col_sizes[c] = row_col_size
|
col_sizes[c] = row_col_size
|
||||||
|
|
||||||
for index, size in enumerate(col_sizes):
|
for index, size in enumerate(col_sizes):
|
||||||
preceding_size = sum(col_sizes[:index])
|
preceding_size = sum(col_sizes[:index]) + index * self.col_spacing
|
||||||
if preceding_size + size > self.size[0]:
|
if preceding_size + size > self.size[0]:
|
||||||
col_sizes[index] = self.size[0] - preceding_size
|
col_sizes[index] = self.size[0] - preceding_size
|
||||||
break
|
break
|
||||||
|
|
||||||
self.max_col_size = col_sizes
|
self.max_col_size = col_sizes
|
||||||
|
|
||||||
def __reform_row_size__ (self):
|
def __reform_row_size__(self):
|
||||||
if self.max_row_size is not None:
|
if self.max_row_size is not None:
|
||||||
return
|
return
|
||||||
|
|
||||||
font = self.__get_font__()
|
|
||||||
row_sizes = []
|
row_sizes = []
|
||||||
for r in range(len(self.matrix)):
|
for r in range(len(self.matrix)):
|
||||||
for c in range(len(self.matrix[0])): #amout of columns
|
for c in range(len(self.matrix[0])): # amout of columns
|
||||||
cell_text = self.matrix[r][c]
|
col_row_size = self.__get_cell_size__(r, c)[1]
|
||||||
if self.wrap:
|
|
||||||
cell_text = wrap_text_with_font(cell_text, self.max_col_size[c], font)
|
|
||||||
col_row_size = font.getsize_multiline(cell_text)[1] #get height of text in that col/row
|
|
||||||
if len(row_sizes) - 1 < r:
|
if len(row_sizes) - 1 < r:
|
||||||
row_sizes.append(col_row_size)
|
row_sizes.append(col_row_size)
|
||||||
elif col_row_size > row_sizes[r]:
|
elif col_row_size > row_sizes[r]:
|
||||||
row_sizes[r] = col_row_size
|
row_sizes[r] = col_row_size
|
||||||
|
|
||||||
self.max_row_size = row_sizes
|
self.max_row_size = row_sizes
|
||||||
|
|
||||||
def __get_truncated_counts__ (self):
|
def __get_cell_size__(self, r, c):
|
||||||
|
content = self.matrix[r][c]
|
||||||
|
size = (0, 0)
|
||||||
|
|
||||||
|
if content == None:
|
||||||
|
return size
|
||||||
|
elif type(content) == str:
|
||||||
|
font = self.__get_font__()
|
||||||
|
# get width of text in that row/col
|
||||||
|
width = font.getsize(self.matrix[r][c])[0]
|
||||||
|
if self.wrap and self.max_col_size != None:
|
||||||
|
content = wrap_text_with_font(
|
||||||
|
content, self.max_col_size[c], font)
|
||||||
|
line_count = content.count('\n') + 1
|
||||||
|
height = font.font.height * line_count # get height of text in that col/row
|
||||||
|
size = (width, height)
|
||||||
|
else: # DesignEntity
|
||||||
|
size = content.size
|
||||||
|
|
||||||
|
return size
|
||||||
|
|
||||||
|
def __get_truncated_counts__(self):
|
||||||
max_col = 0
|
max_col = 0
|
||||||
if self.truncate_cols:
|
if self.truncate_cols:
|
||||||
while max_col < len(self.matrix[0]) and self.__get_cell_pos__(0, max_col + 1)[0] - self.col_spacing <= self.size[0]:
|
while max_col < len(self.matrix[0]) and self.__get_cell_pos__(0, max_col + 1)[0] - self.col_spacing <= self.size[0]:
|
||||||
|
@ -86,29 +111,54 @@ class TableTextDesign (TextDesign):
|
||||||
|
|
||||||
max_row = 0
|
max_row = 0
|
||||||
if self.truncate_rows:
|
if self.truncate_rows:
|
||||||
while max_row < len(self.matrix) and self.__get_cell_pos__(max_row + 1,0)[1] - self.line_spacing <= self.size[1]:
|
while max_row < len(self.matrix) and self.__get_cell_pos__(max_row + 1, 0)[1] - self.line_spacing <= self.size[1]:
|
||||||
max_row += 1
|
max_row += 1
|
||||||
else:
|
else:
|
||||||
max_row = len(self.matrix)
|
max_row = len(self.matrix)
|
||||||
|
|
||||||
return (max_col, max_row)
|
return (max_col, max_row)
|
||||||
|
|
||||||
def __print_table__ (self, matrix):
|
def __print_table__(self, matrix):
|
||||||
for r in range(self.max_row):
|
for r in range(self.max_row):
|
||||||
for c in range(self.max_col):
|
for c in range(self.max_col):
|
||||||
size = self.cell_sizes[r][c]
|
self.__draw_cell__(r, c)
|
||||||
pos = self.__get_cell_pos__(r,c)
|
|
||||||
self.__draw_text__(pos, size, r, c)
|
def __draw_text__(self, pos, size, row, col):
|
||||||
|
|
||||||
def __draw_text__ (self, pos, size, row, col):
|
|
||||||
color = self.__get_cell_prop__(row, col, "color")
|
color = self.__get_cell_prop__(row, col, "color")
|
||||||
bg_color = self.__get_cell_prop__(row, col, "background_color")
|
bg_color = self.__get_cell_prop__(row, col, "background_color")
|
||||||
|
fontsize = self.__get_cell_prop__(row, col, "font-size")
|
||||||
|
|
||||||
design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=self.font_size, horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix)
|
design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=fontsize,
|
||||||
|
horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix)
|
||||||
design.pos = pos
|
design.pos = pos
|
||||||
|
design.mask = False
|
||||||
self.draw_design(design)
|
self.draw_design(design)
|
||||||
|
|
||||||
def __get_cell_pos__ (self, row, col):
|
def __draw_design__(self, pos, size, row, col):
|
||||||
|
bg_color = self.__get_cell_prop__(row, col, "background_color")
|
||||||
|
|
||||||
|
source_design = self.matrix[row][col]
|
||||||
|
source_design.mask = False
|
||||||
|
|
||||||
|
framed_design = DesignEntity(size, mask=False)
|
||||||
|
framed_design.__init_image__(color=bg_color)
|
||||||
|
framed_design.draw_design(source_design)
|
||||||
|
|
||||||
|
framed_design.pos = pos
|
||||||
|
self.draw_design(framed_design)
|
||||||
|
|
||||||
|
def __draw_cell__(self, row, col):
|
||||||
|
size = self.cell_sizes[row][col]
|
||||||
|
pos = self.__get_cell_pos__(row, col)
|
||||||
|
|
||||||
|
if self.matrix[row][col] == None:
|
||||||
|
return
|
||||||
|
elif type(self.matrix[row][col]) == str:
|
||||||
|
self.__draw_text__(pos, size, row, col)
|
||||||
|
else:
|
||||||
|
self.__draw_design__(pos, size, row, col)
|
||||||
|
|
||||||
|
def __get_cell_pos__(self, row, col):
|
||||||
xpos, ypos = (0, 0)
|
xpos, ypos = (0, 0)
|
||||||
for c in range(col):
|
for c in range(col):
|
||||||
xpos += self.cell_sizes[row][c][0]
|
xpos += self.cell_sizes[row][c][0]
|
||||||
|
@ -118,7 +168,7 @@ class TableTextDesign (TextDesign):
|
||||||
ypos += self.line_spacing
|
ypos += self.line_spacing
|
||||||
return (xpos, ypos)
|
return (xpos, ypos)
|
||||||
|
|
||||||
def __get_cell_sizes__ (self):
|
def __get_cell_sizes__(self):
|
||||||
size_matrix = []
|
size_matrix = []
|
||||||
for r in range(len(self.matrix)):
|
for r in range(len(self.matrix)):
|
||||||
size_matrix.append([])
|
size_matrix.append([])
|
||||||
|
@ -127,7 +177,7 @@ class TableTextDesign (TextDesign):
|
||||||
size_matrix[r].append(size)
|
size_matrix[r].append(size)
|
||||||
return size_matrix
|
return size_matrix
|
||||||
|
|
||||||
def __get_col_hori_alignment__ (self, c):
|
def __get_col_hori_alignment__(self, c):
|
||||||
if len(self.column_horizontal_alignments) <= c:
|
if len(self.column_horizontal_alignments) <= c:
|
||||||
return "left"
|
return "left"
|
||||||
return self.column_horizontal_alignments[c]
|
return self.column_horizontal_alignments[c]
|
||||||
|
@ -135,7 +185,8 @@ class TableTextDesign (TextDesign):
|
||||||
def __get_cell_prop__(self, r, c, prop):
|
def __get_cell_prop__(self, r, c, prop):
|
||||||
if self.cell_properties is None:
|
if self.cell_properties is None:
|
||||||
return default_props[prop]
|
return default_props[prop]
|
||||||
try:
|
|
||||||
|
if r < len(self.cell_properties) and c < len(self.cell_properties[r]) and prop in self.cell_properties[r][c].keys():
|
||||||
return self.cell_properties[r][c][prop]
|
return self.cell_properties[r][c][prop]
|
||||||
except:
|
else:
|
||||||
return default_props[prop]
|
return default_props[prop]
|
42
Calendar/TechnicalDataDesign.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
from DesignEntity import DesignEntity
|
||||||
|
from TextDesign import TextDesign
|
||||||
|
from Assets import colors
|
||||||
|
|
||||||
|
font_size = 20
|
||||||
|
|
||||||
|
|
||||||
|
class TechnicalDataDesign(DesignEntity):
|
||||||
|
'''Prints data about the current loop ontop of the panel'''
|
||||||
|
|
||||||
|
def __init__(self, size, start, stop):
|
||||||
|
super(TechnicalDataDesign, self).__init__(size, mask=True)
|
||||||
|
self.start = start
|
||||||
|
self.stop = stop
|
||||||
|
|
||||||
|
def __finish_image__(self):
|
||||||
|
data = self.__get_data__()
|
||||||
|
|
||||||
|
txt = TextDesign(self.size, text=data, fontsize=font_size)
|
||||||
|
txt.color = colors["fg"]
|
||||||
|
txt.pos = (0, 0)
|
||||||
|
self.draw_design(txt)
|
||||||
|
|
||||||
|
txt = TextDesign(self.size, text=data, fontsize=font_size)
|
||||||
|
txt.color = colors["hl"]
|
||||||
|
txt.pos = (1, 1)
|
||||||
|
self.draw_design(txt)
|
||||||
|
|
||||||
|
txt = TextDesign(self.size, text=data, fontsize=font_size)
|
||||||
|
txt.color = colors["fg"]
|
||||||
|
txt.pos = (2, 2)
|
||||||
|
self.draw_design(txt)
|
||||||
|
|
||||||
|
def __get_data__(self):
|
||||||
|
data = "START: "
|
||||||
|
data += str(self.start)
|
||||||
|
data += "\nDURATION BEFORE RENDER: "
|
||||||
|
dur = self.stop - self.start
|
||||||
|
data += str(dur)
|
||||||
|
data += "\nSTOP: "
|
||||||
|
data += str(self.stop)
|
||||||
|
return data
|
|
@ -1,16 +1,17 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from PIL import ImageFont, ImageDraw, ImageOps
|
from PIL import ImageFont, ImageDraw, ImageOps
|
||||||
from Assets import path, defaultfont
|
from Assets import path, defaultfont, colors, defaultfontsize
|
||||||
from TextWraper import wrap_text_with_font
|
from TextWraper import wrap_text_with_font
|
||||||
|
|
||||||
paddingcorrection = -3
|
|
||||||
truncateerror_fontsize = 0.5
|
truncateerror_fontsize = 0.5
|
||||||
|
|
||||||
|
|
||||||
class TextDesign (DesignEntity):
|
class TextDesign (DesignEntity):
|
||||||
"""Object that manages all information relevant to text
|
"""Object that manages all information relevant to text
|
||||||
and prints it to an image"""
|
and prints it to an image"""
|
||||||
def __init__ (self, size, color="black", background_color="white", font = None, fontsize = 12, text = "", horizontalalignment = "left", verticalalignment = "top", mask=True, truncate=False, truncate_suffix = '...', wrap=False):
|
|
||||||
super(TextDesign, self).__init__(size, mask = mask)
|
def __init__(self, size, color=colors["fg"], background_color=colors["bg"], font=None, fontsize=defaultfontsize, text="", horizontalalignment="left", verticalalignment="top", mask=True, truncate=False, truncate_suffix='...', wrap=False):
|
||||||
|
super(TextDesign, self).__init__(size, mask=mask)
|
||||||
if font is None:
|
if font is None:
|
||||||
font = defaultfont
|
font = defaultfont
|
||||||
self.font_family = font
|
self.font_family = font
|
||||||
|
@ -24,10 +25,10 @@ class TextDesign (DesignEntity):
|
||||||
self.color = color
|
self.color = color
|
||||||
self.background_color = background_color
|
self.background_color = background_color
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
if self.color is "white":
|
if self.color is "white":
|
||||||
self.invert_mask = True
|
self.invert_mask = True
|
||||||
if self.background_color not in ["white", "black"]:
|
if self.background_color not in ["white", "black"] or self.color in ["red"]:
|
||||||
self.color_key = True
|
self.color_key = True
|
||||||
self.__init_image__(self.background_color)
|
self.__init_image__(self.background_color)
|
||||||
|
|
||||||
|
@ -37,21 +38,24 @@ class TextDesign (DesignEntity):
|
||||||
if self.wrap:
|
if self.wrap:
|
||||||
self.__wrap_text__()
|
self.__wrap_text__()
|
||||||
pos = self.__pos_from_alignment__()
|
pos = self.__pos_from_alignment__()
|
||||||
ImageDraw.Draw(self.__image__).text(pos, self.text, fill=self.color, font=self.__font__)
|
ImageDraw.Draw(self.__image__).text(
|
||||||
|
pos, self.text, fill=self.color, font=self.__font__)
|
||||||
def __truncate_text__ (self):
|
|
||||||
if self.__font__.getsize(self.text)[0] <= self.size[0]: #does not need truncating
|
def __truncate_text__(self):
|
||||||
|
# does not need truncating
|
||||||
|
if self.__font__.getsize_multiline(self.text)[0] <= self.size[0]:
|
||||||
return
|
return
|
||||||
error = truncateerror_fontsize * self.__font__.getsize("A")[0]
|
suffix_length = self.__font__.getsize_multiline(
|
||||||
suffix_length = self.__font__.getsize(self.truncate_suffix)[0]
|
self.truncate_suffix)[0]
|
||||||
while len(self.text) > 1 and self.__font__.getsize(self.text)[0] + suffix_length >= self.size[0] - error:
|
while len(self.text) > 1 and self.__font__.getsize_multiline(self.text)[0] + suffix_length >= self.size[0]:
|
||||||
self.text = self.text[0:-1]
|
self.text = self.text[0:-1]
|
||||||
|
self.text = self.text.rstrip(' ')
|
||||||
self.text += self.truncate_suffix
|
self.text += self.truncate_suffix
|
||||||
|
|
||||||
def __pos_from_alignment__ (self):
|
def __pos_from_alignment__(self):
|
||||||
width, height = self.__font__.getsize(self.text)
|
width, height = self.__get_text_size__()
|
||||||
x, y = 0, 0
|
x, y = 0, 0
|
||||||
|
|
||||||
if self.vertical_alignment == "center":
|
if self.vertical_alignment == "center":
|
||||||
y = int((self.size[1] / 2) - (height / 2))
|
y = int((self.size[1] / 2) - (height / 2))
|
||||||
elif self.vertical_alignment == "bottom":
|
elif self.vertical_alignment == "bottom":
|
||||||
|
@ -62,10 +66,15 @@ class TextDesign (DesignEntity):
|
||||||
elif self.horizontal_alignment == "right":
|
elif self.horizontal_alignment == "right":
|
||||||
x = int(self.size[0] - width)
|
x = int(self.size[0] - width)
|
||||||
|
|
||||||
return (x, y + paddingcorrection)
|
return (x, y)
|
||||||
|
|
||||||
def __wrap_text__ (self):
|
def __get_text_size__(self):
|
||||||
|
widht = self.__font__.getsize_multiline(self.text)[0]
|
||||||
|
height = (self.text.count('\n') + 1) * self.__font__.font.height
|
||||||
|
return widht, height
|
||||||
|
|
||||||
|
def __wrap_text__(self):
|
||||||
self.text = wrap_text_with_font(self.text, self.size[0], self.__font__)
|
self.text = wrap_text_with_font(self.text, self.size[0], self.__font__)
|
||||||
|
|
||||||
def __get_font__(self):
|
def __get_font__(self):
|
||||||
return ImageFont.truetype(path + self.font_family, int(self.font_size))
|
return ImageFont.truetype(path + self.font_family, int(self.font_size))
|
||||||
|
|
110
Calendar/TextFormatter.py
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
from settings import hours, language
|
||||||
|
from datetime import timedelta, datetime, timezone
|
||||||
|
from DictionaryMapper import get_text
|
||||||
|
from Dictionary import multiday_events, allday_events
|
||||||
|
|
||||||
|
first_occurrence_char = '['
|
||||||
|
middle_occurrence_char = '|'
|
||||||
|
last_occurrence_char = ']'
|
||||||
|
multiday_begin_character = ' >'
|
||||||
|
multiday_end_character = '< '
|
||||||
|
until_character = ' - '
|
||||||
|
allday_character = "•"
|
||||||
|
multiday_character = allday_character + allday_character
|
||||||
|
|
||||||
|
|
||||||
|
def time_str(dt):
|
||||||
|
if hours is "12":
|
||||||
|
return dt.strftime("%I:%M%p")
|
||||||
|
elif hours is "24":
|
||||||
|
return dt.strftime("%H:%M")
|
||||||
|
else:
|
||||||
|
return str(dt)
|
||||||
|
|
||||||
|
|
||||||
|
def event_prefix_str_md_dif(event, relative_date=None):
|
||||||
|
if relative_date is None:
|
||||||
|
relative_date = event.begin_datetime.date()
|
||||||
|
|
||||||
|
if event.multiday is False:
|
||||||
|
return event_time_summary(event)
|
||||||
|
|
||||||
|
# Relative to
|
||||||
|
# First day
|
||||||
|
elif __equal__(event.begin_datetime, relative_date):
|
||||||
|
return event_time_summary(event) + multiday_begin_character
|
||||||
|
|
||||||
|
# Last day
|
||||||
|
elif __equal__(event.end_datetime, relative_date) or \
|
||||||
|
(__day_duration__(event.end_datetime) == timedelta(0) and __equal__(relative_date + timedelta(1), event.end_datetime)):
|
||||||
|
return multiday_end_character + event_time_summary(event)
|
||||||
|
|
||||||
|
# Some day
|
||||||
|
else:
|
||||||
|
event.allday = True
|
||||||
|
return multiday_end_character + event_time_summary(event) + multiday_begin_character
|
||||||
|
|
||||||
|
|
||||||
|
def event_prefix_str(event, relative_date=None):
|
||||||
|
if relative_date is None:
|
||||||
|
relative_date = event.begin_datetime.date()
|
||||||
|
|
||||||
|
if event.multiday:
|
||||||
|
return get_text(multiday_events)
|
||||||
|
else:
|
||||||
|
return event_time_detailed(event)
|
||||||
|
|
||||||
|
|
||||||
|
def event_prefix_str_sum(event, relative_date=None):
|
||||||
|
if relative_date is None:
|
||||||
|
relative_date = event.begin_datetime.date()
|
||||||
|
|
||||||
|
if event.multiday:
|
||||||
|
return multiday_character
|
||||||
|
else:
|
||||||
|
return event_time_summary(event)
|
||||||
|
|
||||||
|
|
||||||
|
def event_time_summary(event):
|
||||||
|
if event.allday:
|
||||||
|
return allday_character
|
||||||
|
else:
|
||||||
|
return time_str(event.begin_datetime)
|
||||||
|
|
||||||
|
|
||||||
|
def event_time_detailed(event):
|
||||||
|
if event.allday:
|
||||||
|
return get_text(allday_events)
|
||||||
|
else:
|
||||||
|
return time_str(event.begin_datetime) + until_character + time_str(event.end_datetime)
|
||||||
|
|
||||||
|
|
||||||
|
def date_str(dt):
|
||||||
|
return remove_leading_zero(dt.strftime('%d. %b'))
|
||||||
|
|
||||||
|
|
||||||
|
def remove_leading_zero(text):
|
||||||
|
while text[0] is '0':
|
||||||
|
text = text[1:]
|
||||||
|
return text
|
||||||
|
|
||||||
|
|
||||||
|
def date_summary_str(dt):
|
||||||
|
day = remove_leading_zero(dt.strftime("%d"))
|
||||||
|
if language is "en":
|
||||||
|
return dt.strftime('%a ' + day + '. %b')
|
||||||
|
elif language is "de":
|
||||||
|
return dt.strftime(day + '. %b., %a')
|
||||||
|
else:
|
||||||
|
return dt.strftime('%a ' + day + '. %b')
|
||||||
|
|
||||||
|
|
||||||
|
def __equal__(dt1, dt2):
|
||||||
|
return dt1.day == dt2.day and \
|
||||||
|
dt1.month == dt2.month and \
|
||||||
|
dt1.year == dt2.year
|
||||||
|
|
||||||
|
|
||||||
|
def __day_duration__(dt):
|
||||||
|
day_begin = datetime(dt.year, dt.month, dt.day, 0, 0, 0, 0, timezone.utc)
|
||||||
|
return dt - day_begin
|
|
@ -1,10 +1,11 @@
|
||||||
from Assets import path, defaultfont
|
from Assets import path, defaultfont
|
||||||
from PIL import ImageFont
|
from PIL import ImageFont
|
||||||
|
|
||||||
def wrap_text_with_font (text, width, font):
|
|
||||||
|
def wrap_text_with_font(text, width, font):
|
||||||
words = text.split(' ')
|
words = text.split(' ')
|
||||||
result = ""
|
result = ""
|
||||||
for index, word in enumerate(words):
|
for word in words:
|
||||||
until_current = (result + " " + word).strip()
|
until_current = (result + " " + word).strip()
|
||||||
txt_width, _ = font.getsize_multiline(until_current)
|
txt_width, _ = font.getsize_multiline(until_current)
|
||||||
if txt_width > width:
|
if txt_width > width:
|
||||||
|
@ -14,5 +15,6 @@ def wrap_text_with_font (text, width, font):
|
||||||
result += word
|
result += word
|
||||||
return result.strip()
|
return result.strip()
|
||||||
|
|
||||||
def wrap_text (text, width, font_size, font_family = defaultfont):
|
|
||||||
return wrap_text_with_font(text, width, ImageFont.truetype(path + font_family, int(font_size)))
|
def wrap_text(text, width, font_size, font_family=defaultfont):
|
||||||
|
return wrap_text_with_font(text, width, ImageFont.truetype(path + font_family, int(font_size)))
|
||||||
|
|
26
Calendar/Translator.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
from Dictionary import default_language, dictionary_collection
|
||||||
|
from settings import language
|
||||||
|
|
||||||
|
'''Looks up a phrase in a given dictionary-collection
|
||||||
|
and returns the translated phrase'''
|
||||||
|
|
||||||
|
|
||||||
|
def translate(phrase, target_lang=language, dictionary_collection=dictionary_collection):
|
||||||
|
dictionary = find_dictionary(phrase, dictionary_collection)
|
||||||
|
|
||||||
|
if dictionary == None:
|
||||||
|
return phrase
|
||||||
|
|
||||||
|
if target_lang in dictionary.keys():
|
||||||
|
return dictionary[target_lang]
|
||||||
|
elif '_' in target_lang and target_lang.split('_')[0] in dictionary.keys():
|
||||||
|
return dictionary[target_lang.split('_')[0]]
|
||||||
|
else:
|
||||||
|
return dictionary[default_language]
|
||||||
|
|
||||||
|
|
||||||
|
def find_dictionary(phrase, dictionary_collection=dictionary_collection):
|
||||||
|
for dictionary in dictionary_collection:
|
||||||
|
if phrase in dictionary.values():
|
||||||
|
return dictionary
|
||||||
|
return None
|
|
@ -1,127 +1,84 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from TableTextDesign import TableTextDesign
|
from TableDesign import TableDesign
|
||||||
from Assets import wpath, weathericons, tempicon, humicon, windicon, no_response
|
from Assets import wpath, weathericons, tempicon, humicon, windicon, no_response, colors, defaultfontsize
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from settings import hours
|
from settings import hours
|
||||||
|
|
||||||
icon_xpos = 0.1
|
icon_xpos = 0.1
|
||||||
icon_x_ypos = -0.1
|
icon_x_ypos = 0
|
||||||
icon_width = 1 - 2 * icon_xpos
|
icon_width = 1 - 2 * icon_xpos
|
||||||
info_x_height = icon_width * 0.3
|
|
||||||
info_x_ypos = icon_x_ypos + icon_width
|
info_x_ypos = icon_x_ypos + icon_width
|
||||||
fontsize_y = 0.1
|
info_yresize = -0.05
|
||||||
numbers_x_ypos = icon_x_ypos + icon_width + info_x_height + 0.1
|
fontsize_static = defaultfontsize
|
||||||
numbers_x_ypadding = 0.05
|
|
||||||
max_symbol_y_width = 0.15
|
max_symbol_y_width = 0.15
|
||||||
|
|
||||||
general_text_color = "black"
|
|
||||||
background_color = "white"
|
|
||||||
|
|
||||||
class WeatherColumnDesign (DesignEntity):
|
class WeatherColumnDesign (DesignEntity):
|
||||||
"""Displays weather information in a column"""
|
"""Displays weather information in a column"""
|
||||||
def __init__ (self, size, forecast):
|
|
||||||
|
def __init__(self, size, forecast):
|
||||||
super().__init__(size)
|
super().__init__(size)
|
||||||
self.forecast = forecast
|
self.forecast = forecast
|
||||||
|
|
||||||
def __finish_image__ (self):
|
def __finish_image__(self):
|
||||||
if self.forecast is None:
|
if self.forecast is None:
|
||||||
self.__draw_no_response__()
|
self.__draw_no_response__()
|
||||||
return
|
return
|
||||||
|
|
||||||
self.__draw_icon__(self.forecast.icon)
|
self.__draw_icon__(self.forecast.icon)
|
||||||
self.__draw_info__(self.forecast.short_description)
|
self.__draw_infos__(self.forecast)
|
||||||
#self.__draw_number_symbols__()
|
|
||||||
#self.__draw_numbers__(self.forecast)
|
def __draw_infos__(self, forecast):
|
||||||
self.__draw_numbers_text__(self.forecast)
|
temperature = forecast.air_temperature + \
|
||||||
|
" " + self.__get_unit__(("°C", "°F"))
|
||||||
|
humidity = forecast.air_humidity + "%"
|
||||||
|
if forecast.units == "aviation":
|
||||||
|
if forecast.wind_deg == None:
|
||||||
|
forecast.wind_deg = ""
|
||||||
|
elif len(forecast.wind_deg) == 1:
|
||||||
|
forecast.wind_deg = "00" + forecast.wind_deg
|
||||||
|
elif len(forecast.wind_deg) == 2:
|
||||||
|
forecast.wind_deg = "0" + forecast.wind_deg
|
||||||
|
if int(forecast.wind_speed) < 10:
|
||||||
|
windspeed = forecast.wind_deg + "@" + "0" + forecast.wind_speed + \
|
||||||
|
self.__get_unit__(
|
||||||
|
("", "")) # added degrees, if wind<10 add a 0 to make two digit
|
||||||
|
else:
|
||||||
|
windspeed = forecast.wind_deg + "@" + forecast.wind_speed + \
|
||||||
|
self.__get_unit__(("", "")) # added degrees
|
||||||
|
else:
|
||||||
|
windspeed = forecast.wind_speed + " " + \
|
||||||
|
self.__get_unit__(("km/h", "mph"))
|
||||||
|
|
||||||
|
numbers_list = [[forecast.short_description],
|
||||||
|
[temperature],
|
||||||
|
[humidity],
|
||||||
|
[windspeed]]
|
||||||
|
|
||||||
def __draw_info__ (self, info):
|
|
||||||
height = info_x_height * self.size[0]
|
|
||||||
ypos = info_x_ypos * self.size[0]
|
ypos = info_x_ypos * self.size[0]
|
||||||
size = (self.size[0], height)
|
|
||||||
pos = (0, ypos)
|
pos = (0, ypos)
|
||||||
|
size = (self.size[0], self.size[1] +
|
||||||
|
info_yresize * self.size[1] - pos[1])
|
||||||
|
line_spacing = (size[1] - len(numbers_list) *
|
||||||
|
fontsize_static) / (len(numbers_list) + 1)
|
||||||
|
|
||||||
txt = TextDesign(size, text=info, fontsize=height, horizontalalignment="center")
|
table = TableDesign(size, numbers_list, fontsize=fontsize_static, line_spacing=line_spacing, column_horizontal_alignments=[
|
||||||
txt.pos = pos
|
"center"], max_col_size=[size[0]], truncate_text=False, truncate_rows=False)
|
||||||
self.draw_design(txt)
|
|
||||||
|
|
||||||
def __draw_number_symbols__ (self):
|
|
||||||
symbols = [ tempicon,
|
|
||||||
humicon,
|
|
||||||
windicon ]
|
|
||||||
|
|
||||||
ypos = numbers_x_ypos * self.size[0]
|
|
||||||
fontsize = fontsize_y * self.size[1]
|
|
||||||
line_spacing = (self.size[1] - ypos - len(symbols) * fontsize) / len(symbols)
|
|
||||||
line_height = fontsize + line_spacing
|
|
||||||
|
|
||||||
width = line_height
|
|
||||||
max_width = max_symbol_y_width * self.size[1]
|
|
||||||
if width > max_width:
|
|
||||||
width = max_width
|
|
||||||
size = (width, width)
|
|
||||||
pos = (0, ypos)
|
|
||||||
|
|
||||||
for index, symbol in enumerate(symbols):
|
|
||||||
sym_ypos = pos[1] + line_height * index
|
|
||||||
self.__draw_resized_image_at__(symbol, (0, sym_ypos), size)
|
|
||||||
|
|
||||||
def __draw_numbers__ (self, forecast):
|
|
||||||
temperature = forecast.air_temperature + " " + self.__get_unit__(("°C", "°F"))
|
|
||||||
humidity = forecast.air_humidity + "%"
|
|
||||||
windspeed = forecast.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
|
|
||||||
|
|
||||||
numbers_list = [ [ temperature ],
|
|
||||||
[ humidity ],
|
|
||||||
[ windspeed ] ]
|
|
||||||
|
|
||||||
fontsize = fontsize_y * self.size[1]
|
|
||||||
ypadding = numbers_x_ypadding * self.size[0]
|
|
||||||
ypos = numbers_x_ypos * self.size[0]
|
|
||||||
line_spacing = (self.size[1] - ypos - len(numbers_list) * fontsize) / len(numbers_list)
|
|
||||||
symbol_width = fontsize + line_spacing
|
|
||||||
max_symbol_width = max_symbol_y_width * self.size[1]
|
|
||||||
if symbol_width > max_symbol_width:
|
|
||||||
symbol_width = max_symbol_width
|
|
||||||
xpos = symbol_width
|
|
||||||
pos = (xpos, ypos + ypadding)
|
|
||||||
size = (self.size[0] - pos[0], self.size[1] - pos[1])
|
|
||||||
|
|
||||||
table = TableTextDesign(size, numbers_list, fontsize=fontsize, line_spacing=line_spacing)
|
|
||||||
table.pos = pos
|
table.pos = pos
|
||||||
self.draw_design(table)
|
self.draw_design(table)
|
||||||
|
|
||||||
def __draw_numbers_text__ (self, forecast):
|
def __draw_icon__(self, icon_id):
|
||||||
temperature = forecast.air_temperature + " " + self.__get_unit__(("°C", "°F"))
|
|
||||||
humidity = forecast.air_humidity + "%"
|
|
||||||
windspeed = forecast.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
|
|
||||||
sunrise = self.__get_time__(forecast.sunrise)
|
|
||||||
sunset = self.__get_time__(forecast.sunset)
|
|
||||||
|
|
||||||
numbers_list = [ [ temperature ],
|
|
||||||
[ humidity ],
|
|
||||||
[ windspeed ] ]
|
|
||||||
|
|
||||||
fontsize = fontsize_y * self.size[1]
|
|
||||||
ypos = numbers_x_ypos * self.size[0]
|
|
||||||
line_spacing = (self.size[1] - ypos - len(numbers_list) * fontsize) / len(numbers_list)
|
|
||||||
pos = (0, ypos)
|
|
||||||
size = (self.size[0], self.size[1] - pos[1])
|
|
||||||
|
|
||||||
table = TableTextDesign(size, numbers_list, fontsize=fontsize, line_spacing=line_spacing, column_horizontal_alignments=[ "center" ], max_col_size=[ size[0] ])
|
|
||||||
table.pos = pos
|
|
||||||
self.draw_design(table)
|
|
||||||
|
|
||||||
def __draw_icon__ (self, icon_id):
|
|
||||||
width = int(icon_width * self.size[0])
|
width = int(icon_width * self.size[0])
|
||||||
size = (width, width)
|
size = (width, width)
|
||||||
xpos = icon_xpos * self.size[0]
|
xpos = icon_xpos * self.size[0]
|
||||||
ypos = icon_x_ypos * self.size[0]
|
ypos = icon_x_ypos * self.size[0]
|
||||||
pos = (xpos, ypos)
|
pos = (xpos, ypos)
|
||||||
|
|
||||||
self.__draw_resized_path_at__(wpath + weathericons[icon_id] + ".jpeg", pos, size)
|
self.__draw_resized_path_at__(
|
||||||
|
wpath + weathericons[icon_id] + ".jpeg", pos, size)
|
||||||
|
|
||||||
def __draw_no_response__ (self):
|
def __draw_no_response__(self):
|
||||||
width = int(icon_width * self.size[0])
|
width = int(icon_width * self.size[0])
|
||||||
size = (width, width)
|
size = (width, width)
|
||||||
xpos = icon_xpos * self.size[0]
|
xpos = icon_xpos * self.size[0]
|
||||||
|
@ -130,27 +87,26 @@ class WeatherColumnDesign (DesignEntity):
|
||||||
|
|
||||||
self.__draw_resized_image_at__(no_response, pos, size)
|
self.__draw_resized_image_at__(no_response, pos, size)
|
||||||
|
|
||||||
def __draw_resized_path_at__ (self, path, pos, size):
|
def __draw_resized_path_at__(self, path, pos, size):
|
||||||
img = Image.open(path)
|
img = Image.open(path)
|
||||||
self.__draw_resized_image_at__(img, pos, size)
|
self.__draw_resized_image_at__(img, pos, size)
|
||||||
|
|
||||||
def __draw_resized_image_at__ (self, img, pos, size):
|
def __draw_resized_image_at__(self, img, pos, size):
|
||||||
size = (int(size[0]), int(size[1]))
|
size = (int(size[0]), int(size[1]))
|
||||||
resized_img = img.resize(size, resample=Image.LANCZOS)
|
resized_img = img.resize(size, resample=Image.LANCZOS)
|
||||||
self.draw(resized_img, pos)
|
self.draw(resized_img, pos)
|
||||||
|
|
||||||
|
def __get_unit__(self, tuple):
|
||||||
def __get_unit__ (self, tuple):
|
if self.forecast.units == "metric" or self.forecast.units == "aviation":
|
||||||
if self.forecast.units == "metric":
|
|
||||||
return tuple[0]
|
return tuple[0]
|
||||||
else:
|
else:
|
||||||
return tuple[1]
|
return tuple[1]
|
||||||
|
|
||||||
def __abs_co__ (self, coordinates):
|
def __abs_co__(self, coordinates):
|
||||||
return (coordinates[0] * self.size[0], coordinates[1] * self.size[1])
|
return (coordinates[0] * self.size[0], coordinates[1] * self.size[1])
|
||||||
|
|
||||||
def __get_time__ (self, time):
|
def __get_time__(self, time):
|
||||||
if hours == "24":
|
if hours == "24":
|
||||||
return time.strftime('%H:%M')
|
return time.strftime('%H:%M')
|
||||||
else:
|
else:
|
||||||
return time.strftime('%I:%M')
|
return time.strftime('%I:%M')
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
class WeatherForecast (object):
|
class WeatherForecast (object):
|
||||||
"""Defines a weather forecast, independent of any implementation"""
|
"""Defines a weather forecast, independent of any implementation"""
|
||||||
def __init__ (self):
|
|
||||||
|
def __init__(self):
|
||||||
self.air_temperature = None
|
self.air_temperature = None
|
||||||
self.air_pressure = None
|
self.air_pressure = None
|
||||||
self.air_humidity = None
|
self.air_humidity = None
|
||||||
|
@ -11,6 +12,7 @@ class WeatherForecast (object):
|
||||||
self.sunset = None
|
self.sunset = None
|
||||||
self.moon_phase = None
|
self.moon_phase = None
|
||||||
self.wind_speed = None
|
self.wind_speed = None
|
||||||
|
self.wind_deg = None
|
||||||
self.clouds = None
|
self.clouds = None
|
||||||
|
|
||||||
self.icon = None
|
self.icon = None
|
||||||
|
@ -20,4 +22,4 @@ class WeatherForecast (object):
|
||||||
self.units = None
|
self.units = None
|
||||||
self.datetime = None
|
self.datetime = None
|
||||||
self.location = None
|
self.location = None
|
||||||
self.fetch_datetime = None
|
self.fetch_datetime = None
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from DesignEntity import DesignEntity
|
from DesignEntity import DesignEntity
|
||||||
from Assets import *
|
from Assets import no_response, windicon, tempicon, sunseticon, sunriseicon, wpath, weathericons, humicon
|
||||||
from TextDesign import TextDesign
|
from TextDesign import TextDesign
|
||||||
from settings import units, hours
|
from settings import units, hours
|
||||||
|
|
||||||
|
@ -10,56 +10,86 @@ windiconspace = (0.206, 0)
|
||||||
sunriseplace = (0.55, 0)
|
sunriseplace = (0.55, 0)
|
||||||
sunsetplace = (0.55, 0.486)
|
sunsetplace = (0.55, 0.486)
|
||||||
|
|
||||||
|
|
||||||
class WeatherHeaderDesign (DesignEntity):
|
class WeatherHeaderDesign (DesignEntity):
|
||||||
"""Defines a top area that displays basic weather information"""
|
"""Defines a top area that displays basic weather information"""
|
||||||
def __init__ (self, size, weather):
|
|
||||||
|
def __init__(self, size, weather):
|
||||||
super(WeatherHeaderDesign, self).__init__(size)
|
super(WeatherHeaderDesign, self).__init__(size)
|
||||||
self.__weather__ = weather
|
self.__weather__ = weather
|
||||||
self.__first_render__()
|
self.__first_render__()
|
||||||
|
|
||||||
def __first_render__ (self):
|
def __first_render__(self):
|
||||||
if self.__weather__.is_available() is False:
|
if self.__weather__.is_available() is False:
|
||||||
self.__render_missing_connection__()
|
self.__render_missing_connection__()
|
||||||
return
|
return
|
||||||
|
|
||||||
cur_weather = self.__weather__.get_today_forecast()
|
cur_weather = self.__weather__.get_today_forecast()
|
||||||
|
|
||||||
temperature = cur_weather.air_temperature + " " + self.__get_unit__(("°C", "°F"))
|
if cur_weather == None:
|
||||||
windspeed = cur_weather.wind_speed + " " + self.__get_unit__(("km/h", "mph"))
|
self.__render_missing_connection__()
|
||||||
|
return
|
||||||
|
|
||||||
self.__draw_text__(temperature, self.__abs_pos__((0.87, 0)), (50,35))
|
temperature = cur_weather.air_temperature + \
|
||||||
self.__draw_text__(windspeed, self.__abs_pos__((0.297, 0.05)), (100,35))
|
" " + self.__get_unit__(("°C", "°F"))
|
||||||
self.__draw_text__(self.__get_time__(cur_weather.sunrise), self.__abs_pos__((0.64,0)), (50,35))
|
if units == "aviation": # pick up aviation
|
||||||
self.__draw_text__(self.__get_time__(cur_weather.sunset), self.__abs_pos__((0.64,0.486)), (50,35))
|
if cur_weather.wind_deg == None:
|
||||||
self.__draw_text__(cur_weather.air_humidity + " %", self.__abs_pos__((0.87,0.486)), (50,35))
|
cur_weather.wind_deg = ""
|
||||||
self.__draw_text__(cur_weather.short_description, self.__abs_pos__((0.182,0.486)), (144,35))
|
elif len(cur_weather.wind_deg) == 1: # if deg is 2, add two zeros for format
|
||||||
|
cur_weather.wind_deg = "00" + cur_weather.wind_deg
|
||||||
|
elif len(cur_weather.wind_deg) == 2:
|
||||||
|
cur_weather.wind_deg = "0" + cur_weather.wind_deg
|
||||||
|
if int(cur_weather.wind_speed) < 10:
|
||||||
|
windspeed = cur_weather.wind_deg + "@" + "0" + cur_weather.wind_speed + \
|
||||||
|
self.__get_unit__(
|
||||||
|
("", "")) # added degrees, if wind<10 add a 0 to make two digit
|
||||||
|
else:
|
||||||
|
windspeed = cur_weather.wind_deg + "@" + cur_weather.wind_speed + \
|
||||||
|
self.__get_unit__(("", "")) # added degrees
|
||||||
|
else:
|
||||||
|
windspeed = cur_weather.wind_speed + " " + \
|
||||||
|
self.__get_unit__(("km/h", "mph"))
|
||||||
|
|
||||||
|
self.__draw_text__(temperature, self.__abs_pos__((0.87, 0)), (50, 35))
|
||||||
|
self.__draw_text__(windspeed, self.__abs_pos__(
|
||||||
|
(0.297, 0.05)), (100, 35))
|
||||||
|
self.__draw_text__(self.__get_time__(cur_weather.sunrise),
|
||||||
|
self.__abs_pos__((0.64, 0)), (50, 35))
|
||||||
|
self.__draw_text__(self.__get_time__(cur_weather.sunset),
|
||||||
|
self.__abs_pos__((0.64, 0.486)), (50, 35))
|
||||||
|
self.__draw_text__(cur_weather.air_humidity + " %",
|
||||||
|
self.__abs_pos__((0.87, 0.486)), (50, 35))
|
||||||
|
self.__draw_text__(cur_weather.short_description,
|
||||||
|
self.__abs_pos__((0.182, 0.486)), (144, 35))
|
||||||
|
|
||||||
self.draw(windicon, self.__abs_pos__(windiconspace))
|
self.draw(windicon, self.__abs_pos__(windiconspace))
|
||||||
self.draw(sunseticon, self.__abs_pos__(sunsetplace))
|
self.draw(sunseticon, self.__abs_pos__(sunsetplace))
|
||||||
self.draw(sunriseicon, self.__abs_pos__(sunriseplace))
|
self.draw(sunriseicon, self.__abs_pos__(sunriseplace))
|
||||||
self.draw(humicon, self.__abs_pos__(humplace))
|
self.draw(humicon, self.__abs_pos__(humplace))
|
||||||
self.draw(tempicon, self.__abs_pos__(tempplace))
|
self.draw(tempicon, self.__abs_pos__(tempplace))
|
||||||
self.draw_image(wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace))
|
self.draw_image(
|
||||||
|
wpath + weathericons[cur_weather.icon] + '.jpeg', self.__abs_pos__(wiconplace))
|
||||||
|
|
||||||
def __render_missing_connection__ (self):
|
def __render_missing_connection__(self):
|
||||||
self.draw_image(no_response, self.__abs_pos__(wiconplace))
|
self.draw(no_response, self.__abs_pos__(wiconplace))
|
||||||
|
|
||||||
def __abs_pos__ (self, pos):
|
def __abs_pos__(self, pos):
|
||||||
return (int(pos[0] * self.size[0]), int(pos[1] * self.size[1]))
|
return (int(pos[0] * self.size[0]), int(pos[1] * self.size[1]))
|
||||||
|
|
||||||
def __draw_text__ (self, text, pos, size):
|
def __draw_text__(self, text, pos, size):
|
||||||
txt = TextDesign(size, fontsize=18, text=text, verticalalignment="center", horizontalalignment="center")
|
txt = TextDesign(size, fontsize=18, text=text,
|
||||||
|
verticalalignment="center", horizontalalignment="center")
|
||||||
txt.pos = pos
|
txt.pos = pos
|
||||||
self.draw_design(txt)
|
self.draw_design(txt)
|
||||||
|
|
||||||
def __get_unit__ (self, tuple):
|
def __get_unit__(self, tuple):
|
||||||
if units == "metric":
|
if units == "metric" or units == "aviation":
|
||||||
return tuple[0]
|
return tuple[0]
|
||||||
else:
|
else:
|
||||||
return tuple[1]
|
return tuple[1]
|
||||||
|
|
||||||
def __get_time__ (self, time):
|
def __get_time__(self, time):
|
||||||
if hours == "24":
|
if hours == "24":
|
||||||
return time.strftime('%H:%M')
|
return time.strftime('%H:%M')
|
||||||
else:
|
else:
|
||||||
return time.strftime('%I:%M')
|
return time.strftime('%I:%M')
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
from DataSourceInterface import DataSourceInterface
|
from DataSourceInterface import DataSourceInterface
|
||||||
|
|
||||||
|
|
||||||
class WeatherInterface (DataSourceInterface):
|
class WeatherInterface (DataSourceInterface):
|
||||||
"""Interface for fetching and processing weather forecast information."""
|
"""Interface for fetching and processing weather forecast information."""
|
||||||
def get_forecast_in_days (self, offset_by_days, location=None):
|
|
||||||
|
def get_forecast_in_days(self, offset_by_days, location=None):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
||||||
def get_today_forecast (self, location=None):
|
def get_today_forecast(self, location=None):
|
||||||
raise NotImplementedError("Functions needs to be implemented")
|
raise NotImplementedError("Functions needs to be implemented")
|
||||||
|
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 5.6 KiB |
|
@ -1,37 +1,48 @@
|
||||||
""" To quickly get started, fill in the following details:"""
|
""" To quickly get started, fill in the following details:"""
|
||||||
|
|
||||||
ical_urls = [
|
ical_urls = [
|
||||||
"https://calendar.google.com/calendar/ical/en.usa%23holiday%40group.v.calendar.google.com/public/basic.ics"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
highlighted_ical_urls = [
|
highlighted_ical_urls = [
|
||||||
]
|
]
|
||||||
|
|
||||||
rss_feeds = [
|
rss_feeds = [
|
||||||
"http://feeds.bbci.co.uk/news/world/rss.xml#"
|
]
|
||||||
|
|
||||||
|
crypto_coins = [
|
||||||
]
|
]
|
||||||
|
|
||||||
api_key = ""
|
api_key = ""
|
||||||
owm_paid_subscription = False
|
owm_paid_subscription = False
|
||||||
location = "Julich, DE"
|
location = "Berlin, DE"
|
||||||
week_starts_on = "Monday"
|
week_starts_on = "Monday"
|
||||||
display_colours = "bwr"
|
display_colours = "bwr"
|
||||||
language = "en"
|
language = "en"
|
||||||
units = "metric"
|
datetime_encoding = "UTF-8" # UTF-8
|
||||||
|
units = "metric" # aviation (celcius,degrees/knots), metric (celcius,kmh), imperial(f,mph)
|
||||||
hours = "24"
|
hours = "24"
|
||||||
update_interval = "60"
|
update_interval = 60
|
||||||
|
max_loop_count = 0 # From 0 to 86400 representing the maximum number the loop is executed, with 0 being unlimited
|
||||||
|
run_on_hour = True # If True, updates calendar every full hour, ignoring differing update_interval
|
||||||
|
|
||||||
|
|
||||||
"""DESIGN"""
|
"""DESIGN"""
|
||||||
|
font_size = 14 # does not affect every text
|
||||||
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
|
font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold
|
||||||
choosen_design = "month-overview" # month-overview, day-list
|
line_thickness = 1 # 1-3 Thickness advised
|
||||||
|
choosen_design = "month-overview" # month-overview, day-list, day-view, day-focus-list, agenda-list, month-view, image-frame
|
||||||
general_settings = { # General settings that designs may use
|
general_settings = { # General settings that designs may use
|
||||||
"info-area" : "rss", # empty, events, rss
|
"info-area" : "rss", # empty, events, rss, crypto
|
||||||
"highlight-event-days" : True
|
"highlight-event-days" : True,
|
||||||
|
"weather-info" : True,
|
||||||
|
"image-folder" : "",
|
||||||
|
"overlay-image" : "", # Size must be 384x640px with default display
|
||||||
|
"extra-excluded-urls" : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
"""DEBUG"""
|
"""DEBUG"""
|
||||||
render_to_display = True
|
render_to_display = True
|
||||||
render_to_file = False # Will be called "design_exported.png" in Calendar-directory
|
render_to_file = False # Will be called "design_exported.png" in Calendar-directory
|
||||||
calibrate_hours = [0, 12, 18]
|
calibrate_hours = [0, 12, 18]
|
||||||
|
print_technical_data = False
|
||||||
|
|
15
Changelog.md
|
@ -4,6 +4,21 @@ The order is from latest to oldest and structured in the following way:
|
||||||
* Version name with date of publishing
|
* Version name with date of publishing
|
||||||
* Sections with either 'added', 'fixed', 'updated' and 'changed'
|
* Sections with either 'added', 'fixed', 'updated' and 'changed'
|
||||||
|
|
||||||
|
## [1.7] Mid April 2019
|
||||||
|
|
||||||
|
### Added
|
||||||
|
* Added agenda-list panel
|
||||||
|
* Added day-view panel
|
||||||
|
* Added offline calendar and rss support
|
||||||
|
* Added more settings
|
||||||
|
* Added support for multi-day and repeating events
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* Improved internal event management
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* Bugs
|
||||||
|
|
||||||
## [1.6] Mid March 2019
|
## [1.6] Mid March 2019
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
BIN
Gallery/agenda-list_example.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
Gallery/day-focus-list_example.png
Normal file
After Width: | Height: | Size: 26 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
BIN
Gallery/day-view_example.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
Gallery/image-frame_example.png
Normal file
After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
@ -21,7 +21,7 @@ if [ -z "$option" ]; then
|
||||||
fi
|
fi
|
||||||
if [ "$option" = 3 ]; then
|
if [ "$option" = 3 ]; then
|
||||||
echo -e "Removing the E-Paper software now..."
|
echo -e "Removing the E-Paper software now..."
|
||||||
pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y
|
pip3 uninstall feedparser -y && sudo pip3 uninstall feedparser -y && pip3 uninstall numpy -y && sudo pip3 uninstall numpy -y && pip3 uninstall Pillow -y && sudo pip3 uninstall Pillow -y && sudo pip3 uninstall pyowm -y&& sudo pip3 uninstall ics -y && pip3 uninstall pyowm -y && pip3 uninstall ics -y && sudo apt-get remove supervisor -y && sudo apt-get clean && sudo apt-get autoremove -y
|
||||||
if [ -e /etc/supervisor/conf.d/E-Paper.conf ]; then
|
if [ -e /etc/supervisor/conf.d/E-Paper.conf ]; then
|
||||||
sudo rm /etc/supervisor/conf.d/E-Paper.conf
|
sudo rm /etc/supervisor/conf.d/E-Paper.conf
|
||||||
fi
|
fi
|
||||||
|
@ -81,8 +81,12 @@ if [ "$option" = 2 ]; then
|
||||||
echo -e "\e[1;36m"Installing a few required packages for the E-Paper Software"\e[0m"
|
echo -e "\e[1;36m"Installing a few required packages for the E-Paper Software"\e[0m"
|
||||||
sudo pip3 install pyowm
|
sudo pip3 install pyowm
|
||||||
sudo pip3 install ics
|
sudo pip3 install ics
|
||||||
|
sudo pip3 install feedparser
|
||||||
|
sudo pip3 install numpy
|
||||||
pip3 install pyowm
|
pip3 install pyowm
|
||||||
pip3 install ics
|
pip3 install ics
|
||||||
|
pip3 install feedparser
|
||||||
|
pip3 install numpy
|
||||||
echo -e "\e[1;36m"Finished installing libraries"\e[0m"
|
echo -e "\e[1;36m"Finished installing libraries"\e[0m"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
### This file contains features in planning for the next release
|
### This file contains features in planning for the next release
|
||||||
|
|
||||||
# For version 1.7
|
# For version 1.8
|
||||||
|
|
||||||
## Installer
|
## Installer
|
||||||
* Optimise the installer by adding a few more options when updating
|
* Optimise the installer by adding a few more options when updating
|
||||||
|
|
||||||
## Main script
|
## Main script
|
||||||
* Implement weekly view (may take a lot of time)
|
* Implement multi-calendar panel
|
||||||
* Implement feature to fetch tasks
|
* Implement feature to fetch tasks
|
||||||
* Add code in E-Paper.py for fixing errors related to the iCalendar (ics.py is pretty picky)
|
|
||||||
* Fix a bug where past events are shown along with ones in the future
|
|
||||||
* Add support for ics files along with iCalendar URLs
|
* Add support for ics files along with iCalendar URLs
|
||||||
* Allow connecting to the openweathermap API servers even when the SSL certificate has expired
|
* Allow connecting to the openweathermap API servers even when the SSL certificate has expired
|
||||||
* Try using the weathericons.io font instead of icons
|
* Try using the weathericons.io font instead of icons
|
||||||
|
|
64
README.md
|
@ -1,27 +1,36 @@
|
||||||
|
# NOT ACTIVELY MAINTAINED ANY LONGER
|
||||||
|
Since [aceisace/Inkycal](https://github.com/aceisace/Inkycal) has grown significantly and is far superior in many ways, I will no longer focus on mainting my own fork. The ical implementation might also not be super reliable in this project.
|
||||||
|
|
||||||
# E-Paper Calendar
|
# E-Paper Calendar
|
||||||
|
|
||||||
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online.
|
This is a software written in python3 that allows you to transform an E-Paper display (like the kindle) into an information display. It fetches live data from Openweathermap (a weather info provider), rss-feeds and your Online Calendar (Google/Yahoo Calendar/...) and displays them on a large, beautiful and ultra-low power E-Paper display. It's ideal for staying organised and keeping track of important details without having to check them up online. It can also be used as an image frame.
|
||||||
|
|
||||||
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
|
This software fully supports the 3-Colour **and** 2-Colour version of the 7.5" E-Paper display from waveshare/gooddisplay and works with Raspberry Pi 2, 3 and 0 (Zero, Zero W, Zero WH).
|
||||||
|
|
||||||
**To get started, follow the instructions below.**
|
**To get started, follow the instructions below.**
|
||||||
|
|
||||||
## News:
|
## News:
|
||||||
|
* **Version 1.7 released with two new designs, offline support and improved support for events** (Mid April 2019)
|
||||||
* **Specified README for this fork, added new design and many options** (Mid March 2019)
|
* **Specified README for this fork, added new design and many options** (Mid March 2019)
|
||||||
* **Version 1.5 released with a new layout, displayed events and many back-end improvements** (Early February 2019)
|
* **Version 1.5 released with a new layout, displayed events and many back-end improvements** (Early February 2019)
|
||||||
* **Added Support for the 2-Colour E-Paper Display as well!** (Late September 2018)
|
* **Added Support for the 2-Colour E-Paper Display as well!** (Late September 2018)
|
||||||
* **Added Support for Raspbian Stretch lite.** (Late September 2018)
|
* **Added Support for Raspbian Stretch lite.** (Late September 2018)
|
||||||
|
|
||||||
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/day-list_example.png" width="400"><img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/month-overview_example.png" width="400">
|
<img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/month-overview_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/agenda-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-view_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/day-focus-list_example.png" width="270"><img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/image-frame_example.png" width="270">
|
||||||
|
|
||||||
Left: Day-List Panel
|
1.: Day-List Panel 
|
||||||
Right: Month-Overview Panel
|
2.: Month-Overview Panel 
|
||||||
|
3.: Agenda-List Panel<br>
|
||||||
|
4.: Day-View Panel 
|
||||||
|
5.: Day-Focus-List Panel 
|
||||||
|
6.: Image-Frame Panel
|
||||||
|
|
||||||
## Main features
|
## Main features
|
||||||
* Display the date and a full monthly calendar or a list of today and the next days
|
* Display a calendar with one of multiple designes
|
||||||
* Optionally get RSS-Feed fetched and shown
|
* Optionally get RSS-Feed fetched and shown
|
||||||
* Syncronise events from any online calendar (like google, yahoo etc.)
|
* Syncronise events from any online calendar (like google, yahoo, etc.)
|
||||||
* Get live weather data (including temperature, humidity, etc.) using openweathermap api
|
* Get live weather data (including temperature, humidity, etc.) using openweathermap api
|
||||||
|
* Only show a slideshow of images
|
||||||
|
|
||||||
## Hardware required
|
## Hardware required
|
||||||
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
|
* 7.5" 3-Colour E-Paper Display (Black, White, Red/Yellow) with driver hat from [waveshare](https://www.waveshare.com/product/7.5inch-e-paper-hat-b.htm)
|
||||||
|
@ -54,33 +63,52 @@ If the Installer should fail for any reason, kindly open an issue and paste the
|
||||||
|
|
||||||
This is how the installer will run:
|
This is how the installer will run:
|
||||||
|
|
||||||
<img src="https://github.com/mgfcf/E-Paper-Calendar/blob/master/Gallery/Installer-v1.2-screenshot.png" width="700">
|
<img src="https://code.giller.dev/m.giller/E-Paper-Calendar/raw/branch/master/Gallery/Installer-v1.2-screenshot.png" width="700">
|
||||||
|
|
||||||
## Adding details to the programm
|
## Adding details to the programm
|
||||||
Once the packages are installed, navigate to the home directory, open 'E-Paper-Master' and open the file 'settings.py' inside the Calendar folder. Adjust the values as needed. You can use the table below as a reference.
|
Once the packages are installed, navigate to the home directory, open 'E-Paper-Master' and open the file 'settings.py' inside the Calendar folder. Adjust the values as needed. You can use the table below as a reference.
|
||||||
|
|
||||||
|
### General
|
||||||
| Parameter | Description |
|
| Parameter | Description |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma. |
|
| ical_urls | Your iCalendar URL/s. To add more than one URL, seperate each with a comma. |
|
||||||
| highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison the ical_urls. To add more than one URL, seperate each with a comma. |
|
| highlighted_ical_urls | Your iCalendar URL/s that should be higlighted in comparison to the ical_urls. To add more than one URL, seperate each with a comma. |
|
||||||
| rss_feeds | All the sources for your rss-feed. To add more than one URL, seperate each with a comma. |
|
| rss_feeds | All the sources for your rss-feed. To add more than one URL, seperate each with a comma. |
|
||||||
| api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info. |
|
| api_key | Your __personal__ openweathermap API-key which you can generate and find in your Account info. |
|
||||||
| owm_paid_subscription | If you have a paid owm subscription you can set it to `true` and in some panels receive forecast information. |
|
| owm_paid_subscription | If you have a paid owm subscription you can set it to `True` and in some panels receive forecast information. |
|
||||||
| location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). |
|
| location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). |
|
||||||
| week_starts_on | When does the work start on your Region? Possible options are `"Monday"` or `"Sunday"`. |
|
| week_starts_on | When does the work start in your region? Possible options are `"Monday"` or `"Sunday"`. |
|
||||||
| display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.|
|
| display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.|
|
||||||
| language | Choosing the language allows changing the language of the month and week-icons. Possible options are `"en"` for english and `"de"` for german.|
|
| language | Sets the language and the related locale for datetime-information. Some texts depend on additional translations that can be added to the dictionary-file.|
|
||||||
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"` or `"imperial"`. |
|
| datetime_encoding | Sets the encoding that will be used in the datetime-texts (month, weekday, ...). Default is `"UTF-8"`. Not to confuse with the full locale, this is only the encoding. |
|
||||||
|
|units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"`, `"imperial"` or `"aviation"` (Celsius, Degree/Knots). |
|
||||||
|hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.|
|
|hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.|
|
||||||
|update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.|
|
|update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.|
|
||||||
|
|max_loop_count | From 0 to 86400 representing the maximum number of times the loop is going to be executed, with `0` being unlimited.|
|
||||||
|
|run_on_hour | If `True`, updates calendar every full hour, ignoring differing update_interval.|
|
||||||
|
|
||||||
## iCalendar
|
### Design
|
||||||
It is a bit tricky to set up the iCalendar so it works correctly without throwing any errors. If you encounter errors related to your iCalendar, please open up an issue and paste the error message there.
|
| Parameter | Description |
|
||||||
|
| --- | --- |
|
||||||
|
| font_size | Varies the size of the font. Can be any number >0. In some cases the size of the font is fixed by the design. Default value is `14`. |
|
||||||
|
| font_boldness | Varies the boldness of the font. Can be one of `extralight`, `light`, `regular`, `semibold`, `bold` or `extrabold`. In some cases the boldness of the font is fixed by the design. Default value is `regular`. |
|
||||||
|
| choosen_design | Sets the desired panel design. |
|
||||||
|
| line_thickness | Varies the boldness of separation lines in the chosen design. |
|
||||||
|
| general_settings | A dictionary containing options that some designs use to optimize there design. Possible options are as follows: |
|
||||||
|
| `"info-area"` | Defines the content type of an additionaly info area on the design. Can be one of `"rss"`, `"events"` or empty, to remove this area or keep it clean. |
|
||||||
|
| `"highlight-event-days"` | If set to `True`, days with events are highlighted in contrast to days without events. |
|
||||||
|
| `"weather-info"` | If set to `False`, weather info areas disappear and make room for events/rss/etc. (depends on the design). |
|
||||||
|
| `"image-folder"` | Set a relative or absolute path to a folder containing images that you want to see fullscreen with the `"image-frame"` design activated. |
|
||||||
|
| `"overlay-image"` | Set a relative or absolute path to an image with the same size as the screen (default: 384x640px) to show some static information over every image shown in the `"image-frame"` design. If the overlay image is contained within the `"image-folder"`, it will not be included into the slideshow. |
|
||||||
|
| `"extra-excluded-urls"` | A list of calendar urls that may be excluded in some panels in certain areas. |
|
||||||
|
|
||||||
A more detailed section about the iCalendar will be added to the wiki soon, but for now, here are some suggestions to prevent error messages:
|
### Debug
|
||||||
1) Ensure your iCalendar URL is fine. If you receive an error showing error 404, it means the URL is wrong.
|
| Parameter | Description |
|
||||||
2) If your existing iCalendar doesn't work at all, export the Calendar as a file, then create a new Calendar and import the file from before.
|
| --- | --- |
|
||||||
3) If you receive errors related to 'alarm' or 'trigger', please make sure your iCalendar does not use reminders. The problem is that some actions are not supported by the Raspberry and cause errors. For example, the Rapsberry can't send a mail, make a noise or display a message as soon as an event starts.
|
| render_to_display | Set to `True` it adds the e-paper display to the outputs. |
|
||||||
|
| render_to_file | Set to `True` it adds a image-file export to the outputs. The exported image can be found in `"/Calendar/design_exported.png"`. |
|
||||||
|
| calibrate_hours | A list containing all the hours in which the outputs get calibrated. That should prevent ghosting on e-paper displays. Takes a little while to execute. |
|
||||||
|
| print_technical_data | Set to `True` it writes start and stop time of the current update cyle and the duration on top of the panel. |
|
||||||
|
|
||||||
## Updating
|
## Updating
|
||||||
If you want to update to the latest version, run the Installer from above again and select the 'update' option.
|
If you want to update to the latest version, run the Installer from above again and select the 'update' option.
|
||||||
|
|