Merge branch 'main' into feature/#311

This commit is contained in:
Ace 2024-06-25 17:55:15 +02:00 committed by GitHub
commit b409cfe544
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 195 additions and 163 deletions

View File

@ -232,14 +232,14 @@ which the given font should be scaled to.</p></li>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="inkycal.custom.functions.draw_border"> <dt class="sig sig-object py" id="inkycal.custom.functions.draw_border">
<span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">draw_border</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">image</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">xy</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">size</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">radius</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">5</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">thickness</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">1</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">shrinkage</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">(0.1,</span> <span class="pre">0.1)</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.draw_border" title="Link to this definition"></a></dt> <span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">draw_border</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">image:</span> <span class="pre">&lt;module</span> <span class="pre">'PIL.Image'</span> <span class="pre">from</span> <span class="pre">'/home/runner/work/Inkycal/Inkycal/venv/lib/python3.11/site-packages/PIL/Image.py'&gt;,</span> <span class="pre">xy:</span> <span class="pre">~typing.Tuple[int,</span> <span class="pre">int],</span> <span class="pre">size:</span> <span class="pre">~typing.Tuple[int,</span> <span class="pre">int],</span> <span class="pre">radius:</span> <span class="pre">int</span> <span class="pre">=</span> <span class="pre">5,</span> <span class="pre">thickness:</span> <span class="pre">int</span> <span class="pre">=</span> <span class="pre">1,</span> <span class="pre">shrinkage:</span> <span class="pre">~typing.Tuple[int,</span> <span class="pre">int]</span> <span class="pre">=</span> <span class="pre">(0.1,</span> <span class="pre">0.1)</span></em><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">None</span></span></span><a class="headerlink" href="#inkycal.custom.functions.draw_border" title="Link to this definition"></a></dt>
<dd><p>Draws a border at given coordinates.</p> <dd><p>Draws a border at given coordinates.</p>
<dl class="simple"> <dl class="simple">
<dt>Args:</dt><dd><ul class="simple"> <dt>Args:</dt><dd><ul class="simple">
<li><p>image: The image on which the border should be drawn (usually im_black or <li><p>image: The image on which the border should be drawn (usually im_black or
im_colour.</p></li> im_colour).</p></li>
<li><p>xy: Tuple representing the top-left corner of the border e.g. (32, 100) <li><p>xy: Tuple representing the top-left corner of the border e.g. (32, 100)
where 32 is the x co-ordinate and 100 is the y-coordinate.</p></li> where 32 is the x-coordinate and 100 is the y-coordinate.</p></li>
<li><p>size: Size of the border as a tuple -&gt; (width, height).</p></li> <li><p>size: Size of the border as a tuple -&gt; (width, height).</p></li>
<li><p>radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.</p></li> <li><p>radius: Radius of the corners, where 0 = plain rectangle, 5 = round corners.</p></li>
<li><p>thickness: Thickness of the border in pixels.</p></li> <li><p>thickness: Thickness of the border in pixels.</p></li>
@ -288,14 +288,14 @@ printed fonts of this function:</p>
<p>The extracted timezone can be used to show the local time instead of UTC. e.g.</p> <p>The extracted timezone can be used to show the local time instead of UTC. e.g.</p>
<div class="doctest highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">arrow</span> <div class="doctest highlight-default notranslate"><div class="highlight"><pre><span></span><span class="gp">&gt;&gt;&gt; </span><span class="kn">import</span> <span class="nn">arrow</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">())</span> <span class="c1"># returns non-timezone-aware time</span> <span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">())</span> <span class="c1"># returns non-timezone-aware time</span>
<span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">get_system_tz</span><span class="p">())</span> <span class="c1"># prints timezone aware time.</span> <span class="gp">&gt;&gt;&gt; </span><span class="nb">print</span><span class="p">(</span><span class="n">arrow</span><span class="o">.</span><span class="n">now</span><span class="p">(</span><span class="n">tz</span><span class="o">=</span><span class="n">get_system_tz</span><span class="p">()))</span> <span class="c1"># prints timezone aware time.</span>
</pre></div> </pre></div>
</div> </div>
</dd></dl> </dd></dl>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="inkycal.custom.functions.internet_available"> <dt class="sig sig-object py" id="inkycal.custom.functions.internet_available">
<span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">internet_available</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.internet_available" title="Link to this definition"></a></dt> <span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">internet_available</span></span><span class="sig-paren">(</span><span class="sig-paren">)</span> <span class="sig-return"><span class="sig-return-icon">&#x2192;</span> <span class="sig-return-typehint"><span class="pre">bool</span></span></span><a class="headerlink" href="#inkycal.custom.functions.internet_available" title="Link to this definition"></a></dt>
<dd><p>checks if the internet is available.</p> <dd><p>checks if the internet is available.</p>
<p>Attempts to connect to google.com with a timeout of 5 seconds to check <p>Attempts to connect to google.com with a timeout of 5 seconds to check
if the network can be reached.</p> if the network can be reached.</p>
@ -315,7 +315,7 @@ if the network can be reached.</p>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="inkycal.custom.functions.text_wrap"> <dt class="sig sig-object py" id="inkycal.custom.functions.text_wrap">
<span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">text_wrap</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">text</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">font</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">max_width</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.text_wrap" title="Link to this definition"></a></dt> <span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">text_wrap</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">text</span></span><span class="p"><span class="pre">:</span></span><span class="w"> </span><span class="n"><span class="pre">str</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">font</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">max_width</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.text_wrap" title="Link to this definition"></a></dt>
<dd><p>Splits a very long text into smaller parts</p> <dd><p>Splits a very long text into smaller parts</p>
<p>Splits a long text to smaller lines which can fit in a line with max_width. <p>Splits a long text to smaller lines which can fit in a line with max_width.
Uses a Font object for more accurate calculations.</p> Uses a Font object for more accurate calculations.</p>
@ -334,7 +334,7 @@ splitting the text into the next chunk.</p></li>
<dl class="py function"> <dl class="py function">
<dt class="sig sig-object py" id="inkycal.custom.functions.write"> <dt class="sig sig-object py" id="inkycal.custom.functions.write">
<span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">write</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="n"><span class="pre">image</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">xy</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">box_size</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">text</span></span></em>, <em class="sig-param"><span class="n"><span class="pre">font</span></span><span class="o"><span class="pre">=</span></span><span class="default_value"><span class="pre">None</span></span></em>, <em class="sig-param"><span class="o"><span class="pre">**</span></span><span class="n"><span class="pre">kwargs</span></span></em><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.write" title="Link to this definition"></a></dt> <span class="sig-prename descclassname"><span class="pre">inkycal.custom.functions.</span></span><span class="sig-name descname"><span class="pre">write</span></span><span class="sig-paren">(</span><em class="sig-param"><span class="pre">image:</span> <span class="pre">&lt;module</span> <span class="pre">'PIL.Image'</span> <span class="pre">from</span> <span class="pre">'/home/runner/work/Inkycal/Inkycal/venv/lib/python3.11/site-packages/PIL/Image.py'&gt;,</span> <span class="pre">xy:</span> <span class="pre">~typing.Tuple[int,</span> <span class="pre">int],</span> <span class="pre">box_size:</span> <span class="pre">~typing.Tuple[int,</span> <span class="pre">int],</span> <span class="pre">text:</span> <span class="pre">str,</span> <span class="pre">font=None,</span> <span class="pre">**kwargs</span></em><span class="sig-paren">)</span><a class="headerlink" href="#inkycal.custom.functions.write" title="Link to this definition"></a></dt>
<dd><p>Writes text on an image.</p> <dd><p>Writes text on an image.</p>
<p>Writes given text at given position on the specified image.</p> <p>Writes given text at given position on the specified image.</p>
<dl class="simple"> <dl class="simple">

