Merge branch 'main' into feature/#311
This commit is contained in:
@@ -75,6 +75,8 @@ class Agenda(inkycal_module):
|
||||
# Additional config
|
||||
self.timezone = get_system_tz()
|
||||
|
||||
self.icon_font = ImageFont.truetype(fonts['MaterialIcons'], size=self.fontsize)
|
||||
|
||||
# give an OK message
|
||||
logger.debug(f'{__name__} loaded')
|
||||
|
||||
@@ -201,10 +203,10 @@ class Agenda(inkycal_module):
|
||||
write(im_black, (x_time, line_pos[cursor][1]),
|
||||
(time_width, line_height), time,
|
||||
font=self.font, alignment='right')
|
||||
if parser.all_day(_):
|
||||
else:
|
||||
write(im_black, (x_time, line_pos[cursor][1]),
|
||||
(time_width, line_height), "all day",
|
||||
font=self.font, alignment='right')
|
||||
(time_width, line_height), "\ue878",
|
||||
font=self.icon_font, alignment='right')
|
||||
|
||||
write(im_black, (x_event, line_pos[cursor][1]),
|
||||
(event_width, line_height),
|
||||
|
@@ -6,16 +6,16 @@ Copyright by aceinnolab
|
||||
# pylint: disable=logging-fstring-interpolation
|
||||
|
||||
import calendar as cal
|
||||
import arrow
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
from inkycal.custom import *
|
||||
from inkycal.modules.template import inkycal_module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Calendar(inkycal_module):
|
||||
"""Calendar class
|
||||
Create monthly calendar and show events from given icalendars
|
||||
Create monthly calendar and show events from given iCalendars
|
||||
"""
|
||||
|
||||
name = "Calendar - Show monthly calendar with events from iCalendars"
|
||||
@@ -39,12 +39,12 @@ class Calendar(inkycal_module):
|
||||
},
|
||||
"date_format": {
|
||||
"label": "Use an arrow-supported token for custom date formatting "
|
||||
+ "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM",
|
||||
+ "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM",
|
||||
"default": "D MMM",
|
||||
},
|
||||
"time_format": {
|
||||
"label": "Use an arrow-supported token for custom time formatting "
|
||||
+ "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm",
|
||||
+ "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm",
|
||||
"default": "HH:mm",
|
||||
},
|
||||
}
|
||||
@@ -61,7 +61,7 @@ class Calendar(inkycal_module):
|
||||
self._days_with_events = None
|
||||
|
||||
# optional parameters
|
||||
self.weekstart = config['week_starts_on']
|
||||
self.week_start = config['week_starts_on']
|
||||
self.show_events = config['show_events']
|
||||
self.date_format = config["date_format"]
|
||||
self.time_format = config['time_format']
|
||||
@@ -109,7 +109,7 @@ class Calendar(inkycal_module):
|
||||
# Allocate space for month-names, weekdays etc.
|
||||
month_name_height = int(im_height * 0.10)
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
weekdays_height = int((text_bbox_height[3] - text_bbox_height[1]) * 1.25)
|
||||
weekdays_height = int((abs(text_bbox_height[3]) + abs(text_bbox_height[1])) * 1.25)
|
||||
logger.debug(f"month_name_height: {month_name_height}")
|
||||
logger.debug(f"weekdays_height: {weekdays_height}")
|
||||
|
||||
@@ -117,7 +117,7 @@ class Calendar(inkycal_module):
|
||||
logger.debug("Allocating space for events")
|
||||
calendar_height = int(im_height * 0.6)
|
||||
events_height = (
|
||||
im_height - month_name_height - weekdays_height - calendar_height
|
||||
im_height - month_name_height - weekdays_height - calendar_height
|
||||
)
|
||||
logger.debug(f'calendar-section size: {im_width} x {calendar_height} px')
|
||||
logger.debug(f'events-section size: {im_width} x {events_height} px')
|
||||
@@ -156,13 +156,13 @@ class Calendar(inkycal_module):
|
||||
|
||||
now = arrow.now(tz=self.timezone)
|
||||
|
||||
# Set weekstart of calendar to specified weekstart
|
||||
if self.weekstart == "Monday":
|
||||
# Set week-start of calendar to specified week-start
|
||||
if self.week_start == "Monday":
|
||||
cal.setfirstweekday(cal.MONDAY)
|
||||
weekstart = now.shift(days=-now.weekday())
|
||||
week_start = now.shift(days=-now.weekday())
|
||||
else:
|
||||
cal.setfirstweekday(cal.SUNDAY)
|
||||
weekstart = now.shift(days=-now.isoweekday())
|
||||
week_start = now.shift(days=-now.isoweekday())
|
||||
|
||||
# Write the name of current month
|
||||
write(
|
||||
@@ -174,9 +174,9 @@ class Calendar(inkycal_module):
|
||||
autofit=True,
|
||||
)
|
||||
|
||||
# Set up weeknames in local language and add to main section
|
||||
# Set up week-names in local language and add to main section
|
||||
weekday_names = [
|
||||
weekstart.shift(days=+_).format('ddd', locale=self.language)
|
||||
week_start.shift(days=+_).format('ddd', locale=self.language)
|
||||
for _ in range(7)
|
||||
]
|
||||
logger.debug(f'weekday names: {weekday_names}')
|
||||
@@ -192,7 +192,7 @@ class Calendar(inkycal_module):
|
||||
fill_height=0.9,
|
||||
)
|
||||
|
||||
# Create a calendar template and flatten (remove nestings)
|
||||
# Create a calendar template and flatten (remove nesting)
|
||||
calendar_flat = self.flatten(cal.monthcalendar(now.year, now.month))
|
||||
# logger.debug(f" calendar_flat: {calendar_flat}")
|
||||
|
||||
@@ -281,7 +281,7 @@ class Calendar(inkycal_module):
|
||||
month_start = arrow.get(now.floor('month'))
|
||||
month_end = arrow.get(now.ceil('month'))
|
||||
|
||||
# fetch events from given icalendars
|
||||
# fetch events from given iCalendars
|
||||
self.ical = iCalendar()
|
||||
parser = self.ical
|
||||
|
||||
@@ -294,14 +294,12 @@ class Calendar(inkycal_module):
|
||||
month_events = parser.get_events(month_start, month_end, self.timezone)
|
||||
parser.sort()
|
||||
self.month_events = month_events
|
||||
|
||||
|
||||
# Initialize days_with_events as an empty list
|
||||
days_with_events = []
|
||||
|
||||
# Handle multi-day events by adding all days between start and end
|
||||
for event in month_events:
|
||||
start_date = event['begin'].date()
|
||||
end_date = event['end'].date()
|
||||
|
||||
# Convert start and end dates to arrow objects with timezone
|
||||
start = arrow.get(event['begin'].date(), tzinfo=self.timezone)
|
||||
@@ -324,9 +322,7 @@ class Calendar(inkycal_module):
|
||||
im_colour,
|
||||
grid[days],
|
||||
(icon_width, icon_height),
|
||||
radius=6,
|
||||
thickness=1,
|
||||
shrinkage=(0, 0),
|
||||
radius=6
|
||||
)
|
||||
|
||||
# Filter upcoming events until 4 weeks in the future
|
||||
@@ -345,13 +341,13 @@ class Calendar(inkycal_module):
|
||||
|
||||
date_width = int(max((
|
||||
self.font.getlength(events['begin'].format(self.date_format, locale=lang))
|
||||
for events in upcoming_events))* 1.1
|
||||
)
|
||||
for events in upcoming_events)) * 1.1
|
||||
)
|
||||
|
||||
time_width = int(max((
|
||||
self.font.getlength(events['begin'].format(self.time_format, locale=lang))
|
||||
for events in upcoming_events))* 1.1
|
||||
)
|
||||
for events in upcoming_events)) * 1.1
|
||||
)
|
||||
|
||||
text_bbox_height = self.font.getbbox("hg")
|
||||
line_height = text_bbox_height[3] + line_spacing
|
||||
@@ -369,7 +365,8 @@ class Calendar(inkycal_module):
|
||||
event_duration = (event['end'] - event['begin']).days
|
||||
if event_duration > 1:
|
||||
# Format the duration using Arrow's localization
|
||||
days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True, locale=lang)
|
||||
days_translation = arrow.get().shift(days=event_duration).humanize(only_distance=True,
|
||||
locale=lang)
|
||||
the_name = f"{event['title']} ({days_translation})"
|
||||
else:
|
||||
the_name = event['title']
|
||||
|
@@ -2,12 +2,12 @@
|
||||
Inkycal weather module
|
||||
Copyright by aceinnolab
|
||||
"""
|
||||
|
||||
import arrow
|
||||
import decimal
|
||||
import logging
|
||||
import math
|
||||
from typing import Tuple
|
||||
|
||||
import arrow
|
||||
from PIL import Image
|
||||
from PIL import ImageDraw
|
||||
from PIL import ImageFont
|
||||
@@ -51,7 +51,7 @@ class Weather(inkycal_module):
|
||||
"options": [True, False],
|
||||
},
|
||||
|
||||
"round_windspeed": {
|
||||
"round_wind_speed": {
|
||||
"label": "Round windspeed?",
|
||||
"options": [True, False],
|
||||
},
|
||||
@@ -89,7 +89,7 @@ class Weather(inkycal_module):
|
||||
|
||||
# Check if all required parameters are present
|
||||
for param in self.requires:
|
||||
if not param in config:
|
||||
if param not in config:
|
||||
raise Exception(f'config is missing {param}')
|
||||
|
||||
# required parameters
|
||||
@@ -98,15 +98,15 @@ class Weather(inkycal_module):
|
||||
|
||||
# optional parameters
|
||||
self.round_temperature = config['round_temperature']
|
||||
self.round_windspeed = config['round_windspeed']
|
||||
self.round_wind_speed = config['round_windspeed']
|
||||
self.forecast_interval = config['forecast_interval']
|
||||
self.hour_format = int(config['hour_format'])
|
||||
if config['units'] == "imperial":
|
||||
self.temp_unit = "fahrenheit"
|
||||
else:
|
||||
self.temp_unit = "celsius"
|
||||
|
||||
if config['use_beaufort'] == True:
|
||||
|
||||
if config['use_beaufort']:
|
||||
self.wind_unit = "beaufort"
|
||||
elif config['units'] == "imperial":
|
||||
self.wind_unit = "miles_hour"
|
||||
@@ -116,17 +116,17 @@ class Weather(inkycal_module):
|
||||
# additional configuration
|
||||
|
||||
self.owm = OpenWeatherMap(
|
||||
api_key=self.api_key,
|
||||
city_id=self.location,
|
||||
wind_unit=self.wind_unit,
|
||||
api_key=self.api_key,
|
||||
city_id=self.location,
|
||||
wind_unit=self.wind_unit,
|
||||
temp_unit=self.temp_unit,
|
||||
language=self.locale,
|
||||
language=self.locale,
|
||||
tz_name=self.timezone
|
||||
)
|
||||
|
||||
)
|
||||
|
||||
self.weatherfont = ImageFont.truetype(
|
||||
fonts['weathericons-regular-webfont'], size=self.fontsize)
|
||||
|
||||
|
||||
if self.wind_unit == "beaufort":
|
||||
self.windDispUnit = "bft"
|
||||
elif self.wind_unit == "knots":
|
||||
@@ -145,8 +145,6 @@ class Weather(inkycal_module):
|
||||
# give an OK message
|
||||
logger.debug(f"{__name__} loaded")
|
||||
|
||||
|
||||
|
||||
def generate_image(self):
|
||||
"""Generate image for this module"""
|
||||
|
||||
@@ -191,7 +189,7 @@ class Weather(inkycal_module):
|
||||
7: '\uf0ae'
|
||||
}[int(index) & 7]
|
||||
|
||||
def is_negative(temp:str):
|
||||
def is_negative(temp: str):
|
||||
"""Check if temp is below freezing point of water (0°C/32°F)
|
||||
returns True if temp below freezing point, else False"""
|
||||
answer = False
|
||||
@@ -224,12 +222,19 @@ class Weather(inkycal_module):
|
||||
'50n': '\uf023'
|
||||
}
|
||||
|
||||
def draw_icon(image, xy, box_size, icon, rotation=None):
|
||||
"""Custom function to add icons of weather font on image
|
||||
image = on which image should the text be added?
|
||||
xy = xy-coordinates as tuple -> (x,y)
|
||||
box_size = size of text-box -> (width,height)
|
||||
icon = icon-unicode, looks this up in weathericons dictionary
|
||||
def draw_icon(image: Image, xy: Tuple[int, int], box_size: Tuple[int, int], icon: str, rotation=None):
|
||||
"""Custom function to add icons of weather font on the image.
|
||||
|
||||
Args:
|
||||
- image:
|
||||
the image on which image should the text be added
|
||||
- xy:
|
||||
coordinates as tuple -> (x,y)
|
||||
- box_size:
|
||||
size of text-box -> (width,height)
|
||||
- icon:
|
||||
icon-unicode, looks this up in weather-icons dictionary
|
||||
|
||||
"""
|
||||
|
||||
icon_size_correction = {
|
||||
@@ -264,7 +269,6 @@ class Weather(inkycal_module):
|
||||
'\uf0a0': 0,
|
||||
'\uf0a3': 0,
|
||||
'\uf0a7': 0,
|
||||
'\uf0aa': 0,
|
||||
'\uf0ae': 0
|
||||
}
|
||||
|
||||
@@ -278,8 +282,7 @@ class Weather(inkycal_module):
|
||||
font = ImageFont.truetype(font.path, size)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
|
||||
while (text_width < int(box_width * 0.9) and
|
||||
text_height < int(box_height * 0.9)):
|
||||
while text_width < int(box_width * 0.9) and text_height < int(box_height * 0.9):
|
||||
size += 1
|
||||
font = ImageFont.truetype(font.path, size)
|
||||
text_width, text_height = font.getbbox(text)[2:]
|
||||
@@ -290,8 +293,6 @@ class Weather(inkycal_module):
|
||||
x = int((box_width / 2) - (text_width / 2))
|
||||
y = int((box_height / 2) - (text_height / 2))
|
||||
|
||||
# Draw the text in the text-box
|
||||
draw = ImageDraw.Draw(image)
|
||||
space = Image.new('RGBA', (box_width, box_height))
|
||||
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font)
|
||||
|
||||
@@ -350,17 +351,17 @@ class Weather(inkycal_module):
|
||||
row3 = row2 + line_gap + row_height
|
||||
|
||||
# Draw lines on each row and border
|
||||
############################################################################
|
||||
## draw = ImageDraw.Draw(im_black)
|
||||
## draw.line((0, 0, im_width, 0), fill='red')
|
||||
## draw.line((0, im_height-1, im_width, im_height-1), fill='red')
|
||||
## draw.line((0, row1, im_width, row1), fill='black')
|
||||
## draw.line((0, row1+row_height, im_width, row1+row_height), fill='black')
|
||||
## draw.line((0, row2, im_width, row2), fill='black')
|
||||
## draw.line((0, row2+row_height, im_width, row2+row_height), fill='black')
|
||||
## draw.line((0, row3, im_width, row3), fill='black')
|
||||
## draw.line((0, row3+row_height, im_width, row3+row_height), fill='black')
|
||||
############################################################################
|
||||
###########################################################################
|
||||
# draw = ImageDraw.Draw(im_black)
|
||||
# draw.line((0, 0, im_width, 0), fill='red')
|
||||
# draw.line((0, im_height-1, im_width, im_height-1), fill='red')
|
||||
# draw.line((0, row1, im_width, row1), fill='black')
|
||||
# draw.line((0, row1+row_height, im_width, row1+row_height), fill='black')
|
||||
# draw.line((0, row2, im_width, row2), fill='black')
|
||||
# draw.line((0, row2+row_height, im_width, row2+row_height), fill='black')
|
||||
# draw.line((0, row3, im_width, row3), fill='black')
|
||||
# draw.line((0, row3+row_height, im_width, row3+row_height), fill='black')
|
||||
###########################################################################
|
||||
|
||||
# Positions for current weather details
|
||||
weather_icon_pos = (col1, 0)
|
||||
@@ -379,24 +380,24 @@ class Weather(inkycal_module):
|
||||
sunset_time_pos = (col3 + icon_small, row3)
|
||||
|
||||
# Positions for forecast 1
|
||||
stamp_fc1 = (col4, row1)
|
||||
icon_fc1 = (col4, row1 + row_height)
|
||||
temp_fc1 = (col4, row3)
|
||||
stamp_fc1 = (col4, row1) # noqa
|
||||
icon_fc1 = (col4, row1 + row_height) # noqa
|
||||
temp_fc1 = (col4, row3) # noqa
|
||||
|
||||
# Positions for forecast 2
|
||||
stamp_fc2 = (col5, row1)
|
||||
icon_fc2 = (col5, row1 + row_height)
|
||||
temp_fc2 = (col5, row3)
|
||||
stamp_fc2 = (col5, row1) # noqa
|
||||
icon_fc2 = (col5, row1 + row_height) # noqa
|
||||
temp_fc2 = (col5, row3) # noqa
|
||||
|
||||
# Positions for forecast 3
|
||||
stamp_fc3 = (col6, row1)
|
||||
icon_fc3 = (col6, row1 + row_height)
|
||||
temp_fc3 = (col6, row3)
|
||||
stamp_fc3 = (col6, row1) # noqa
|
||||
icon_fc3 = (col6, row1 + row_height) # noqa
|
||||
temp_fc3 = (col6, row3) # noqa
|
||||
|
||||
# Positions for forecast 4
|
||||
stamp_fc4 = (col7, row1)
|
||||
icon_fc4 = (col7, row1 + row_height)
|
||||
temp_fc4 = (col7, row3)
|
||||
stamp_fc4 = (col7, row1) # noqa
|
||||
icon_fc4 = (col7, row1 + row_height) # noqa
|
||||
temp_fc4 = (col7, row3) # noqa
|
||||
|
||||
# Create current-weather and weather-forecast objects
|
||||
logging.debug('looking up location by ID')
|
||||
@@ -405,7 +406,7 @@ class Weather(inkycal_module):
|
||||
|
||||
# Set decimals
|
||||
dec_temp = 0 if self.round_temperature == True else 1
|
||||
dec_wind = 0 if self.round_windspeed == True else 1
|
||||
dec_wind = 0 if self.round_wind_speed == True else 1
|
||||
|
||||
logging.debug(f'temperature unit: {self.temp_unit}')
|
||||
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}')
|
||||
@@ -425,7 +426,8 @@ class Weather(inkycal_module):
|
||||
fc_data['fc' + str(index + 1)] = {
|
||||
'temp': f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}",
|
||||
'icon': forecast["icon"],
|
||||
'stamp': forecast["datetime"].strftime("%I %p" if self.hour_format == 12 else "%H:%M")}
|
||||
'stamp': forecast["datetime"].strftime("%I %p" if self.hour_format == 12 else "%H:%M")
|
||||
}
|
||||
|
||||
elif self.forecast_interval == 'daily':
|
||||
|
||||
@@ -434,7 +436,7 @@ class Weather(inkycal_module):
|
||||
daily_forecasts = [self.owm.get_forecast_for_day(days) for days in range(1, 5)]
|
||||
|
||||
for index, forecast in enumerate(daily_forecasts):
|
||||
fc_data['fc' + str(index +1)] = {
|
||||
fc_data['fc' + str(index + 1)] = {
|
||||
'temp': f'{forecast["temp_min"]:.{dec_temp}f}{self.tempDispUnit}/{forecast["temp_max"]:.{dec_temp}f}{self.tempDispUnit}',
|
||||
'icon': forecast['icon'],
|
||||
'stamp': forecast['datetime'].strftime("%A")
|
||||
@@ -514,6 +516,9 @@ class Weather(inkycal_module):
|
||||
# Add the forecast data to the correct places
|
||||
for pos in range(1, len(fc_data) + 1):
|
||||
stamp = fc_data[f'fc{pos}']['stamp']
|
||||
# check if we're using daily forecasts
|
||||
if "day" in stamp:
|
||||
stamp = arrow.get(fc_data[f'fc{pos}']['stamp'], "dddd").format("dddd", locale="de")
|
||||
|
||||
icon = weather_icons[fc_data[f'fc{pos}']['icon']]
|
||||
temp = fc_data[f'fc{pos}']['temp']
|
||||
|
@@ -41,7 +41,10 @@ class Webshot(inkycal_module):
|
||||
},
|
||||
"crop_h": {
|
||||
"label": "Please enter the crop height",
|
||||
}
|
||||
},
|
||||
"rotation": {
|
||||
"label": "Please enter the rotation. Must be either 0, 90, 180 or 270",
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, config):
|
||||
@@ -73,6 +76,12 @@ class Webshot(inkycal_module):
|
||||
else:
|
||||
self.crop_y = 0
|
||||
|
||||
self.rotation = 0
|
||||
if "rotation" in config:
|
||||
self.rotation = int(config["rotation"])
|
||||
if self.rotation not in [0, 90, 180, 270]:
|
||||
raise Exception("Rotation must be either 0, 90, 180 or 270")
|
||||
|
||||
# give an OK message
|
||||
logger.debug(f'Inkycal webshot loaded')
|
||||
|
||||
@@ -106,7 +115,7 @@ class Webshot(inkycal_module):
|
||||
logger.info(
|
||||
f'preparing webshot from {self.url}... cropH{self.crop_h} cropW{self.crop_w} cropX{self.crop_x} cropY{self.crop_y}')
|
||||
|
||||
shot = WebShot()
|
||||
shot = WebShot(size=(im_height, im_width))
|
||||
|
||||
shot.params = {
|
||||
"--crop-x": self.crop_x,
|
||||
@@ -152,11 +161,21 @@ class Webshot(inkycal_module):
|
||||
|
||||
centerPosX = int((im_width / 2) - (im.image.width / 2))
|
||||
|
||||
webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY))
|
||||
im_black.paste(webshotSpaceBlack)
|
||||
|
||||
webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY))
|
||||
im_colour.paste(webshotSpaceColour)
|
||||
if self.rotation != 0:
|
||||
webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY))
|
||||
im_black.paste(webshotSpaceBlack)
|
||||
im_black = im_black.rotate(self.rotation, expand=True)
|
||||
|
||||
webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY))
|
||||
im_colour.paste(webshotSpaceColour)
|
||||
im_colour = im_colour.rotate(self.rotation, expand=True)
|
||||
else:
|
||||
webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY))
|
||||
im_black.paste(webshotSpaceBlack)
|
||||
|
||||
webshotSpaceColour.paste(im_webshot_colour, (centerPosX, webshotCenterPosY))
|
||||
im_colour.paste(webshotSpaceColour)
|
||||
|
||||
im.clear()
|
||||
logger.info(f'added webshot image')
|
||||
|
Reference in New Issue
Block a user