diff --git a/Calendar/Assets.py b/Calendar/Assets.py index 14442c0..bb8ffff 100644 --- a/Calendar/Assets.py +++ b/Calendar/Assets.py @@ -32,12 +32,6 @@ fonts = { defaultfont = fonts[font_boldness] defaultfontsize = int(font_size) -datetime_locals = { - "de" : "de_DE.UTF-8", - "en" : "en_US.UTF-8", - "zh_TW" : "zh_TW.UTF-8" -} - weathericons = { '01d': 'wi-day-sunny', '02d':'wi-day-cloudy', '03d': 'wi-cloudy', '04d': 'wi-cloudy-windy', '09d': 'wi-showers', '10d':'wi-rain', diff --git a/Calendar/DayBoxDesign.py b/Calendar/DayBoxDesign.py new file mode 100644 index 0000000..2128280 --- /dev/null +++ b/Calendar/DayBoxDesign.py @@ -0,0 +1,13 @@ +from DesignEntity import DesignEntity + +class DayBoxDesign (DesignEntity): + """Represents a day with its events in a box.""" + def __init__(self, size, date): + super(DayBoxDesign, self).__init__(size) + self.date = date + + def add_calendar(self, calendar): + pass + + def __finish_image__(self): + pass \ No newline at end of file diff --git a/Calendar/Dictionary.py b/Calendar/Dictionary.py new file mode 100644 index 0000000..9b18dfb --- /dev/null +++ b/Calendar/Dictionary.py @@ -0,0 +1,16 @@ +default_language = "en" + +'''Characters following '*' are placeholders and will be replaced by some number/text/etc.''' + +more_events = { + 'en' : '+ *0 more', + 'de' : '+ *0 weitere' +} +multiday_events = { + 'en' : 'Multi-day', + 'de' : 'Mehrtägig' +} +allday_events = { + 'en' : 'All-day', + 'de' : 'Ganztägig' +} \ No newline at end of file diff --git a/Calendar/DictionaryMapper.py b/Calendar/DictionaryMapper.py new file mode 100644 index 0000000..3d7c12a --- /dev/null +++ b/Calendar/DictionaryMapper.py @@ -0,0 +1,27 @@ +from Dictionary import default_language +from settings import language + +'''Takes a collection of phrases and outputs the necessary text +according to the language and inserts parameters.''' + +def get_text(dictionary, *params): + text = dictionary[default_language] + if language in dictionary.keys(): + text = dictionary[language] + + return __insert_params__(text, params) + +def __insert_params__(text, params): + index = 0 + while '*%d' % index in text and index < len(params): + splitted = text.split('*%d' % index) + text = splitted[0] + str(params[index]) + splitted[1] + index += 1 + while '*' in text: + splitted = text.split('*%d' % index) + if len(splitted) > 1: + text = splitted[0] + splitted[1].lstrip(' ') + else: + text = splitted[0].rsplit(' ') + index += 1 + return text \ No newline at end of file diff --git a/Calendar/E-Paper.py b/Calendar/E-Paper.py index a99a55a..ea477c6 100644 --- a/Calendar/E-Paper.py +++ b/Calendar/E-Paper.py @@ -9,20 +9,25 @@ Copyright by aceisace """ from datetime import datetime from time import sleep -from Assets import datetime_locals, path +from Assets import path from LoopTimer import LoopTimer import locale from DebugConsole import DebugConsole -from settings import language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours +from settings import datetime_encoding, language, render_to_display, render_to_file, display_colours, location, api_key, owm_paid_subscription, choosen_design, ical_urls, highlighted_ical_urls, rss_feeds, update_interval, calibrate_hours from MonthOvPanel import MonthOvPanel from DayListPanel import DayListPanel from DayViewPanel import DayViewPanel +from MonthViewPanel import MonthViewPanel from AgendaListPanel import AgendaListPanel import OwmForecasts import IcalEvents import RssParserPosts -locale.setlocale(locale.LC_ALL, datetime_locals[language]) +all_locales = locale.locale_alias +if language not in all_locales.keys(): + raise Exception("The locale for \"%s\" is currently not supported! If you need support, please open an issue on github." % language) +locale.setlocale(locale.LC_ALL, "%s.%s" % (all_locales[language].split('.')[0], datetime_encoding)) + debug = DebugConsole() output_adapters = [] @@ -45,7 +50,8 @@ available_panels = { "day-list" : DayListPanel, "month-overview" : MonthOvPanel, "day-view" : DayViewPanel, - "agenda-list" : AgendaListPanel + "agenda-list" : AgendaListPanel, + "month-view" : MonthViewPanel } loop_timer = LoopTimer(update_interval, run_on_hour=True) diff --git a/Calendar/EventListDesign.py b/Calendar/EventListDesign.py index a7772c6..e59b891 100644 --- a/Calendar/EventListDesign.py +++ b/Calendar/EventListDesign.py @@ -3,6 +3,8 @@ from TableDesign import TableDesign from settings import language from Assets import defaultfontsize, colors from TextFormatter import date_str +from DictionaryMapper import get_text +from Dictionary import more_events class EventListDesign (DesignEntity): """Creates a TableDesign filled with event @@ -30,7 +32,7 @@ class EventListDesign (DesignEntity): self.__fill_event_matrix__() col_hori_alignment = [ 'right', 'left' ] - table_design = TableDesign(self.size, background_color = self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing, text_matrix=self.__event_matrix__, fontsize = self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__) + table_design = TableDesign(self.size, background_color = self.background_color, font=self.font_family, line_spacing=self.line_spacing, col_spacing=self.col_spacing, matrix=self.__event_matrix__, fontsize = self.text_size, column_horizontal_alignments=col_hori_alignment, mask=False, truncate_cols=False, cell_properties=self.__props_matrix__) self.draw_design(table_design) def __get_formatted_event__ (self, event, index): @@ -51,9 +53,9 @@ class EventListDesign (DesignEntity): return additional_events_count = len(self.events) - len(visible_events) - more_text = self.__get_more_text__() + more_text = " " + get_text(more_events, additional_events_count) if additional_events_count > 0: - self.__event_matrix__.append([ "", " + " + str(additional_events_count) + " " + more_text ]) + self.__event_matrix__.append([ "", more_text ]) self.__props_matrix__.append(self.__get_row_props__()) def __get_row_props__ (self, event = None): @@ -65,11 +67,4 @@ class EventListDesign (DesignEntity): "color" : color, "background_color" : bg_color } - return [ cell, cell ] - - def __get_more_text__ (self): - more_texts = { - "de" : "weitere", - "en" : "more" - } - return more_texts[language] \ No newline at end of file + return [ cell, cell ] \ No newline at end of file diff --git a/Calendar/IcalEvents.py b/Calendar/IcalEvents.py index 86ae6e0..ae348c1 100644 --- a/Calendar/IcalEvents.py +++ b/Calendar/IcalEvents.py @@ -14,18 +14,13 @@ class IcalEvents(CalendarInterface): super(IcalEvents, self).__init__() def is_available(self): - try: - testurl = "" - if self.urls: - testurl = self.urls[0] - elif self.highlighted_urls: - testurl = self.highlighted_urls[0] - else: - return False - urlopen(testurl) - return True - except: - return False + for url in self.urls + self.highlighted_urls: + try: + urlopen(url) + return True + except: + pass + return False def __get_events__(self): events = self.__get_events_from_urls__(self.urls) diff --git a/Calendar/MonthViewPanel.py b/Calendar/MonthViewPanel.py new file mode 100644 index 0000000..ff81348 --- /dev/null +++ b/Calendar/MonthViewPanel.py @@ -0,0 +1,110 @@ +from PanelDesign import PanelDesign +from settings import general_settings, week_starts_on +from PIL import ImageDraw +from datetime import date +from Assets import colors +import calendar as callib +from TableDesign import TableDesign +from DayBoxDesign import DayBoxDesign +from RssPostListDesign import RssPostListDesign +from WeatherHeaderDesign import WeatherHeaderDesign + +weather_height = 0.113 +info_height = 0.25 +info_padding = 5 +seperator_width = 3 + +class MonthViewPanel (PanelDesign): + """Displays a grid of the day of the current month + with detailed event descriptions.""" + def __init__(self, size, month = None, year = None): + super(MonthViewPanel, self).__init__(size) + self.day_table = [] + self.month = month + if self.month == None: + self.month = date.today().month + self.year = year + if self.year == None: + self.year = date.today().year + self.__init_sizes__() + self.__init_day_boxes__() + + def __init_sizes__(self): + self.weather_height = 0 + self.info_height = 0 + if general_settings["info-area"] in ["events", "rss"]: + self.info_height = info_height + if general_settings["weather-info"]: + self.weather_height = weather_height + self.day_area_height = 1 - self.weather_height - self.info_height + self.day_area_ypos = self.weather_height + + area_height = self.size[1] * self.day_area_height + area_width = self.size[0] + self.day_box_size = (area_width / 7, area_height) + + def add_weather (self, weather): + if general_settings["weather-info"] == False: + return + size = (self.size[0], self.size[1] * self.weather_height) + + header = WeatherHeaderDesign(size, weather) + self.draw_design(header) + self.__draw_seperator__(size[1], colors["hl"]) + + def add_calendar (self, calendar): + self.__add_calendar_to_days__(calendar) + + def __add_calendar_to_days__(self, calendar): + for week in self.day_table: + for day in week: + if day != None: + day.add_calendar(calendar) + + def add_rssfeed (self, rss): + if general_settings["info-area"] != "rss": + return + + size = (self.size[0], self.size[1] * self.info_height) + pos = (0, self.size[1] - size[1] + info_padding) + + rss = RssPostListDesign(size, rss) + rss.pos = pos + self.draw_design(rss) + + def add_taks (self, tasks): + pass + + def __finish_panel__(self): + self.__draw_days__() + + def __draw_days__(self): + size = (self.size[0], self.size[1] * self.day_area_height) + pos = (0, self.size[0] * self.day_area_ypos) + + table = TableDesign(size, matrix = self.day_table) + table.pos = pos + self.draw_design(table) + + def __draw_seperator__ (self, height, color): + ImageDraw.Draw(self.__image__).line([ (0, height * self.size[1]), (self.size[0], height * self.size[1]) ], fill=color, width=seperator_width) + + def __init_day_boxes__(self): + if week_starts_on == "Monday": + callib.setfirstweekday(callib.MONDAY) + elif week_starts_on == "Sunday": + callib.setfirstweekday(callib.SUNDAY) + + weeks = callib.monthcalendar(self.year, self.month) + for i, week in enumerate(weeks): + self.day_table.append([]) + for day in week: + self.day_table[i].append(self.__create_day__(day)) + + def __create_day__(self, day): + if day == None or day == 0: + return None + + design = DayBoxDesign(self.day_box_size, date(self.year, self.month, int(day))) + + return design \ No newline at end of file diff --git a/Calendar/RssPostListDesign.py b/Calendar/RssPostListDesign.py index 96074ea..0ebaefd 100644 --- a/Calendar/RssPostListDesign.py +++ b/Calendar/RssPostListDesign.py @@ -14,7 +14,7 @@ class RssPostListDesign (DesignEntity): def __finish_image__ (self): self.__fill_post_matrix__() - table_design = TableDesign(self.size, line_spacing=5, col_spacing=3, text_matrix=self.__post_matrix__, fontsize = self.text_size, mask=False, truncate_cols=False, wrap=True) + table_design = TableDesign(self.size, line_spacing=5, col_spacing=3, matrix=self.__post_matrix__, fontsize = self.text_size, mask=False, truncate_cols=False, wrap=True) self.draw_design(table_design) def __get_formatted_post__ (self, post): diff --git a/Calendar/TableDesign.py b/Calendar/TableDesign.py index c5bcfff..c4fc7ff 100644 --- a/Calendar/TableDesign.py +++ b/Calendar/TableDesign.py @@ -1,6 +1,7 @@ from TextDesign import TextDesign from TextWraper import wrap_text_with_font from Assets import defaultfontsize, colors +from DesignEntity import DesignEntity default_props = { @@ -11,10 +12,10 @@ default_props = { class TableDesign (TextDesign): """Gets a matrix with text or designs that is than displayed in a table without borders.""" - def __init__ (self, size, text_matrix, max_col_size = None, max_row_size = None, font = None, fontsize = defaultfontsize, column_horizontal_alignments = [], mask = True, line_spacing = 0, col_spacing = 0, truncate_rows = True, truncate_cols = True, wrap = False, truncate_text=True, truncate_suffix="...", cell_properties=None, background_color = colors["bg"]): + def __init__ (self, size, matrix, max_col_size = None, max_row_size = None, font = None, fontsize = defaultfontsize, column_horizontal_alignments = [], mask = True, line_spacing = 0, col_spacing = 0, truncate_rows = True, truncate_cols = True, wrap = False, truncate_text=True, truncate_suffix="...", cell_properties=None, background_color = colors["bg"]): super(TableDesign, self).__init__(size, font=font, fontsize=fontsize, mask=mask) self.__init_image__(background_color) - self.matrix = text_matrix + self.matrix = matrix self.max_col_size = max_col_size self.max_row_size = max_row_size self.line_spacing = line_spacing @@ -42,11 +43,10 @@ class TableDesign (TextDesign): if self.max_col_size is not None: return - font = self.__get_font__() col_sizes = [] for c in range(len(self.matrix[0])): #amout of columns for r in range(len(self.matrix)): - row_col_size = font.getsize(self.matrix[r][c])[0] #get width of text in that row/col + row_col_size = self.__get_cell_size__(r,c)[0] if len(col_sizes) - 1 < c: col_sizes.append(row_col_size) elif row_col_size > col_sizes[c]: @@ -64,14 +64,10 @@ class TableDesign (TextDesign): if self.max_row_size is not None: return - font = self.__get_font__() row_sizes = [] for r in range(len(self.matrix)): for c in range(len(self.matrix[0])): #amout of columns - cell_text = self.matrix[r][c] - if self.wrap: - cell_text = wrap_text_with_font(cell_text, self.max_col_size[c], font) - col_row_size = font.getsize_multiline(cell_text)[1] #get height of text in that col/row + col_row_size = self.__get_cell_size__(r,c)[1] if len(row_sizes) - 1 < r: row_sizes.append(col_row_size) elif col_row_size > row_sizes[r]: @@ -79,6 +75,24 @@ class TableDesign (TextDesign): self.max_row_size = row_sizes + def __get_cell_size__(self, r, c): + content = self.matrix[r][c] + size = (0, 0) + + if content == None: + return size + elif type(content) == str: + font = self.__get_font__() + width = font.getsize(self.matrix[r][c])[0] #get width of text in that row/col + if self.wrap and self.max_col_size != None: + content = wrap_text_with_font(content, self.max_col_size[c], font) + height = font.getsize_multiline(content)[1] #get height of text in that col/row + size = (width, height) + else: #DesignEntity + size = content.size + + return size + def __get_truncated_counts__ (self): max_col = 0 if self.truncate_cols: @@ -99,9 +113,7 @@ class TableDesign (TextDesign): def __print_table__ (self, matrix): for r in range(self.max_row): for c in range(self.max_col): - size = self.cell_sizes[r][c] - pos = self.__get_cell_pos__(r,c) - self.__draw_text__(pos, size, r, c) + self.__draw_cell__(r,c) def __draw_text__ (self, pos, size, row, col): color = self.__get_cell_prop__(row, col, "color") @@ -110,6 +122,30 @@ class TableDesign (TextDesign): design = TextDesign(size, text=self.matrix[row][col], font=self.font_family, color=color, background_color=bg_color, fontsize=self.font_size, horizontalalignment=self.__get_col_hori_alignment__(col), wrap=self.wrap, truncate=self.truncate_text, truncate_suffix=self.truncate_suffix) design.pos = pos self.draw_design(design) + + def __draw_design__ (self, pos, size, row, col): + bg_color = self.__get_cell_prop__(row, col, "background_color") + + source_design = self.matrix[row][col] + source_design.mask = False + + framed_design = DesignEntity(size, mask = False) + framed_design.__init_image__(color=bg_color) + framed_design.draw_design(source_design) + + framed_design.pos = pos + self.draw_design(framed_design) + + def __draw_cell__ (self, row, col): + size = self.cell_sizes[row][col] + pos = self.__get_cell_pos__(row,col) + + if self.matrix[row][col] == None: + return + elif type(self.matrix[row][col]) == str: + self.__draw_text__(pos, size, row, col) + else: + self.__draw_design__(pos, size, row, col) def __get_cell_pos__ (self, row, col): xpos, ypos = (0, 0) diff --git a/Calendar/TextFormatter.py b/Calendar/TextFormatter.py index a74ee02..94e5b59 100644 --- a/Calendar/TextFormatter.py +++ b/Calendar/TextFormatter.py @@ -1,5 +1,7 @@ from settings import hours, language from datetime import timedelta, datetime, timezone +from DictionaryMapper import get_text +from Dictionary import multiday_events, allday_events first_occurrence_char = '[' middle_occurrence_char = '|' @@ -10,18 +12,6 @@ until_character = ' - ' allday_character = "•" multiday_character = allday_character + allday_character -allday_lang = { - "en" : "All day", - "de" : "Ganztägig" -} -allday_detailed = allday_lang[language] - -multiday_lang = { - "en" : "Multi-day", - "de" : "Mehrtägig" -} -multiday_detailed = multiday_lang[language] - def time_str (dt): if hours is "12": return dt.strftime("%I:%M%p") @@ -57,7 +47,7 @@ def event_prefix_str (event, relative_date=None): relative_date = event.begin_datetime.date() if event.multiday: - return multiday_detailed + return get_text(multiday_events) else: return event_time_detailed(event) @@ -78,7 +68,7 @@ def event_time_summary (event): def event_time_detailed (event): if event.allday: - return allday_detailed + return get_text(allday_events) else: return time_str(event.begin_datetime) + until_character + time_str(event.end_datetime) diff --git a/Calendar/settings.py.sample b/Calendar/settings.py.sample index 0fae9af..a900641 100644 --- a/Calendar/settings.py.sample +++ b/Calendar/settings.py.sample @@ -17,6 +17,7 @@ location = "Julich, DE" week_starts_on = "Monday" display_colours = "bwr" language = "en" +datetime_encoding = "UTF-8" # UTF-8 units = "metric" hours = "24" update_interval = 60 @@ -25,7 +26,7 @@ update_interval = 60 """DESIGN""" font_size = 14 # does not affect every text font_boldness = "semibold" # extralight, light, regular, semibold, bold, extrabold -choosen_design = "month-overview" # month-overview, day-list, day-view, agenda-list +choosen_design = "month-overview" # month-overview, day-list, day-view, agenda-list, month-view general_settings = { # General settings that designs may use "info-area" : "rss", # empty, events, rss "highlight-event-days" : True, diff --git a/README.md b/README.md index cb1bcf0..f6e5375 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,8 @@ Once the packages are installed, navigate to the home directory, open 'E-Paper-M | location | Location refers to the closest weather station from your place. It isn't necessarily the place you live in. To find this location, type your city name in the search box on [openweathermap](https://openweathermap.org/). The output should be in the following format: City Name, Country ISO-Code. Not sure what your ISO code is? Check here: [(find iso-code)](https://countrycode.org/). | | week_starts_on | When does the work start on your Region? Possible options are `"Monday"` or `"Sunday"`. | | display_colours | This should normally be set by the installer when you choose the type of your display. Options include `"bw"` if you're using the black and white E-Paper or `"bwr"` when you're using the black-white-red or black-white-yellow E-Paper.| -| language | Choosing the language allows changing the language of the month and week-icons. Possible options are `"en"` for english and `"de"` for german.| +| language | Sets the language and the related locale for datetime-information. Some texts depend on additional translations that can be added to the dictionary-file.| +| datetime_encoding | Sets the encoding that will be used in the datetime-texts (month, weekday, ...). Default is `"UTF-8"`.| |units| Selecting units allows switching units from km/h (kilometer per hour) and °C (degree Celcius) to mph (miles per hour) and °F (degree Fahrenheit). Possible options are `"metric"` or `"imperial"`. | |hours | Which time format do you prefer? This will change the sunrise and sunset times from 24-hours format to 12-hours format. Possible options are `"24"` for 24-hours and `"12"` for 12-hours.| |update_interval | The update delay between two updates in minutes. By default there is always an update on a full hour.|