Binary file not shown.

View File

@ -8,9 +8,9 @@ import logging
import os import os
import time import time
import traceback import traceback
from typing import Tuple
import arrow import arrow
import PIL
import requests import requests
import tzlocal import tzlocal
from PIL import Image from PIL import Image
@ -73,7 +73,7 @@ def get_system_tz() -> str:
>>> import arrow >>> import arrow
>>> print(arrow.now()) # returns non-timezone-aware time >>> print(arrow.now()) # returns non-timezone-aware time
>>> print(arrow.now(tz=get_system_tz()) # prints timezone aware time. >>> print(arrow.now(tz=get_system_tz())) # prints timezone aware time.
""" """
try: try:
local_tz = tzlocal.get_localzone().key local_tz = tzlocal.get_localzone().key
@ -111,7 +111,7 @@ def auto_fontsize(font, max_height):
return font return font
def write(image, xy, box_size, text, font=None, **kwargs): def write(image: Image, xy: Tuple[int, int], box_size: Tuple[int, int], text: str, font=None, **kwargs):
"""Writes text on an image. """Writes text on an image.
Writes given text at given position on the specified image. Writes given text at given position on the specified image.
@ -161,7 +161,7 @@ def write(image, xy, box_size, text, font=None, **kwargs):
text_bbox = font.getbbox(text) text_bbox = font.getbbox(text)
text_width = text_bbox[2] - text_bbox[0] text_width = text_bbox[2] - text_bbox[0]
text_bbox_height = font.getbbox("hg") text_bbox_height = font.getbbox("hg")
text_height = text_bbox_height[3] - text_bbox_height[1] text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1])
while text_width < int(box_width * fill_width) and text_height < int(box_height * fill_height): while text_width < int(box_width * fill_width) and text_height < int(box_height * fill_height):
size += 1 size += 1
@ -169,12 +169,12 @@ def write(image, xy, box_size, text, font=None, **kwargs):
text_bbox = font.getbbox(text) text_bbox = font.getbbox(text)
text_width = text_bbox[2] - text_bbox[0] text_width = text_bbox[2] - text_bbox[0]
text_bbox_height = font.getbbox("hg") text_bbox_height = font.getbbox("hg")
text_height = text_bbox_height[3] - text_bbox_height[1] text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1])
text_bbox = font.getbbox(text) text_bbox = font.getbbox(text)
text_width = text_bbox[2] - text_bbox[0] text_width = text_bbox[2] - text_bbox[0]
text_bbox_height = font.getbbox("hg") text_bbox_height = font.getbbox("hg")
text_height = text_bbox_height[3] - text_bbox_height[1] text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1])
# Truncate text if text is too long, so it can fit inside the box # Truncate text if text is too long, so it can fit inside the box
if (text_width, text_height) > (box_width, box_height): if (text_width, text_height) > (box_width, box_height):
@ -184,7 +184,7 @@ def write(image, xy, box_size, text, font=None, **kwargs):
text_bbox = font.getbbox(text) text_bbox = font.getbbox(text)
text_width = text_bbox[2] - text_bbox[0] text_width = text_bbox[2] - text_bbox[0]
text_bbox_height = font.getbbox("hg") text_bbox_height = font.getbbox("hg")
text_height = text_bbox_height[3] - text_bbox_height[1] text_height = abs(text_bbox_height[3]) # - abs(text_bbox_height[1])
logger.debug(text) logger.debug(text)
# Align text to desired position # Align text to desired position
@ -195,10 +195,13 @@ def write(image, xy, box_size, text, font=None, **kwargs):
elif alignment == "right": elif alignment == "right":
x = int(box_width - text_width) x = int(box_width - text_width)
# Vertical centering
y = int((box_height / 2) - (text_height / 2))
# Draw the text in the text-box # Draw the text in the text-box
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
space = Image.new('RGBA', (box_width, box_height)) space = Image.new('RGBA', (box_width, box_height))
ImageDraw.Draw(space).text((x, 0), text, fill=colour, font=font) ImageDraw.Draw(space).text((x, y), text, fill=colour, font=font)
# Uncomment following two lines, comment out above two lines to show # Uncomment following two lines, comment out above two lines to show
# red text-box with white text (debugging purposes) # red text-box with white text (debugging purposes)
@ -213,7 +216,7 @@ def write(image, xy, box_size, text, font=None, **kwargs):
image.paste(space, xy, space) image.paste(space, xy, space)
def text_wrap(text, font=None, max_width=None): def text_wrap(text: str, font=None, max_width=None):
"""Splits a very long text into smaller parts """Splits a very long text into smaller parts
Splits a long text to smaller lines which can fit in a line with max_width. Splits a long text to smaller lines which can fit in a line with max_width.
@ -249,7 +252,7 @@ def text_wrap(text, font=None, max_width=None):
return lines return lines
def internet_available(): def internet_available() -> bool:
"""checks if the internet is available. """checks if the internet is available.
Attempts to connect to google.com with a timeout of 5 seconds to check Attempts to connect to google.com with a timeout of 5 seconds to check
@ -274,15 +277,16 @@ def internet_available():
return False return False
def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)): def draw_border(image: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int = 5, thickness: int = 1,
shrinkage: Tuple[int, int] = (0.1, 0.1)) -> None:
"""Draws a border at given coordinates. """Draws a border at given coordinates.
Args: Args:
- image: The image on which the border should be drawn (usually im_black or - image: The image on which the border should be drawn (usually im_black or
im_colour. im_colour).
- xy: Tuple representing the top-left corner of the border e.g. (32, 100) - xy: Tuple representing the top-left corner of the border e.g. (32, 100)
where 32 is the x co-ordinate and 100 is the y-coordinate. where 32 is the x-coordinate and 100 is the y-coordinate.
- size: Size of the border as a tuple -> (width, height). - size: Size of the border as a tuple -> (width, height).
@ -320,6 +324,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height) c5, c6 = ((x + width) - diameter, (y + height) - diameter), (x + width, y + height)
c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height) c7, c8 = (x, (y + height) - diameter), (x + diameter, y + height)
# Draw lines and arcs, creating a square with round corners # Draw lines and arcs, creating a square with round corners
draw = ImageDraw.Draw(image) draw = ImageDraw.Draw(image)
draw.line((p1, p2), fill=colour, width=thickness) draw.line((p1, p2), fill=colour, width=thickness)
@ -334,7 +339,7 @@ def draw_border(image, xy, size, radius=5, thickness=1, shrinkage=(0.1, 0.1)):
draw.arc((c7, c8), 90, 180, fill=colour, width=thickness) draw.arc((c7, c8), 90, 180, fill=colour, width=thickness)
def draw_border_2(im: PIL.Image, xy: tuple, size: tuple, radius: int): def draw_border_2(im: Image, xy: Tuple[int, int], size: Tuple[int, int], radius: int):
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
x, y = xy x, y = xy

