Package Gnumed :: Package timelinelib :: Package canvas :: Package eventboxdrawers :: Module defaulteventboxdrawer
[frames] | no frames]

Source Code for Module Gnumed.timelinelib.canvas.eventboxdrawers.defaulteventboxdrawer

  1  # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018  Rickard Lindberg, Roger Lindberg 
  2  # 
  3  # This file is part of Timeline. 
  4  # 
  5  # Timeline is free software: you can redistribute it and/or modify 
  6  # it under the terms of the GNU General Public License as published by 
  7  # the Free Software Foundation, either version 3 of the License, or 
  8  # (at your option) any later version. 
  9  # 
 10  # Timeline is distributed in the hope that it will be useful, 
 11  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 12  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 13  # GNU General Public License for more details. 
 14  # 
 15  # You should have received a copy of the GNU General Public License 
 16  # along with Timeline.  If not, see <http://www.gnu.org/licenses/>. 
 17   
 18   
 19  import os 
 20  import math 
 21   
 22  import wx 
 23   
 24  from timelinelib.canvas.drawing.utils import darken_color 
 25  from timelinelib.canvas.drawing.utils import get_colour 
 26  from timelinelib.config.paths import EVENT_ICONS_DIR 
 27  from timelinelib.features.experimental.experimentalfeatures import EXTENDED_CONTAINER_HEIGHT 
 28   
 29   
 30  HANDLE_SIZE = 4 
 31  HALF_HANDLE_SIZE = HANDLE_SIZE / 2 
 32  DATA_INDICATOR_SIZE = 10 
 33  INNER_PADDING = 3  # Space inside event box to text (pixels) 
 34  GRAY = (200, 200, 200) 
 35   
 36   
