from DesignEntity import DesignEntity from settings import hours, language, line_thickness from TextDesign import TextDesign from PIL import ImageDraw from Assets import colors, defaultfontsize, fonts from BoxDesign import BoxDesign from datetime import timedelta, datetime hourbox_y_width = 1 hour_box_fontsize = 0.85 hour_ypadding = 0.1 hoursubtext_fontsize = 0.7 hoursubtext_height = 0.45 event_title_fontsize = defaultfontsize event_title_xpadding = 3 event_title_ypadding = 5 line_thickness = line_thickness currenttimeline_thickness = line_thickness event_title_font = fonts['bold'] class HourListDesign (DesignEntity): """Hours of a day are listed vertically and resemble a timeline.""" def __init__(self, size, first_hour=0, last_hour=23): super(HourListDesign, self).__init__(size) self.first_hour = first_hour self.last_hour = last_hour self.__calc_parameters__() self.events = [] def add_events(self, events): self.events.extend(events) self.events.sort(key=lambda x: x.begin_datetime) def __finish_image__(self): self.number_columns = self.__get_max_num_simultaneous_events__() self.__draw_lines__() self.__draw_events__() self.__draw_current_time_line__() self.__draw_hour_rows__() def __calc_parameters__(self): self.hour_count = self.last_hour - self.first_hour + 1 self.row_size = (self.size[0], self.size[1] / self.hour_count) def __get_hour_text__(self, hour): if hour <= 12 or hours is "24": return str(hour) else: short = hour - 12 return str(short) if short > 0 else "12" def __get_ypos_for_time__(self, hour, minute=0): return self.__get_height_for_duration__(hour, minute) - self.__get_height_for_duration__(self.first_hour) def __get_height_for_duration__(self, hours, minutes=0): row_height = self.row_size[1] return row_height * (hours + minutes / 60) def __draw_events__(self): column_events = [] for _ in range(self.number_columns): column_events.append(None) for event in self.events: column_events = self.__update_columns_events__( column_events, event) self.__draw_event__(event, column_events.index(event)) def __update_columns_events__(self, column_events, new_event): current_time = new_event.begin_datetime new_event_added = False for index in range(len(column_events)): if column_events[index] != None and column_events[index].end_datetime <= current_time: column_events[index] = None if new_event_added == False and column_events[index] == None: column_events[index] = new_event new_event_added = True return column_events def __draw_hour_rows__(self): for hour in range(self.first_hour, self.last_hour + 1): self.__draw_row__(hour) def __draw_row__(self, hour): subtext_height = self.row_size[1] * hoursubtext_height sub_fontsize = subtext_height * hoursubtext_fontsize ypadding = hour_ypadding * self.row_size[1] width = hourbox_y_width * self.row_size[1] height = self.row_size[1] - subtext_height size = (width, height) pos = (0, self.__get_ypos_for_time__(hour) + ypadding) fontsize = size[1] * hour_box_fontsize txt = TextDesign(size, text=self.__get_hour_text__( hour), fontsize=fontsize, verticalalignment="bottom", horizontalalignment="center") txt.pos = pos self.draw_design(txt) sub = TextDesign((width, subtext_height), text=self.__get_hour_sub_text__( hour), fontsize=sub_fontsize, verticalalignment="top", horizontalalignment="center") sub.pos = (0, height + self.__get_ypos_for_time__(hour)) self.draw_design(sub) def __draw_lines__(self): for i in range(self.hour_count): ypos = i * self.row_size[1] line_start = (0, ypos) line_end = (self.size[0], ypos) ImageDraw.Draw(self.__image__).line( [line_start, line_end], fill=colors["fg"], width=line_thickness) def __get_hour_sub_text__(self, hour): if hours == "12": return "AM" if hour < 12 else "PM" elif language is "de": return "Uhr" elif language is "en": return "o'c" return "" def __draw_event__(self, event, column=0): xoffset = hourbox_y_width * self.row_size[1] column_width = (self.size[0] - xoffset) / self.number_columns begin = event.begin_datetime time_ypos = self.__get_ypos_for_time__(begin.hour, begin.minute) hours = event.duration.total_seconds() / 3600 time_height = self.__get_height_for_duration__(hours) yoffset_correction = 0 if time_ypos < 0: yoffset_correction = time_ypos pos = (xoffset + column_width * column, time_ypos - yoffset_correction) size = (column_width, time_height + yoffset_correction) if size[1] < 0: return # Event not in shown time range self.__draw_event_block__(pos, size, event) def __draw_event_block__(self, pos, size, event): box_color = colors["hl"] if event.highlight else colors["fg"] box = BoxDesign(size, fill=box_color) box.mask = False box.pos = pos self.draw_design(box) text = event.title text_color = colors["bg"] textbox_size = (size[0] - event_title_xpadding, size[1] - event_title_ypadding) txt = TextDesign(textbox_size, text=text, font=event_title_font, fontsize=event_title_fontsize, color=text_color, background_color=box_color, wrap=True) txt.mask = False txt.pos = (pos[0] + event_title_xpadding, pos[1] + event_title_ypadding) self.draw_design(txt) half_ypadding = int(event_title_ypadding / 2) line_start = (pos[0] + event_title_xpadding, pos[1] + half_ypadding) line_end = (pos[0] + size[0] - event_title_xpadding, pos[1] + half_ypadding) ImageDraw.Draw(self.__image__).line( [line_start, line_end], fill=colors["bg"], width=1) def __get_max_num_simultaneous_events__(self): parallelity_count = 1 for index, event in enumerate(self.events): current_parallelity = 1 # Assumption: Events are ordered chronologically preceding = self.events[:index] for pre_event in preceding: if self.__are_simultaneous__(pre_event, event): current_parallelity += 1 if parallelity_count < current_parallelity: parallelity_count = current_parallelity return parallelity_count def __are_simultaneous__(self, ev_a, ev_b): if ev_a.begin_datetime > ev_b.begin_datetime: ev_a, ev_b = ev_b, ev_a mes_dur = ev_b.begin_datetime - ev_a.begin_datetime return mes_dur < ev_a.duration def __draw_current_time_line__(self): now = datetime.now() ypos = self.__get_ypos_for_time__(now.hour, now.minute) line_start = (0, ypos) line_end = (self.size[0], ypos) ImageDraw.Draw(self.__image__).line( [line_start, line_end], fill=colors["hl"], width=currenttimeline_thickness)