View File

@ -41,18 +41,9 @@ def get_json_from_url(request_url):
class OpenWeatherMap: class OpenWeatherMap:
def __init__( def __init__(self, api_key: str, city_id: int = None, lat: float = None, lon: float = None,
self, api_version: API_VERSIONS = "2.5", temp_unit: TEMP_UNITS = "celsius",
api_key: str, wind_unit: WIND_UNITS = "meters_sec", language: str = "en", tz_name: str = "UTC") -> None:
city_id: int = None,
lat: float = None,
lon: float = None,
api_version: API_VERSIONS = "2.5",
temp_unit: TEMP_UNITS = "celsius",
wind_unit: WIND_UNITS = "meters_sec",
language: str = "en",
tz_name: str = "UTC",
) -> None:
self.api_key = api_key self.api_key = api_key
self.temp_unit = temp_unit self.temp_unit = temp_unit
self.wind_unit = wind_unit self.wind_unit = wind_unit
@ -187,7 +178,7 @@ class OpenWeatherMap:
:return: :return:
Forecast dictionary Forecast dictionary
""" """
# Make sure hourly forecasts are up to date # Make sure hourly forecasts are up-to-date
_ = self.get_weather_forecast() _ = self.get_weather_forecast()
# Calculate the start and end times for the specified number of days from now # Calculate the start and end times for the specified number of days from now
@ -207,7 +198,7 @@ class OpenWeatherMap:
] ]
# In case the next available forecast is already for the next day, use that one for the less than 3 remaining hours of today # In case the next available forecast is already for the next day, use that one for the less than 3 remaining hours of today
if forecasts == []: if not forecasts:
forecasts.append(self.hourly_forecasts[0]) forecasts.append(self.hourly_forecasts[0])
# Get rain and temperatures for that day # Get rain and temperatures for that day