37 -class DefaultEventBoxDrawer(object):
38
39 - def draw(self, dc, scene, rect, event, view_properties):
40 self.scene = scene 41 self.view_properties = view_properties 42 selected = view_properties.is_selected(event) 43 self.center_text = scene.center_text() 44 if event.is_milestone(): 45 self._draw_milestone_event(dc, rect, scene, event, selected) 46 elif scene.never_show_period_events_as_point_events() and rect.y < scene.divider_y and event.is_period(): 47 self._draw_period_event_as_symbol_below_divider_line(dc, scene, event) 48 else: 49 self._draw_event_box(dc, rect, event, selected)
50
51 - def _draw_period_event_as_symbol_below_divider_line(self, dc, scene, event):
52 dc.DestroyClippingRegion() 53 x = scene.x_pos_for_time(event.mean_time()) 54 y0 = scene.divider_y 55 y1 = y0 + 10 56 dc.SetBrush(self._black_solid_brush()) 57 dc.SetPen(self._black_solid_pen(1)) 58 dc.DrawLine(x, y0, x, y1) 59 dc.DrawCircle(x, y1, 2)
60
61 - def _draw_event_box(self, dc, rect, event, selected):
62 self._draw_background(dc, rect, event) 63 self._draw_fuzzy_edges(dc, rect, event) 64 self._draw_locked_edges(dc, rect, event) 65 self._draw_progress_box(dc, rect, event) 66 self._draw_text(dc, rect, event) 67 self._draw_contents_indicator(dc, event, rect) 68 self._draw_locked_edges(dc, rect, event) 69 self._draw_selection_handles(dc, event, rect, selected) 70 self._draw_hyperlink(dc, rect, event)
71
72 - def _draw_background(self, dc, rect, event):
73 dc.SetBrush(wx.Brush(self._get_event_color(event), wx.BRUSHSTYLE_SOLID)) 74 dc.SetPen(self._get_pen(dc, event)) 75 dc.DrawRectangle(rect)
76
77 - def _get_pen(self, dc, event):
78 pen = self._get_thin_border_pen(event) 79 if self.view_properties.is_highlighted(event): 80 if self.view_properties.get_highlight_count(event) % 2 == 0: 81 dc.DestroyClippingRegion() 82 pen = self._get_thick_border_pen(event) 83 return pen
84
85 - def _draw_fuzzy_edges(self, dc, rect, event):
86 if event.get_fuzzy(): 87 self._draw_fuzzy_start(dc, rect, event) 88 if not event.get_ends_today(): 89 self._draw_fuzzy_end(dc, rect, event)
90
91 - def _draw_locked_edges(self, dc, rect, event):
92 if event.get_ends_today(): 93 self._draw_locked_end(dc, event, rect) 94 if event.get_locked(): 95 self._draw_locked_start(dc, event, rect) 96 self._draw_locked_end(dc, event, rect)
97
98 - def _draw_contents_indicator(self, dc, event, rect):
99 if event.has_balloon_data(): 100 self._draw_balloon_indicator(dc, event, rect)
101
102 - def _draw_selection_handles(self, dc, event, rect, selected):
103 if not event.locked and selected: 104 self._draw_handles(dc, event, rect)
105
106 - def _get_thin_border_pen(self, event):
107 return self._get_border_pen(event)
108
109 - def _get_thick_border_pen(self, event):
110 return self._get_border_pen(event, thickness=8)
111
112 - def _get_border_pen(self, event, thickness=1):
113 return wx.Pen(self._get_border_color(event), thickness, wx.PENSTYLE_SOLID)
114
115 - def _get_balloon_indicator_brush(self, event):
116 base_color = self._get_event_color(event) 117 darker_color = darken_color(base_color, 0.6) 118 brush = wx.Brush(darker_color, wx.BRUSHSTYLE_SOLID) 119 return brush
120
121 - def _get_border_color(self, event):
122 return darken_color(self._get_event_color(event))
123
124 - def _get_event_color(self, event):
125 try: 126 return event.get_category().color 127 except: 128 return event.get_default_color()
129
130 - def _draw_fuzzy_start(self, dc, rect, event):
131 self._inflate_clipping_region(dc, rect) 132 dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x - 4, rect.y + 4, True)
133
134 - def _draw_fuzzy_end(self, dc, rect, event):
135 self._inflate_clipping_region(dc, rect) 136 dc.DrawBitmap(self._get_fuzzy_bitmap(), rect.x + rect.width - 8, rect.y + 4, True)
137
138 - def draw_fuzzy(self, dc, event, p1, p2, p3, p4, p5):
139 self._erase_outzide_fuzzy_box(dc, p1, p2, p3) 140 self._erase_outzide_fuzzy_box(dc, p3, p4, p5) 141 self._draw_fuzzy_border(dc, event, p2, p3, p5)
142
143 - def _erase_outzide_fuzzy_box(self, dc, p1, p2, p3):
144 dc.SetBrush(wx.WHITE_BRUSH) 145 dc.SetPen(wx.WHITE_PEN) 146 dc.DrawPolygon((p1, p2, p3))
147
148 - def _draw_fuzzy_border(self, dc, event, p1, p2, p3):
149 gc = wx.GraphicsContext.Create(dc) 150 path = gc.CreatePath() 151 path.MoveToPoint(p1.x, p1.y) 152 path.AddLineToPoint(p2.x, p2.y) 153 path.AddLineToPoint(p3.x, p3.y) 154 gc.SetPen(self._get_thin_border_pen(event)) 155 gc.StrokePath(path)
156
157 - def _draw_locked_start(self, dc, event, rect):
158 self._inflate_clipping_region(dc, rect) 159 dc.DrawBitmap(self._get_lock_bitmap(), rect.x - 7, rect.y + 3, True)
160
161 - def _draw_locked_end(self, dc, event, rect):
162 self._inflate_clipping_region(dc, rect) 163 dc.DrawBitmap(self._get_lock_bitmap(), rect.x + rect.width - 8, rect.y + 3, True)
164
165 - def _draw_locked(self, dc, event, rect, x, start_angle, end_angle):
166 y = rect.y + rect.height / 2 167 r = rect.height / 2.5 168 dc.SetBrush(wx.WHITE_BRUSH) 169 dc.SetPen(wx.WHITE_PEN) 170 dc.DrawCircle(x, y, r) 171 dc.SetPen(self._get_thin_border_pen(event)) 172 self.draw_segment(dc, event, x, y, r, start_angle, end_angle)
173
174 - def draw_segment(self, dc, event, x0, y0, r, start_angle, end_angle):
175 gc = wx.GraphicsContext.Create(dc) 176 path = gc.CreatePath() 177 segment_length = 2.0 * (end_angle - start_angle) * r 178 delta = (end_angle - start_angle) / segment_length 179 angle = start_angle 180 x1 = r * math.cos(angle) + x0 181 y1 = r * math.sin(angle) + y0 182 path.MoveToPoint(x1, y1) 183 while angle < end_angle: 184 angle += delta 185 if angle > end_angle: 186 angle = end_angle 187 x2 = r * math.cos(angle) + x0 188 y2 = r * math.sin(angle) + y0 189 path.AddLineToPoint(x2, y2) 190 x1 = x2 191 y1 = y2 192 gc.SetPen(self._get_thin_border_pen(event)) 193 gc.StrokePath(path)
194
195 - def _draw_progress_box(self, dc, rect, event):
196 if event.get_data("progress"): 197 self._set_progress_color(dc, event) 198 progress_rect = self._get_progress_rect(rect, event) 199 dc.DrawRectangle(progress_rect)
200
201 - def _set_progress_color(self, dc, event):
202 progress_color = event.get_progress_color() 203 dc.SetBrush(wx.Brush(wx.Colour(progress_color[0], progress_color[1], progress_color[2])))
204
205 - def _get_progress_rect(self, event_rect, event):
206 HEIGHT_FACTOR = 0.35 207 h = event_rect.height * HEIGHT_FACTOR 208 y = event_rect.y + (event_rect.height - h) 209 rw, _ = self.scene._calc_width_and_height_for_period_event(event) 210 rx = self.scene._calc_x_pos_for_period_event(event) 211 w = rw * event.get_data("progress") / 100.0 212 return wx.Rect(rx, y, w, h)
213
214 - def _draw_balloon_indicator(self, dc, event, rect):
215 """ 216 The data contents indicator is a small triangle drawn in the upper 217 right corner of the event rectangle. 218 """ 219 corner_x = rect.X + rect.Width 220 points = ( 221 wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y), 222 wx.Point(corner_x, rect.Y), 223 wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE), 224 ) 225 dc.SetBrush(self._get_balloon_indicator_brush(event)) 226 dc.SetPen(wx.TRANSPARENT_PEN) 227 dc.DrawPolygon(points)
228
229 - def _draw_text(self, dc, rect, event):
230 # Ensure that we can't draw content outside inner rectangle 231 if self._there_is_room_for_the_text(rect): 232 self._draw_the_text(dc, rect, event)
233
234 - def _there_is_room_for_the_text(self, rect):
235 return self._get_inner_rect(rect).Width > 0
236
237 - def _draw_the_text(self, dc, rect, event):
238 self._set_text_foreground_color(dc, event) 239 if event.is_container() and EXTENDED_CONTAINER_HEIGHT.enabled(): 240 EXTENDED_CONTAINER_HEIGHT.draw_container_text_top_adjusted(event.get_text(), dc, rect) 241 else: 242 self._draw_normal_text(dc, rect, event) 243 dc.DestroyClippingRegion()
244
245 - def _draw_normal_text(self, dc, rect, event):
246 self._set_clipping_rect(dc, rect) 247 dc.DrawText(self._get_text(event), self._calc_x_pos(dc, rect, event), self._calc_y_pos(rect))
248
249 - def _get_text(self, event):
250 if event.get_progress() == 100 and self.view_properties.get_display_checkmark_on_events_done(): 251 return u"\u2714" + event.get_text() 252 else: 253 return event.get_text()
254
255 - def _get_inner_rect(self, rect):
256 # Under windows with wx 3.0 the following lined doesn't work !!! 257 # return wx.Rect(*rect).Deflate(INNER_PADDING, INNER_PADDING) 258 # The x-coordinate is corrupted 259 r = wx.Rect(*rect) 260 r.Deflate(INNER_PADDING, INNER_PADDING) 261 return r
262
263 - def _set_clipping_rect(self, dc, rect):
264 dc.SetClippingRegion(self._get_inner_rect(rect))
265
266 - def _calc_x_pos(self, dc, rect, event):
267 inner_rect = self._get_inner_rect(rect) 268 text_x = inner_rect.X 269 text_x = self._adjust_x_for_edge_icons(event, rect, text_x) 270 text_x = self._adjust_x_for_centered_text(dc, event, inner_rect, text_x) 271 return text_x
272
273 - def _adjust_x_for_edge_icons(self, event, rect, text_x):
274 if self._event_has_edge_icons(event): 275 text_x += rect.Height / 2 276 return text_x
277
278 - def _adjust_x_for_centered_text(self, dc, event, inner_rect, text_x):
279 if self.center_text: 280 text_x = self._center_text(dc, event, inner_rect, text_x) 281 return text_x
282
283 - def _event_has_edge_icons(self, event):
284 return event.get_fuzzy() or event.get_locked()
285
286 - def _calc_y_pos(self, rect):
287 return self._get_inner_rect(rect).Y
288
289 - def _center_text(self, dc, event, inner_rect, text_x):
290 width, _ = dc.GetTextExtent(self._get_text(event)) 291 return max(text_x, text_x + (inner_rect.width - width) / 2)
292
293 - def _set_text_foreground_color(self, dc, event):
298
299 - def _draw_handles(self, dc, event, rect):
300 301 def draw_frame_around_event(): 302 small_rect = wx.Rect(*rect) 303 small_rect.Deflate(1, 1) 304 border_color = self._get_border_color(event) 305 border_color = darken_color(border_color) 306 pen = wx.Pen(border_color, 1, wx.PENSTYLE_SOLID) 307 dc.SetBrush(wx.TRANSPARENT_BRUSH) 308 dc.SetPen(pen) 309 dc.DrawRectangle(small_rect)
310 311 dc.SetClippingRegion(rect) 312 draw_frame_around_event() 313 self._draw_all_handles(dc, rect, event) 314 dc.DestroyClippingRegion()
315
316 - def _draw_all_handles(self, dc, rect, event):
317 318 def inflate_clipping_region(): 319 big_rect = wx.Rect(*rect) 320 big_rect.Inflate(HANDLE_SIZE, HANDLE_SIZE) 321 dc.DestroyClippingRegion() 322 dc.SetClippingRegion(big_rect)
323 324 def set_pen_and_brush(): 325 dc.SetBrush(wx.BLACK_BRUSH) 326 dc.SetPen(wx.BLACK_PEN) 327 328 def create_handle_rect(): 329 HALF_EVENT_HEIGHT = rect.Height / 2 330 y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE 331 x = rect.X - HALF_HANDLE_SIZE + 1 332 return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE) 333 334 def draw_rect(handle_rect, offset): 335 handle_rect.Offset(offset, 0) 336 dc.DrawRectangle(handle_rect) 337 338 def draw_handle_rects(handle_rect): 339 HALF_EVENT_WIDTH = rect.Width / 2 340 EVENT_WIDTH = rect.Width 341 draw_rect(handle_rect, 0) 342 draw_rect(handle_rect, EVENT_WIDTH - 2) 343 if not event.ends_today: 344 draw_rect(handle_rect, -HALF_EVENT_WIDTH) 345 346 inflate_clipping_region() 347 set_pen_and_brush() 348 handle_rect = create_handle_rect() 349 draw_handle_rects(handle_rect) 350 354
355 - def _inflate_clipping_region(self, dc, rect):
356 copy = wx.Rect(*rect) 357 copy.Inflate(10, 0) 358 dc.DestroyClippingRegion() 359 dc.SetClippingRegion(copy)
360 363
364 - def _get_lock_bitmap(self):
365 return self._get_bitmap(self.view_properties.get_locked_icon())
366
367 - def _get_fuzzy_bitmap(self):
368 return self._get_bitmap(self.view_properties.get_fuzzy_icon())
369
370 - def _get_bitmap(self, name):
371 return wx.Bitmap(os.path.join(EVENT_ICONS_DIR, name))
372
373 - def _draw_milestone_event(self, dc, rect, scene, event, selected):
374 375 def create_handle_rect(): 376 HALF_EVENT_HEIGHT = rect.Height / 2 377 y = rect.Y + HALF_EVENT_HEIGHT - HALF_HANDLE_SIZE 378 x = rect.X - HALF_HANDLE_SIZE + 1 379 return wx.Rect(x, y, HANDLE_SIZE, HANDLE_SIZE)
380 381 def draw_shape(): 382 # draw_diamond_shape() 383 draw_rectangle_shape() 384 # draw_circle_shape() 385 386 def draw_rectangle_shape(): 387 dc.DestroyClippingRegion() 388 dc.SetPen(self._black_solid_pen(1)) 389 if event.get_category() is None: 390 dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BRUSHSTYLE_SOLID)) 391 else: 392 dc.SetBrush(wx.Brush(wx.Colour(*event.get_category().get_color()), wx.BRUSHSTYLE_SOLID)) 393 dc.DrawRectangle(rect) 394 395 def draw_circle_shape(): 396 half_size = rect.width / 2 397 dc.DestroyClippingRegion() 398 dc.SetPen(self._black_solid_pen(1)) 399 dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BRUSHSTYLE_SOLID)) 400 dc.DrawCircle(rect.x + half_size, rect.y + half_size, 2 * rect.width / 3) 401 402 def draw_diamond_shape(): 403 SIZE = 2 404 x = rect.x 405 y = rect.y 406 half_size = rect.width / 2 407 points = (wx.Point(x - SIZE, y + half_size), 408 wx.Point(x + half_size, y - SIZE), 409 wx.Point(x + rect.width + SIZE, y + half_size), 410 wx.Point(x + half_size, y + rect.width + SIZE)) 411 dc.DestroyClippingRegion() 412 dc.SetPen(self._black_solid_pen(1)) 413 dc.SetBrush(wx.Brush(wx.Colour(*event.get_default_color()), wx.BUSHSTYLE_SOLID)) 414 dc.DrawPolygon(points) 415 416 def draw_label(): 417 x_offset = 6 418 y_offset = 2 419 try: 420 label = event.get_text()[0] 421 except IndexError: 422 label = " " 423 dc.DrawText(label, rect.x + x_offset, rect.y + y_offset) 424 425 def draw_move_handle(): 426 dc.SetBrush(self._black_solid_brush()) 427 handle_rect = create_handle_rect() 428 handle_rect.Offset(rect.Width / 2, 0) 429 dc.DrawRectangle(handle_rect) 430 431 draw_shape() 432 draw_label() 433 if selected: 434 draw_move_handle() 435
436 - def _black_solid_pen(self, size):
437 return wx.Pen(wx.Colour(0, 0, 0), size, wx.PENSTYLE_SOLID)
438
439 - def _black_solid_brush(self):
440 return wx.Brush(wx.Colour(0, 0, 0), wx.BRUSHSTYLE_SOLID)
441