| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2022-10-02 00:49:27 +02:00
										 |  |  | Inkycal Calendar Module | 
					
						
							| 
									
										
										
										
											2023-06-03 16:16:07 +02:00
										 |  |  | Copyright by aceinnolab | 
					
						
							| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | """
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | # pylint: disable=logging-fstring-interpolation | 
					
						
							| 
									
										
										
										
											2020-05-23 01:45:40 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | import calendar as cal | 
					
						
							|  |  |  | import arrow | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  | from inkycal.modules.template import inkycal_module | 
					
						
							|  |  |  | from inkycal.custom import * | 
					
						
							| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-03 02:56:04 +02:00
										 |  |  | logger = logging.getLogger(__name__) | 
					
						
							| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-05 00:25:48 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-05-26 19:10:20 +02:00
										 |  |  | class Calendar(inkycal_module): | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     """Calendar class
 | 
					
						
							|  |  |  |     Create monthly calendar and show events from given icalendars | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     name = "Calendar - Show monthly calendar with events from iCalendars" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     optional = { | 
					
						
							|  |  |  |         "week_starts_on": { | 
					
						
							|  |  |  |             "label": "When does your week start? (default=Monday)", | 
					
						
							|  |  |  |             "options": ["Monday", "Sunday"], | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             "default": "Monday", | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         "show_events": { | 
					
						
							|  |  |  |             "label": "Show parsed events? (default = True)", | 
					
						
							|  |  |  |             "options": [True, False], | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             "default": True, | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         }, | 
					
						
							|  |  |  |         "ical_urls": { | 
					
						
							|  |  |  |             "label": "iCalendar URL/s, separate multiple ones with a comma", | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "ical_files": { | 
					
						
							|  |  |  |             "label": "iCalendar filepaths, separated with a comma", | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "date_format": { | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             "label": "Use an arrow-supported token for custom date formatting " | 
					
						
							|  |  |  |             + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. D MMM", | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             "default": "D MMM", | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         "time_format": { | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             "label": "Use an arrow-supported token for custom time formatting " | 
					
						
							|  |  |  |             + "see https://arrow.readthedocs.io/en/stable/#supported-tokens, e.g. HH:mm", | 
					
						
							|  |  |  |             "default": "HH:mm", | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         }, | 
					
						
							| 
									
										
										
										
											2020-11-09 17:51:15 +01:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     def __init__(self, config): | 
					
						
							|  |  |  |         """Initialize inkycal_calendar module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         super().__init__(config) | 
					
						
							|  |  |  |         config = config['config'] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         self.ical = None | 
					
						
							|  |  |  |         self.month_events = None | 
					
						
							|  |  |  |         self._upcoming_events = None | 
					
						
							|  |  |  |         self._days_with_events = None | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # optional parameters | 
					
						
							|  |  |  |         self.weekstart = config['week_starts_on'] | 
					
						
							|  |  |  |         self.show_events = config['show_events'] | 
					
						
							|  |  |  |         self.date_format = config["date_format"] | 
					
						
							|  |  |  |         self.time_format = config['time_format'] | 
					
						
							|  |  |  |         self.language = config['language'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if config['ical_urls'] and isinstance(config['ical_urls'], str): | 
					
						
							|  |  |  |             self.ical_urls = config['ical_urls'].split(',') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.ical_urls = config['ical_urls'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if config['ical_files'] and isinstance(config['ical_files'], str): | 
					
						
							|  |  |  |             self.ical_files = config['ical_files'].split(',') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             self.ical_files = config['ical_files'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # additional configuration | 
					
						
							|  |  |  |         self.timezone = get_system_tz() | 
					
						
							|  |  |  |         self.num_font = ImageFont.truetype( | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             fonts['NotoSans-SemiCondensed'], size=self.fontsize | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # give an OK message | 
					
						
							| 
									
										
										
										
											2022-10-03 02:56:04 +02:00
										 |  |  |         print(f'{__name__} loaded') | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |     @staticmethod | 
					
						
							|  |  |  |     def flatten(values): | 
					
						
							|  |  |  |         """Flatten the values.""" | 
					
						
							|  |  |  |         return [x for y in values for x in y] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |     def generate_image(self): | 
					
						
							|  |  |  |         """Generate image for this module""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Define new image size with respect to padding | 
					
						
							|  |  |  |         im_width = int(self.width - (2 * self.padding_left)) | 
					
						
							|  |  |  |         im_height = int(self.height - (2 * self.padding_top)) | 
					
						
							|  |  |  |         im_size = im_width, im_height | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         events_height = 0 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         logger.info(f'Image size: {im_size}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create an image for black pixels and one for coloured pixels | 
					
						
							|  |  |  |         im_black = Image.new('RGB', size=im_size, color='white') | 
					
						
							|  |  |  |         im_colour = Image.new('RGB', size=im_size, color='white') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Allocate space for month-names, weekdays etc. | 
					
						
							|  |  |  |         month_name_height = int(im_height * 0.10) | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |         text_bbox_height = self.font.getbbox("hg") | 
					
						
							|  |  |  |         weekdays_height = int((text_bbox_height[3] - text_bbox_height[1])* 1.25) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         logger.debug(f"month_name_height: {month_name_height}") | 
					
						
							|  |  |  |         logger.debug(f"weekdays_height: {weekdays_height}") | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |         if self.show_events: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             logger.debug("Allocating space for events") | 
					
						
							|  |  |  |             calendar_height = int(im_height * 0.6) | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             events_height = ( | 
					
						
							|  |  |  |                 im_height - month_name_height - weekdays_height - calendar_height | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') | 
					
						
							|  |  |  |             logger.debug(f'events-section size: {im_width} x {events_height} px') | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             logger.debug("Not allocating space for events") | 
					
						
							|  |  |  |             calendar_height = im_height - month_name_height - weekdays_height | 
					
						
							|  |  |  |             logger.debug(f'calendar-section size: {im_width} x {calendar_height} px') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Create a 7x6 grid and calculate icon sizes | 
					
						
							|  |  |  |         calendar_rows, calendar_cols = 6, 7 | 
					
						
							|  |  |  |         icon_width = im_width // calendar_cols | 
					
						
							|  |  |  |         icon_height = calendar_height // calendar_rows | 
					
						
							|  |  |  |         logger.debug(f"icon_size: {icon_width}x{icon_height}px") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate spacings for calendar area | 
					
						
							|  |  |  |         x_spacing_calendar = int((im_width % calendar_cols) / 2) | 
					
						
							|  |  |  |         y_spacing_calendar = int((im_height % calendar_rows) / 2) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         logger.debug(f"x_spacing_calendar: {x_spacing_calendar}") | 
					
						
							|  |  |  |         logger.debug(f"y_spacing_calendar :{y_spacing_calendar}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Calculate positions for days of month | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         grid_start_y = month_name_height + weekdays_height + y_spacing_calendar | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         grid_start_x = x_spacing_calendar | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         grid_coordinates = [ | 
					
						
							|  |  |  |             (grid_start_x + icon_width * x, grid_start_y + icon_height * y) | 
					
						
							|  |  |  |             for y in range(calendar_rows) | 
					
						
							|  |  |  |             for x in range(calendar_cols) | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         weekday_pos = [ | 
					
						
							|  |  |  |             (grid_start_x + icon_width * _, month_name_height) | 
					
						
							|  |  |  |             for _ in range(calendar_cols) | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         now = arrow.now(tz=self.timezone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Set weekstart of calendar to specified weekstart | 
					
						
							|  |  |  |         if self.weekstart == "Monday": | 
					
						
							|  |  |  |             cal.setfirstweekday(cal.MONDAY) | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             weekstart = now.shift(days=-now.weekday()) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         else: | 
					
						
							|  |  |  |             cal.setfirstweekday(cal.SUNDAY) | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             weekstart = now.shift(days=-now.isoweekday()) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Write the name of current month | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         write( | 
					
						
							|  |  |  |             im_black, | 
					
						
							|  |  |  |             (0, 0), | 
					
						
							|  |  |  |             (im_width, month_name_height), | 
					
						
							|  |  |  |             str(now.format('MMMM', locale=self.language)), | 
					
						
							|  |  |  |             font=self.font, | 
					
						
							|  |  |  |             autofit=True, | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Set up weeknames in local language and add to main section | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         weekday_names = [ | 
					
						
							|  |  |  |             weekstart.shift(days=+_).format('ddd', locale=self.language) | 
					
						
							|  |  |  |             for _ in range(7) | 
					
						
							|  |  |  |         ] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         logger.debug(f'weekday names: {weekday_names}') | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |         for index, weekday in enumerate(weekday_pos): | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             write( | 
					
						
							|  |  |  |                 im_black, | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 weekday, | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                 (icon_width, weekdays_height), | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                 weekday_names[index], | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                 font=self.font, | 
					
						
							|  |  |  |                 autofit=True, | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                 fill_height=0.9, | 
					
						
							| 
									
										
										
										
											2021-02-02 23:52:52 +01:00
										 |  |  |             ) | 
					
						
							| 
									
										
										
										
											2020-05-18 03:46:49 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # Create a calendar template and flatten (remove nestings) | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |         calendar_flat = self.flatten(cal.monthcalendar(now.year, now.month)) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # logger.debug(f" calendar_flat: {calendar_flat}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # Map days of month to co-ordinates of grid -> 3: (row2_x,col3_y) | 
					
						
							|  |  |  |         grid = {} | 
					
						
							|  |  |  |         for i in calendar_flat: | 
					
						
							|  |  |  |             if i != 0: | 
					
						
							|  |  |  |                 grid[i] = grid_coordinates[calendar_flat.index(i)] | 
					
						
							|  |  |  |         # logger.debug(f"grid:{grid}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # remove zeros from calendar since they are not required | 
					
						
							|  |  |  |         calendar_flat = [num for num in calendar_flat if num != 0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |         # ensure all numbers have the same size | 
					
						
							|  |  |  |         fontsize_numbers = int(min(icon_width, icon_height) * 0.5) | 
					
						
							|  |  |  |         number_font = ImageFont.truetype(self.font.path, fontsize_numbers) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         # Add the numbers on the correct positions | 
					
						
							|  |  |  |         for number in calendar_flat: | 
					
						
							|  |  |  |             if number != int(now.day): | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 write( | 
					
						
							|  |  |  |                     im_black, | 
					
						
							|  |  |  |                     grid[number], | 
					
						
							|  |  |  |                     (icon_width, icon_height), | 
					
						
							|  |  |  |                     str(number), | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                     font=number_font, | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # Draw a red/black circle with the current day of month in white | 
					
						
							|  |  |  |         icon = Image.new('RGBA', (icon_width, icon_height)) | 
					
						
							|  |  |  |         current_day_pos = grid[int(now.day)] | 
					
						
							|  |  |  |         x_circle, y_circle = int(icon_width / 2), int(icon_height / 2) | 
					
						
							|  |  |  |         radius = int(icon_width * 0.2) | 
					
						
							|  |  |  |         ImageDraw.Draw(icon).ellipse( | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             ( | 
					
						
							|  |  |  |                 x_circle - radius, | 
					
						
							|  |  |  |                 y_circle - radius, | 
					
						
							|  |  |  |                 x_circle + radius, | 
					
						
							|  |  |  |                 y_circle + radius, | 
					
						
							|  |  |  |             ), | 
					
						
							|  |  |  |             fill='black', | 
					
						
							|  |  |  |             outline=None, | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |         write( | 
					
						
							|  |  |  |             icon, | 
					
						
							|  |  |  |             (0, 0), | 
					
						
							|  |  |  |             (icon_width, icon_height), | 
					
						
							|  |  |  |             str(now.day), | 
					
						
							|  |  |  |             font=self.num_font, | 
					
						
							|  |  |  |             fill_height=0.5, | 
					
						
							|  |  |  |             colour='white', | 
					
						
							|  |  |  |         ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |         im_colour.paste(icon, current_day_pos, icon) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # If events should be loaded and shown... | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |         if self.show_events: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # If this month requires 5 instead of 6 rows, increase event section height | 
					
						
							|  |  |  |             if len(cal.monthcalendar(now.year, now.month)) == 5: | 
					
						
							|  |  |  |                 events_height += icon_height | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # If this month requires 4 instead of 6 rows, increase event section height | 
					
						
							|  |  |  |             elif len(cal.monthcalendar(now.year, now.month)) == 4: | 
					
						
							|  |  |  |                 events_height += icon_height * 2 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # import the ical-parser | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             # pylint: disable=import-outside-toplevel | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             from inkycal.modules.ical_parser import iCalendar | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # find out how many lines can fit at max in the event section | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |             line_spacing = 2 | 
					
						
							|  |  |  |             text_bbox_height = self.font.getbbox("hg") | 
					
						
							| 
									
										
										
										
											2023-11-21 16:03:09 +01:00
										 |  |  |             line_height = text_bbox_height[3] + line_spacing | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |             max_event_lines = events_height // (line_height + line_spacing) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # generate list of coordinates for each line | 
					
						
							|  |  |  |             events_offset = im_height - events_height | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             event_lines = [ | 
					
						
							|  |  |  |                 (0, events_offset + int(events_height / max_event_lines * _)) | 
					
						
							|  |  |  |                 for _ in range(max_event_lines) | 
					
						
							|  |  |  |             ] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # logger.debug(f"event_lines {event_lines}") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # timeline for filtering events within this month | 
					
						
							|  |  |  |             month_start = arrow.get(now.floor('month')) | 
					
						
							|  |  |  |             month_end = arrow.get(now.ceil('month')) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # fetch events from given icalendars | 
					
						
							|  |  |  |             self.ical = iCalendar() | 
					
						
							|  |  |  |             parser = self.ical | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if self.ical_urls: | 
					
						
							|  |  |  |                 parser.load_url(self.ical_urls) | 
					
						
							|  |  |  |             if self.ical_files: | 
					
						
							|  |  |  |                 parser.load_from_file(self.ical_files) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Filter events for full month (even past ones) for drawing event icons | 
					
						
							|  |  |  |             month_events = parser.get_events(month_start, month_end, self.timezone) | 
					
						
							|  |  |  |             parser.sort() | 
					
						
							|  |  |  |             self.month_events = month_events | 
					
						
							| 
									
										
										
										
											2023-11-10 14:57:47 +01:00
										 |  |  |              | 
					
						
							|  |  |  |             # Initialize days_with_events as an empty list | 
					
						
							|  |  |  |             days_with_events = [] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-10 14:57:47 +01:00
										 |  |  |             # 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) | 
					
						
							|  |  |  |                 end = arrow.get(event['end'].date(), tzinfo=self.timezone) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Use arrow's range function for generating dates | 
					
						
							|  |  |  |                 for day in arrow.Arrow.range('day', start, end): | 
					
						
							|  |  |  |                     day_num = int(day.format('D'))  # get day number using arrow's format method | 
					
						
							|  |  |  |                     if day_num not in days_with_events: | 
					
						
							|  |  |  |                         days_with_events.append(day_num) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # remove duplicates (more than one event in a single day) | 
					
						
							| 
									
										
										
										
											2023-11-10 14:57:47 +01:00
										 |  |  |             days_with_events = sorted(set(days_with_events)) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             self._days_with_events = days_with_events | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Draw a border with specified parameters around days with events | 
					
						
							|  |  |  |             for days in days_with_events: | 
					
						
							|  |  |  |                 if days in grid: | 
					
						
							|  |  |  |                     draw_border( | 
					
						
							|  |  |  |                         im_colour, | 
					
						
							|  |  |  |                         grid[days], | 
					
						
							|  |  |  |                         (icon_width, icon_height), | 
					
						
							|  |  |  |                         radius=6, | 
					
						
							|  |  |  |                         thickness=1, | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                         shrinkage=(0.4, 0.2), | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                     ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # Filter upcoming events until 4 weeks in the future | 
					
						
							|  |  |  |             parser.clear_events() | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |             upcoming_events = parser.get_events(now, now.shift(weeks=4), self.timezone) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |             self._upcoming_events = upcoming_events | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # delete events which won't be able to fit (more events than lines) | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |             upcoming_events = upcoming_events[:max_event_lines] | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             # Check if any events were found in the given timerange | 
					
						
							|  |  |  |             if upcoming_events: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 # Find out how much space (width) the date format requires | 
					
						
							|  |  |  |                 lang = self.language | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                 date_width = int(max(( | 
					
						
							|  |  |  |                     self.font.getlength(events['begin'].format(self.date_format, locale=lang)) | 
					
						
							|  |  |  |                     for events in upcoming_events))* 1.1 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                 time_width = int(max(( | 
					
						
							|  |  |  |                     self.font.getlength(events['begin'].format(self.time_format, locale=lang)) | 
					
						
							|  |  |  |                     for events in upcoming_events))* 1.1 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                 text_bbox_height = self.font.getbbox("hg") | 
					
						
							| 
									
										
										
										
											2023-11-21 16:03:09 +01:00
										 |  |  |                 line_height = text_bbox_height[3] + line_spacing | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 event_width_s = im_width - date_width - time_width | 
					
						
							|  |  |  |                 event_width_l = im_width - date_width | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 # Display upcoming events below calendar TODO: not used? | 
					
						
							|  |  |  |                 # tomorrow = now.shift(days=1).floor('day') | 
					
						
							|  |  |  |                 # in_two_days = now.shift(days=2).floor('day') | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 cursor = 0 | 
					
						
							|  |  |  |                 for event in upcoming_events: | 
					
						
							|  |  |  |                     if cursor < len(event_lines): | 
					
						
							| 
									
										
										
										
											2023-11-10 13:39:08 +01:00
										 |  |  |                         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) | 
					
						
							|  |  |  |                             the_name = f"{event['title']} ({days_translation})" | 
					
						
							|  |  |  |                         else: | 
					
						
							|  |  |  |                             the_name = event['title'] | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                         the_date = event['begin'].format(self.date_format, locale=lang) | 
					
						
							|  |  |  |                         the_time = event['begin'].format(self.time_format, locale=lang) | 
					
						
							|  |  |  |                         # logger.debug(f"name:{the_name}   date:{the_date} time:{the_time}") | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                         if now < event['end']: | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                             write( | 
					
						
							|  |  |  |                                 im_colour, | 
					
						
							|  |  |  |                                 event_lines[cursor], | 
					
						
							|  |  |  |                                 (date_width, line_height), | 
					
						
							|  |  |  |                                 the_date, | 
					
						
							|  |  |  |                                 font=self.font, | 
					
						
							|  |  |  |                                 alignment='left', | 
					
						
							|  |  |  |                             ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                             # Check if event is all day | 
					
						
							| 
									
										
										
										
											2022-04-10 06:35:08 +02:00
										 |  |  |                             if parser.all_day(event): | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                                 write( | 
					
						
							|  |  |  |                                     im_black, | 
					
						
							|  |  |  |                                     (date_width, event_lines[cursor][1]), | 
					
						
							|  |  |  |                                     (event_width_l, line_height), | 
					
						
							|  |  |  |                                     the_name, | 
					
						
							|  |  |  |                                     font=self.font, | 
					
						
							|  |  |  |                                     alignment='left', | 
					
						
							|  |  |  |                                 ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                             else: | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                                 write( | 
					
						
							|  |  |  |                                     im_black, | 
					
						
							|  |  |  |                                     (date_width, event_lines[cursor][1]), | 
					
						
							|  |  |  |                                     (time_width, line_height), | 
					
						
							|  |  |  |                                     the_time, | 
					
						
							|  |  |  |                                     font=self.font, | 
					
						
							|  |  |  |                                     alignment='left', | 
					
						
							|  |  |  |                                 ) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                                 write( | 
					
						
							|  |  |  |                                     im_black, | 
					
						
							|  |  |  |                                     (date_width + time_width, event_lines[cursor][1]), | 
					
						
							|  |  |  |                                     (event_width_s, line_height), | 
					
						
							|  |  |  |                                     the_name, | 
					
						
							|  |  |  |                                     font=self.font, | 
					
						
							|  |  |  |                                     alignment='left', | 
					
						
							|  |  |  |                                 ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                             cursor += 1 | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 symbol = '- ' | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 while self.font.getlength(symbol) < im_width * 0.9: | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  |                     symbol += ' -' | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                 write( | 
					
						
							|  |  |  |                     im_black, | 
					
						
							|  |  |  |                     event_lines[0], | 
					
						
							| 
									
										
										
										
											2023-11-07 22:49:48 +01:00
										 |  |  |                     (im_width, line_height), | 
					
						
							| 
									
										
										
										
											2023-02-25 15:38:15 +02:00
										 |  |  |                     symbol, | 
					
						
							|  |  |  |                     font=self.font, | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2022-04-02 01:30:17 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # return the images ready for the display | 
					
						
							|  |  |  |         return im_black, im_colour |