View File

@ -75,6 +75,8 @@ class Agenda(inkycal_module):
# Additional config # Additional config
self.timezone = get_system_tz() self.timezone = get_system_tz()
self.icon_font = ImageFont.truetype(fonts['MaterialIcons'], size=self.fontsize)
# give an OK message # give an OK message
logger.debug(f'{__name__} loaded') logger.debug(f'{__name__} loaded')
@ -201,10 +203,10 @@ class Agenda(inkycal_module):
write(im_black, (x_time, line_pos[cursor][1]), write(im_black, (x_time, line_pos[cursor][1]),
(time_width, line_height), time, (time_width, line_height), time,
font=self.font, alignment='right') font=self.font, alignment='right')
if parser.all_day(_): else:
write(im_black, (x_time, line_pos[cursor][1]), write(im_black, (x_time, line_pos[cursor][1]),
(time_width, line_height), "all day", (time_width, line_height), "\ue878",
font=self.font, alignment='right') font=self.icon_font, alignment='right')
write(im_black, (x_event, line_pos[cursor][1]), write(im_black, (x_event, line_pos[cursor][1]),
(event_width, line_height), (event_width, line_height),

View File

@ -6,16 +6,16 @@ Copyright by aceinnolab
# pylint: disable=logging-fstring-interpolation # pylint: disable=logging-fstring-interpolation
import calendar as cal import calendar as cal
import arrow
from inkycal.modules.template import inkycal_module
from inkycal.custom import * from inkycal.custom import *
from inkycal.modules.template import inkycal_module
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class Calendar(inkycal_module): class Calendar(inkycal_module):
"""Calendar class """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" name = "Calendar - Show monthly calendar with events from iCalendars"
@ -61,7 +61,7 @@ class Calendar(inkycal_module):
self._days_with_events = None self._days_with_events = None
# optional parameters # optional parameters
self.weekstart = config['week_starts_on'] self.week_start = config['week_starts_on']
self.show_events = config['show_events'] self.show_events = config['show_events']
self.date_format = config["date_format"] self.date_format = config["date_format"]
self.time_format = config['time_format'] self.time_format = config['time_format']
@ -109,7 +109,7 @@ class Calendar(inkycal_module):
# Allocate space for month-names, weekdays etc. # Allocate space for month-names, weekdays etc.
month_name_height = int(im_height * 0.10) month_name_height = int(im_height * 0.10)
text_bbox_height = self.font.getbbox("hg") 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"month_name_height: {month_name_height}")
logger.debug(f"weekdays_height: {weekdays_height}") logger.debug(f"weekdays_height: {weekdays_height}")
@ -156,13 +156,13 @@ class Calendar(inkycal_module):
now = arrow.now(tz=self.timezone) now = arrow.now(tz=self.timezone)
# Set weekstart of calendar to specified weekstart # Set week-start of calendar to specified week-start
if self.weekstart == "Monday": if self.week_start == "Monday":
cal.setfirstweekday(cal.MONDAY) cal.setfirstweekday(cal.MONDAY)
weekstart = now.shift(days=-now.weekday()) week_start = now.shift(days=-now.weekday())
else: else:
cal.setfirstweekday(cal.SUNDAY) cal.setfirstweekday(cal.SUNDAY)
weekstart = now.shift(days=-now.isoweekday()) week_start = now.shift(days=-now.isoweekday())
# Write the name of current month # Write the name of current month
write( write(
@ -174,9 +174,9 @@ class Calendar(inkycal_module):
autofit=True, 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 = [ weekday_names = [
weekstart.shift(days=+_).format('ddd', locale=self.language) week_start.shift(days=+_).format('ddd', locale=self.language)
for _ in range(7) for _ in range(7)
] ]
logger.debug(f'weekday names: {weekday_names}') logger.debug(f'weekday names: {weekday_names}')
@ -192,7 +192,7 @@ class Calendar(inkycal_module):
fill_height=0.9, 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)) calendar_flat = self.flatten(cal.monthcalendar(now.year, now.month))
# logger.debug(f" calendar_flat: {calendar_flat}") # logger.debug(f" calendar_flat: {calendar_flat}")
@ -281,7 +281,7 @@ class Calendar(inkycal_module):
month_start = arrow.get(now.floor('month')) month_start = arrow.get(now.floor('month'))
month_end = arrow.get(now.ceil('month')) month_end = arrow.get(now.ceil('month'))
# fetch events from given icalendars # fetch events from given iCalendars
self.ical = iCalendar() self.ical = iCalendar()
parser = self.ical parser = self.ical
@ -300,8 +300,6 @@ class Calendar(inkycal_module):
# Handle multi-day events by adding all days between start and end # Handle multi-day events by adding all days between start and end
for event in month_events: 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 # Convert start and end dates to arrow objects with timezone
start = arrow.get(event['begin'].date(), tzinfo=self.timezone) start = arrow.get(event['begin'].date(), tzinfo=self.timezone)
@ -324,9 +322,7 @@ class Calendar(inkycal_module):
im_colour, im_colour,
grid[days], grid[days],
(icon_width, icon_height), (icon_width, icon_height),
radius=6, radius=6
thickness=1,
shrinkage=(0, 0),
) )
# Filter upcoming events until 4 weeks in the future # Filter upcoming events until 4 weeks in the future
@ -369,7 +365,8 @@ class Calendar(inkycal_module):
event_duration = (event['end'] - event['begin']).days event_duration = (event['end'] - event['begin']).days
if event_duration > 1: if event_duration > 1:
# Format the duration using Arrow's localization # 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})" the_name = f"{event['title']} ({days_translation})"
else: else:
the_name = event['title'] the_name = event['title']

