Source code for timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer

# Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg
#
# This file is part of Timeline.
#
# Timeline is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Timeline is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Timeline.  If not, see <http://www.gnu.org/licenses/>.


import os

import wx

from timelinelib.canvas.drawing.utils import black_solid_pen, black_solid_brush, get_colour, darken_color
from timelinelib.config.paths import EVENT_ICONS_DIR
from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT
from timelinelib.canvas.eventboxdrawers.defaultmilestonedrawer import DefaultMilestoneDrawer
from timelinelib.canvas.eventboxdrawers.handlerect import HandleRect, MIDDLE_HANDLE, LEFT_HANDLE, RIGHT_HANDLE


HANDLE_SIZE = 4
HALF_HANDLE_SIZE = HANDLE_SIZE // 2
DATA_INDICATOR_SIZE = 10
INNER_PADDING = 3  # Space inside event box to text (pixels)
GRAY = (200, 200, 200)


[docs]class DefaultEventBoxDrawer:
[docs] def draw(self, dc, scene, rect, event, view_properties): self.scene = scene self.view_properties = view_properties selected = view_properties.is_selected(event) self.center_text = scene.center_text() if event.is_milestone(): DefaultMilestoneDrawer(rect, event, selected).draw(dc) elif scene.never_show_period_events_as_point_events() and rect.y < scene.divider_y and event.is_period(): self._draw_period_event_as_symbol_below_divider_line(dc, scene, event) else: self._draw_event_box(dc, rect, event, selected)
def _draw_period_event_as_symbol_below_divider_line(self, dc, scene, event): dc.DestroyClippingRegion() x = scene.x_pos_for_time(event.mean_time()) y0 = scene.divider_y y1 = y0 + 10 dc.SetBrush(black_solid_brush()) dc.SetPen(black_solid_pen(1)) dc.DrawLine(x, y0, x, y1) dc.DrawCircle(x, y1, 2) def _draw_event_box(self, dc, rect, event, selected): self._draw_background(dc, rect, event) self._draw_fuzzy_edges(dc, rect, event) self._draw_locked_edges(dc, rect, event) self._draw_progress_box(dc, rect, event) self._draw_text(dc, rect, event) self._draw_contents_indicator(dc, event, rect) self._draw_locked_edges(dc, rect, event) self._draw_selection_handles(dc, event, rect, selected) self._draw_hyperlink(dc, rect, event) def _draw_background(self, dc, rect, event): dc.SetBrush(wx.Brush(event.get_color(), wx.BRUSHSTYLE_SOLID)) dc.SetPen(self._get_pen(dc, event)) dc.DrawRectangle(rect) def _get_pen(self, dc, event): pen = self._get_thin_border_pen(event) if self.view_properties.is_highlighted(event): if self.view_properties.get_highlight_count(event) % 2 == 0: dc.DestroyClippingRegion() pen = self._get_thick_border_pen(event) return pen def _draw_fuzzy_edges(self, dc, rect, event): if event.fuzzy_start: self._draw_fuzzy_start(dc, rect) if event.fuzzy_end: if not event.get_ends_today(): self._draw_fuzzy_end(dc, rect) def _draw_locked_edges(self, dc, rect, event): if event.get_ends_today(): self._draw_locked_end(dc, rect) if event.get_locked(): self._draw_locked_start(dc, rect) self._draw_locked_end(dc, rect) def _draw_contents_indicator(self, dc, event, rect): if event.has_balloon_data(): self._draw_balloon_indicator(dc, event, rect) def _draw_selection_handles(self, dc, event, rect, selected): if not event.locked and selected: self._draw_handles(dc, event, rect) def _get_thin_border_pen(self, event): return self._get_border_pen(event) def _get_thick_border_pen(self, event): return self._get_border_pen(event, thickness=8) def _get_border_pen(self, event, thickness=1): return wx.Pen(event.get_border_color(), thickness, wx.PENSTYLE_SOLID) def _get_balloon_indicator_brush(self, event): base_color = event.get_color() darker_color = darken_color(base_color, 0.6) brush = wx.Brush(darker_color, wx.BRUSHSTYLE_SOLID) return brush def _draw_fuzzy_start(self, dc, rect): self._inflate_clipping_region(dc, rect) dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x - 4, rect.y + 4, True) def _draw_fuzzy_end(self, dc, rect): self._inflate_clipping_region(dc, rect) dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x + rect.width - 8, rect.y + 4, True)
[docs] def draw_fuzzy(self, dc, event, p1, p2, p3, p4, p5): self._erase_outzide_fuzzy_box(dc, p1, p2, p3) self._erase_outzide_fuzzy_box(dc, p3, p4, p5) self._draw_fuzzy_border(dc, event, p2, p3, p5)
def _erase_outzide_fuzzy_box(self, dc, p1, p2, p3): dc.SetBrush(wx.WHITE_BRUSH) dc.SetPen(wx.WHITE_PEN) dc.DrawPolygon((p1, p2, p3)) def _draw_fuzzy_border(self, dc, event, p1, p2, p3): gc = wx.GraphicsContext.Create(dc) path = gc.CreatePath() path.MoveToPoint(p1.x, p1.y) path.AddLineToPoint(p2.x, p2.y) path.AddLineToPoint(p3.x, p3.y) gc.SetPen(self._get_thin_border_pen(event)) gc.StrokePath(path) def _draw_locked_start(self, dc, rect): self._inflate_clipping_region(dc, rect) dc.DrawBitmap(self._get_lock_bitmap(), rect.x - 7, rect.y + 3, True) def _draw_locked_end(self, dc, rect): self._inflate_clipping_region(dc, rect) dc.DrawBitmap(self._get_lock_bitmap(), rect.x + rect.width - 8, rect.y + 3, True) def _draw_progress_box(self, dc, rect, event): if event.get_data("progress"): self._set_progress_color(dc, event) progress_rect = self._get_progress_rect(rect, event) dc.DrawRectangle(progress_rect) def _set_progress_color(self, dc, event): progress_color = event.get_progress_color() dc.SetBrush(wx.Brush(wx.Colour(progress_color[0], progress_color[1], progress_color[2]))) def _get_progress_rect(self, event_rect, event): HEIGHT_FACTOR = 0.35 h = event_rect.height * HEIGHT_FACTOR y = event_rect.y + (event_rect.height - h) rw, _ = self.scene._calc_width_and_height_for_period_event(event) rx = self.scene._calc_x_pos_for_period_event(event) w = rw * event.get_data("progress") / 100.0 # Avoid overflow values in args to wx.Rect() # which can happen when rx has a very large negative or positive value. if rx < 0: w += rx rx = 0 if w > self.scene.width: w = self.scene.width return wx.Rect(int(rx), int(y), int(w), int(h)) def _draw_balloon_indicator(self, dc, event, rect): """ The data contents indicator is a small triangle drawn in the upper right corner of the event rectangle. """ corner_x = rect.X + rect.Width points = ( wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y), wx.Point(corner_x, rect.Y), wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE), ) dc.SetBrush(self._get_balloon_indicator_brush(event)) dc.SetPen(wx.TRANSPARENT_PEN) dc.DrawPolygon(points) def _draw_text(self, dc, rect, event): # Ensure that we can't draw content outside inner rectangle if self._there_is_room_for_the_text(rect): self._draw_the_text(dc, rect, event) def _there_is_room_for_the_text(self, rect): return deflate_rect(rect).Width > 0 def _draw_the_text(self, dc, rect, event): self._set_text_foreground_color(dc, event) if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled(): EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect) else: self._draw_normal_text(dc, rect, event) dc.DestroyClippingRegion() def _draw_normal_text(self, dc, rect, event): self._set_clipping_rect(dc, rect) dc.DrawText(self._get_text(event), self._calc_x_pos(dc, rect, event), self._calc_y_pos(rect)) def _get_text(self, event): if event.get_progress() == 100 and self.view_properties.get_display_checkmark_on_events_done(): return "\u2714" + event.get_text() else: return event.get_text() def _set_clipping_rect(self, dc, rect): dc.SetClippingRegion(deflate_rect(rect)) def _calc_x_pos(self, dc, rect, event): inner_rect = deflate_rect(rect) text_x = inner_rect.X text_x = self._adjust_x_for_edge_icons(event, rect, text_x) text_x = self._adjust_x_for_centered_text(dc, event, inner_rect, text_x) return text_x def _adjust_x_for_edge_icons(self, event, rect, text_x): if event.has_edge_icons(): text_x += rect.Height // 2 return text_x def _adjust_x_for_centered_text(self, dc, event, inner_rect, text_x): if self.center_text: text_x = self._center_text(dc, event, inner_rect, text_x) return text_x def _calc_y_pos(self, rect): return deflate_rect(rect).Y def _center_text(self, dc, event, inner_rect, text_x): width, _ = dc.GetTextExtent(self._get_text(event)) return max(text_x, text_x + (inner_rect.width - width) // 2) def _set_text_foreground_color(self, dc, event): try: dc.SetTextForeground(get_colour(event.get_category().font_color)) except AttributeError: dc.SetTextForeground(wx.BLACK) def _draw_handles(self, dc, event, rect): def draw_frame_around_event(): small_rect = wx.Rect(*rect) small_rect.Deflate(1, 1) border_color = event.get_border_color() border_color = darken_color(border_color) pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID) dc.SetBrush(wx.TRANSPARENT_BRUSH) dc.SetPen(pen) dc.DrawRectangle(small_rect) dc.SetClippingRegion(rect) draw_frame_around_event() self._draw_all_handles(dc, rect) dc.DestroyClippingRegion() @staticmethod def _draw_all_handles(dc, rect): dc.DestroyClippingRegion() HandleRect(rect, LEFT_HANDLE).draw(dc) HandleRect(rect, MIDDLE_HANDLE).draw(dc) HandleRect(rect, RIGHT_HANDLE).draw(dc) def _draw_hyperlink(self, dc, rect, event): if event.get_hyperlink(): dc.DrawBitmap(self._get_hyperlink_bitmap(), rect.x + rect.width - 14, rect.y + 4, True) @staticmethod def _inflate_clipping_region(dc, rect): copy = wx.Rect(*rect) copy.Inflate(10, 0) dc.DestroyClippingRegion() dc.SetClippingRegion(copy) def _get_hyperlink_bitmap(self): return get_bitmap(self.view_properties.get_hyperlink_icon()) def _get_lock_bitmap(self): return get_bitmap(self.view_properties.get_locked_icon()) def _get_fuzzy_bitmap(self): return get_bitmap(self.view_properties.get_fuzzy_icon())
[docs]def deflate_rect(rect, dx=INNER_PADDING, dy=INNER_PADDING): return wx.Rect(*rect).Deflate(dx, dy)
[docs]def center_point_with_offset(rect, dx=0, dy=0): y = rect.Y + rect.Height // 2 - dy x = rect.X + rect.Width // 2 - dx return wx.Point(x, y)
[docs]def draw_centered_text(dc, rect, label): size = dc.GetTextExtent(label) point = center_point_with_offset(rect, size.width // 2, size.height // 2) dc.DrawText(label, point)
[docs]def get_bitmap(name): return wx.Bitmap(os.path.join(EVENT_ICONS_DIR, name))