View File

@ -2,12 +2,12 @@
Inkycal weather module Inkycal weather module
Copyright by aceinnolab Copyright by aceinnolab
""" """
import arrow
import decimal import decimal
import logging import logging
import math import math
from typing import Tuple
import arrow
from PIL import Image from PIL import Image
from PIL import ImageDraw from PIL import ImageDraw
from PIL import ImageFont from PIL import ImageFont
@ -51,7 +51,7 @@ class Weather(inkycal_module):
"options": [True, False], "options": [True, False],
}, },
"round_windspeed": { "round_wind_speed": {
"label": "Round windspeed?", "label": "Round windspeed?",
"options": [True, False], "options": [True, False],
}, },
@ -89,7 +89,7 @@ class Weather(inkycal_module):
# Check if all required parameters are present # Check if all required parameters are present
for param in self.requires: for param in self.requires:
if not param in config: if param not in config:
raise Exception(f'config is missing {param}') raise Exception(f'config is missing {param}')
# required parameters # required parameters
@ -98,7 +98,7 @@ class Weather(inkycal_module):
# optional parameters # optional parameters
self.round_temperature = config['round_temperature'] 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.forecast_interval = config['forecast_interval']
self.hour_format = int(config['hour_format']) self.hour_format = int(config['hour_format'])
if config['units'] == "imperial": if config['units'] == "imperial":
@ -106,7 +106,7 @@ class Weather(inkycal_module):
else: else:
self.temp_unit = "celsius" self.temp_unit = "celsius"
if config['use_beaufort'] == True: if config['use_beaufort']:
self.wind_unit = "beaufort" self.wind_unit = "beaufort"
elif config['units'] == "imperial": elif config['units'] == "imperial":
self.wind_unit = "miles_hour" self.wind_unit = "miles_hour"
@ -145,8 +145,6 @@ class Weather(inkycal_module):
# give an OK message # give an OK message
logger.debug(f"{__name__} loaded") logger.debug(f"{__name__} loaded")
def generate_image(self): def generate_image(self):
"""Generate image for this module""" """Generate image for this module"""
@ -224,12 +222,19 @@ class Weather(inkycal_module):
'50n': '\uf023' '50n': '\uf023'
} }
def draw_icon(image, xy, box_size, icon, rotation=None): 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 image """Custom function to add icons of weather font on the image.
image = on which image should the text be added?
xy = xy-coordinates as tuple -> (x,y) Args:
box_size = size of text-box -> (width,height) - image:
icon = icon-unicode, looks this up in weathericons dictionary 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 = { icon_size_correction = {
@ -264,7 +269,6 @@ class Weather(inkycal_module):
'\uf0a0': 0, '\uf0a0': 0,
'\uf0a3': 0, '\uf0a3': 0,
'\uf0a7': 0, '\uf0a7': 0,
'\uf0aa': 0,
'\uf0ae': 0 '\uf0ae': 0
} }
@ -278,8 +282,7 @@ class Weather(inkycal_module):
font = ImageFont.truetype(font.path, size) font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getbbox(text)[2:] text_width, text_height = font.getbbox(text)[2:]
while (text_width < int(box_width * 0.9) and while text_width < int(box_width * 0.9) and text_height < int(box_height * 0.9):
text_height < int(box_height * 0.9)):
size += 1 size += 1
font = ImageFont.truetype(font.path, size) font = ImageFont.truetype(font.path, size)
text_width, text_height = font.getbbox(text)[2:] text_width, text_height = font.getbbox(text)[2:]
@ -290,8 +293,6 @@ class Weather(inkycal_module):
x = int((box_width / 2) - (text_width / 2)) x = int((box_width / 2) - (text_width / 2))
y = int((box_height / 2) - (text_height / 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)) space = Image.new('RGBA', (box_width, box_height))
ImageDraw.Draw(space).text((x, y), text, fill='black', font=font) 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 row3 = row2 + line_gap + row_height
# Draw lines on each row and border # Draw lines on each row and border
############################################################################ ###########################################################################
## draw = ImageDraw.Draw(im_black) # draw = ImageDraw.Draw(im_black)
## draw.line((0, 0, im_width, 0), fill='red') # 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, im_height-1, im_width, im_height-1), fill='red')
## draw.line((0, row1, im_width, row1), fill='black') # 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, row1+row_height, im_width, row1+row_height), fill='black')
## draw.line((0, row2, im_width, row2), 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, row2+row_height, im_width, row2+row_height), fill='black')
## draw.line((0, row3, im_width, row3), 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.line((0, row3+row_height, im_width, row3+row_height), fill='black')
############################################################################ ###########################################################################
# Positions for current weather details # Positions for current weather details
weather_icon_pos = (col1, 0) weather_icon_pos = (col1, 0)
@ -379,24 +380,24 @@ class Weather(inkycal_module):
sunset_time_pos = (col3 + icon_small, row3) sunset_time_pos = (col3 + icon_small, row3)
# Positions for forecast 1 # Positions for forecast 1
stamp_fc1 = (col4, row1) stamp_fc1 = (col4, row1) # noqa
icon_fc1 = (col4, row1 + row_height) icon_fc1 = (col4, row1 + row_height) # noqa
temp_fc1 = (col4, row3) temp_fc1 = (col4, row3) # noqa
# Positions for forecast 2 # Positions for forecast 2
stamp_fc2 = (col5, row1) stamp_fc2 = (col5, row1) # noqa
icon_fc2 = (col5, row1 + row_height) icon_fc2 = (col5, row1 + row_height) # noqa
temp_fc2 = (col5, row3) temp_fc2 = (col5, row3) # noqa
# Positions for forecast 3 # Positions for forecast 3
stamp_fc3 = (col6, row1) stamp_fc3 = (col6, row1) # noqa
icon_fc3 = (col6, row1 + row_height) icon_fc3 = (col6, row1 + row_height) # noqa
temp_fc3 = (col6, row3) temp_fc3 = (col6, row3) # noqa
# Positions for forecast 4 # Positions for forecast 4
stamp_fc4 = (col7, row1) stamp_fc4 = (col7, row1) # noqa
icon_fc4 = (col7, row1 + row_height) icon_fc4 = (col7, row1 + row_height) # noqa
temp_fc4 = (col7, row3) temp_fc4 = (col7, row3) # noqa
# Create current-weather and weather-forecast objects # Create current-weather and weather-forecast objects
logging.debug('looking up location by ID') logging.debug('looking up location by ID')
@ -405,7 +406,7 @@ class Weather(inkycal_module):
# Set decimals # Set decimals
dec_temp = 0 if self.round_temperature == True else 1 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'temperature unit: {self.temp_unit}')
logging.debug(f'decimals temperature: {dec_temp} | decimals wind: {dec_wind}') 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)] = { fc_data['fc' + str(index + 1)] = {
'temp': f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}", 'temp': f"{forecast['temp']:.{dec_temp}f}{self.tempDispUnit}",
'icon': forecast["icon"], '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': elif self.forecast_interval == 'daily':
@ -514,6 +516,9 @@ class Weather(inkycal_module):
# Add the forecast data to the correct places # Add the forecast data to the correct places
for pos in range(1, len(fc_data) + 1): for pos in range(1, len(fc_data) + 1):
stamp = fc_data[f'fc{pos}']['stamp'] 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']] icon = weather_icons[fc_data[f'fc{pos}']['icon']]
temp = fc_data[f'fc{pos}']['temp'] temp = fc_data[f'fc{pos}']['temp']

View File

@ -41,7 +41,10 @@ class Webshot(inkycal_module):
}, },
"crop_h": { "crop_h": {
"label": "Please enter the crop height", "label": "Please enter the crop height",
} },
"rotation": {
"label": "Please enter the rotation. Must be either 0, 90, 180 or 270",
},
} }
def __init__(self, config): def __init__(self, config):
@ -73,6 +76,12 @@ class Webshot(inkycal_module):
else: else:
self.crop_y = 0 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 # give an OK message
logger.debug(f'Inkycal webshot loaded') logger.debug(f'Inkycal webshot loaded')
@ -106,7 +115,7 @@ class Webshot(inkycal_module):
logger.info( logger.info(
f'preparing webshot from {self.url}... cropH{self.crop_h} cropW{self.crop_w} cropX{self.crop_x} cropY{self.crop_y}') 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 = { shot.params = {
"--crop-x": self.crop_x, "--crop-x": self.crop_x,
@ -152,6 +161,16 @@ class Webshot(inkycal_module):
centerPosX = int((im_width / 2) - (im.image.width / 2)) centerPosX = int((im_width / 2) - (im.image.width / 2))
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)) webshotSpaceBlack.paste(im_webshot_black, (centerPosX, webshotCenterPosY))
im_black.paste(webshotSpaceBlack) im_black.paste(webshotSpaceBlack)

View File

@ -37,7 +37,7 @@ python-dotenv==1.0.1
pytz==2024.1 pytz==2024.1
PyYAML==6.0.1 PyYAML==6.0.1
recurring-ical-events==2.1.2 recurring-ical-events==2.1.2
requests==2.31.0 requests==2.32.0
sgmllib3k==1.0.0 sgmllib3k==1.0.0
six==1.16.0 six==1.16.0
soupsieve==2.5 soupsieve==2.5
@ -46,7 +46,7 @@ types-python-dateutil==2.8.19.20240106
typing_extensions==4.9.0 typing_extensions==4.9.0
tzdata==2024.1 tzdata==2024.1
tzlocal==5.2 tzlocal==5.2
urllib3==2.2.0 urllib3==2.2.2
virtualenv==20.25.0 virtualenv==20.25.0
webencodings==0.5.1 webencodings==0.5.1
x-wr-timezone==0.0.6 x-wr-timezone==0.0.6

View File

@ -1,12 +1,22 @@
""" """
Test the functions in the functions module. Test the functions in the functions module.
""" """
import unittest
from PIL import Image, ImageFont from PIL import Image, ImageFont
from inkycal.custom import write, fonts
from inkycal.custom import write, fonts, get_system_tz
def test_write(): class TestIcalendar(unittest.TestCase):
def test_write(self):
im = Image.new("RGB", (500, 200), "white") im = Image.new("RGB", (500, 200), "white")
font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size=40) font = ImageFont.truetype(fonts['NotoSans-SemiCondensed'], size=40)
write(im, (125, 75), (250, 50), "Hello World", font) write(im, (125, 75), (250, 50), "Hello World", font)
# im.show() # im.show()
def test_get_system_tz(self):
tz = get_system_tz()
assert isinstance(tz, str)

View File

@ -37,7 +37,7 @@ tests = [
"size": [500, 800], "size": [500, 800],
"ical_urls": sample_url, "ical_urls": sample_url,
"ical_files": None, "ical_files": None,
"date_format": "ddd D MMM", "date_format": "DD.MMMM YYYY",
"time_format": "HH:mm", "time_format": "HH:mm",
"padding_x": 10, "padding_x": 10,
"padding_y": 10, "padding_y": 10,

View File

@ -20,7 +20,7 @@ tests = [
{ {
"name": "Calendar", "name": "Calendar",
"config": { "config": {
"size": [500, 500], "size": [500, 600],
"week_starts_on": "Monday", "week_starts_on": "Monday",
"show_events": True, "show_events": True,
"ical_urls": sample_url, "ical_urls": sample_url,

View File

@ -34,7 +34,7 @@ tests = [
"padding_x": 10, "padding_x": 10,
"padding_y": 10, "padding_y": 10,
"fontsize": 12, "fontsize": 12,
"language": "en" "language": "de"
} }
}, },
{ {

View File

@ -16,33 +16,13 @@ preview = Inkyimage.preview
merge = Inkyimage.merge merge = Inkyimage.merge
tests = [ tests = [
{
"position": 1,
"name": "Webshot",
"config": {
"size": [400, 100],
"url": "https://github.com",
"palette": "bwr",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
},
{ {
"position": 1, "position": 1,
"name": "Webshot", "name": "Webshot",
"config": { "config": {
"size": [400, 200], "size": [400, 200],
"url": "https://github.com", "url": "https://aceinnolab.com",
"palette": "bwy", "palette": "bwr",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
},
{
"position": 1,
"name": "Webshot",
"config": {
"size": [400, 300],
"url": "https://github.com",
"palette": "bw",
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
}, },
@ -51,8 +31,31 @@ tests = [
"name": "Webshot", "name": "Webshot",
"config": { "config": {
"size": [400, 400], "size": [400, 400],
"url": "https://github.com", "url": "https://aceinnolab.com",
"palette": "bwy",
"rotation": 0,
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
},
{
"position": 1,
"name": "Webshot",
"config": {
"size": [400, 600],
"url": "https://aceinnolab.com",
"palette": "bw",
"rotation": 90,
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
}
},
{
"position": 1,
"name": "Webshot",
"config": {
"size": [400, 800],
"url": "https://aceinnolab.com",
"palette": "bwr", "palette": "bwr",
"rotation": 180,
"padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en" "padding_x": 10, "padding_y": 10, "fontsize": 12, "language": "en"
} }
} }