1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import math
20 import os.path
21
22 import wx
23
24 from timelinelib.config.paths import ICONS_DIR
25 from timelinelib.db.objects.category import sort_categories
26 from timelinelib.drawing.interface import Drawer
27 from timelinelib.drawing.scene import TimelineScene
28 from timelinelib.drawing.utils import darken_color
29 from timelinelib.drawing.utils import get_default_font
30
31
32 OUTER_PADDING = 5
33 INNER_PADDING = 3
34 PERIOD_THRESHOLD = 20
35 BALLOON_RADIUS = 12
36 DATA_INDICATOR_SIZE = 10
37 CONTRAST_RATIO_THREASHOLD = 2250
38 WHITE = (255, 255, 255)
39 BLACK = (0, 0, 0)
40
41
43
45 self._create_fonts()
46 self._create_pens()
47 self._create_brushes()
48 self.fast_draw = False
49
54
56 self.red_solid_pen = wx.Pen(wx.Color(255,0, 0), 1, wx.SOLID)
57 self.black_solid_pen = wx.Pen(wx.Color(0, 0, 0), 1, wx.SOLID)
58 self.darkred_solid_pen = wx.Pen(wx.Color(200, 0, 0), 1, wx.SOLID)
59 self.black_dashed_pen = wx.Pen(wx.Color(200, 200, 200), 1, wx.USER_DASH)
60 self.black_dashed_pen.SetDashes([2, 2])
61 self.black_dashed_pen.SetCap(wx.CAP_BUTT)
62 self.grey_solid_pen = wx.Pen(wx.Color(200, 200, 200), 1, wx.SOLID)
63 self.red_solid_pen = wx.Pen(wx.Color(255, 0, 0), 1, wx.SOLID)
64
66 self.white_solid_brush = wx.Brush(wx.Color(255, 255, 255), wx.SOLID)
67 self.black_solid_brush = wx.Brush(wx.Color(0, 0, 0), wx.SOLID)
68 self.red_solid_brush = wx.Brush(wx.Color(255, 0, 0), wx.SOLID)
69 self.lightgrey_solid_brush = wx.Brush(wx.Color(230, 230, 230), wx.SOLID)
70
74
75 - def _get_text_extent(self, text):
76 self.dc.SetFont(self.small_text_font)
77 tw, th = self.dc.GetTextExtent(text)
78 return (tw, th)
79
82
83 - def draw(self, dc, timeline, view_properties, config):
84 self.config = config
85 self.dc = dc
86 self.time_type = timeline.get_time_type()
87 self.scene = self._create_scene(
88 dc.GetSizeTuple(), timeline, view_properties, self._get_text_extent)
89 self._perform_drawing(view_properties)
90 del self.dc
91
92 - def _create_scene(self, size, db, view_properties, get_text_extent_fn):
100
106
110
117
118 - def snap(self, time, snap_region=10):
119 if self._distance_to_left_border(time) < snap_region:
120 return self._get_time_at_left_border(time)
121 elif self._distance_to_right_border(time) < snap_region:
122 return self._get_time_at_right_border(time)
123 else:
124 return time
125
129
133
135 left_strip_time, right_strip_time = self._snap_region(time)
136 return left_strip_time
137
139 left_strip_time, right_strip_time = self._snap_region(time)
140 return right_strip_time
141
143 left_strip_time = self.scene.minor_strip.start(time)
144 right_strip_time = self.scene.minor_strip.increment(left_strip_time)
145 return (left_strip_time, right_strip_time)
146
150
151 - def event_at(self, x, y, alt_down=False):
152 container_event = None
153 for (event, rect) in self.scene.event_data:
154 if rect.Contains(wx.Point(x, y)):
155 if event.is_container():
156 if alt_down:
157 return event
158 container_event = event
159 else:
160 return event
161 return container_event
162
164 container_event = None
165 container_rect = None
166 for (event, rect) in self.scene.event_data:
167 if rect.Contains(wx.Point(x, y)):
168 if event.is_container():
169 if alt_down:
170 return event, rect
171 container_event = event
172 container_rect = rect
173 else:
174 return event, rect
175 if container_event == None:
176 return None
177 return container_event, container_rect
178
180 for (event, rect) in self.scene.event_data:
181 if evt == event:
182 return rect
183 return None
184
186 event = None
187 for (event_in_list, rect) in self.balloon_data:
188 if rect.Contains(wx.Point(x, y)):
189 event = event_in_list
190 return event
191
194
197
199 if not view_properties.period_selection:
200 return
201 start, end = view_properties.period_selection
202 start_x = self.scene.x_pos_for_time(start)
203 end_x = self.scene.x_pos_for_time(end)
204 self.dc.SetBrush(self.lightgrey_solid_brush)
205 self.dc.SetPen(wx.TRANSPARENT_PEN)
206 self.dc.DrawRectangle(start_x, 0,
207 end_x - start_x + 1, self.scene.height)
208
210 if self.fast_draw:
211 self._draw_fast_bg()
212 else:
213 self._draw_normal_bg(view_properties)
214
216 self._draw_minor_strips()
217 self._draw_divider_line()
218
220 self._draw_minor_strips()
221 self._draw_major_strips()
222 self._draw_divider_line()
223 self._draw_now_line()
224
226 for strip_period in self.scene.minor_strip_data:
227 self._draw_minor_strip_divider_line_at(strip_period.end_time)
228 self._draw_minor_strip_label(strip_period)
229
234
242
244 self.dc.SetFont(self.header_font)
245 self.dc.SetPen(self.grey_solid_pen)
246 for time_period in self.scene.major_strip_data:
247 self._draw_major_strip_end_line(time_period)
248 self._draw_major_strip_label(time_period)
249
254
259
274
276 self.dc.SetPen(self.black_solid_pen)
277 self.dc.DrawLine(0, self.scene.divider_y, self.scene.width,
278 self.scene.divider_y)
279
281 for (event, rect) in self.scene.event_data:
282 if self._invisible_container_subevent(event, rect):
283 continue
284 if self._event_displayed_as_point_event(rect):
285 self._draw_line(view_properties, event, rect)
286
290
292 return self.scene.divider_y > rect.Y
293
294 - def _draw_line(self, view_properties, event, rect):
295 x = self.scene.x_pos_for_time(event.mean_time())
296 y = rect.Y + rect.Height
297 y2 = self._get_end_of_line(event)
298 self._set_line_color(view_properties, event)
299 self.dc.DrawLine(x, y, x, y2)
300 self.dc.DrawCircle(x, y2, 2)
301
303 if self._point_subevent(event):
304 y = self._get_container_y(event.container_id)
305 else:
306 y = self.scene.divider_y
307 return y
308
311
318
320 if view_properties.is_selected(event):
321 self.dc.SetPen(self.red_solid_pen)
322 self.dc.SetBrush(self.red_solid_brush)
323 else:
324 self.dc.SetBrush(self.black_solid_brush)
325 self.dc.SetPen(self.black_solid_pen)
326
328 now_time = self.time_type.now()
329 x = self.scene.x_pos_for_time(now_time)
330 if x > 0 and x < self.scene.width:
331 self.dc.SetPen(self.darkred_solid_pen)
332 self.dc.DrawLine(x, 0, x, self.scene.height)
333
335 categories = []
336 for (event, rect) in self.scene.event_data:
337 cat = event.category
338 if cat and not cat in categories:
339 categories.append(cat)
340 return sort_categories(categories)
341
343 if self._legend_should_be_drawn(view_properties, categories):
344 self.dc.SetFont(self.small_text_font)
345 rect = self._calculate_legend_rect(categories)
346 self._draw_legend_box(rect)
347 self._draw_legend_items(rect, categories)
348
350 return view_properties.show_legend and len(categories) > 0
351
353 max_width = 0
354 height = INNER_PADDING
355 for cat in categories:
356 tw, th = self.dc.GetTextExtent(cat.name)
357 height = height + th + INNER_PADDING
358 if tw > max_width:
359 max_width = tw
360 item_height = self._text_height_with_current_font()
361 width = max_width + 4 * INNER_PADDING + item_height
362 return wx.Rect(OUTER_PADDING,
363 self.scene.height - height - OUTER_PADDING,
364 width,
365 height)
366
368 self.dc.SetBrush(self.white_solid_brush)
369 self.dc.SetPen(self.black_solid_pen)
370 self.dc.DrawRectangleRect(rect)
371
373 STRING_WITH_MIXED_CAPITALIZATION = "jJ"
374 tw, th = self.dc.GetTextExtent(STRING_WITH_MIXED_CAPITALIZATION)
375 return th
376
378 item_height = self._text_height_with_current_font()
379 cur_y = rect.Y + INNER_PADDING
380 for cat in categories:
381 base_color = cat.color
382 border_color = darken_color(base_color)
383 self.dc.SetBrush(wx.Brush(base_color, wx.SOLID))
384 self.dc.SetPen(wx.Pen(border_color, 1, wx.SOLID))
385 color_box_rect = (OUTER_PADDING + rect.Width - item_height -
386 INNER_PADDING,
387 cur_y, item_height, item_height)
388 self.dc.DrawRectangleRect(color_box_rect)
389 self.dc.SetTextForeground((0, 0, 0))
390 self.dc.DrawText(cat.name, OUTER_PADDING + INNER_PADDING, cur_y)
391 cur_y = cur_y + item_height + INNER_PADDING
392
394 """Draw all event boxes and the text inside them."""
395 self.dc.SetFont(self.small_text_font)
396 self.dc.DestroyClippingRegion()
397 self._draw_lines_to_non_period_events(view_properties)
398 for (event, rect) in self.scene.event_data:
399 if event.is_container():
400 self._draw_container(event, rect, view_properties)
401 else:
402 self._draw_event(event, rect, view_properties)
403
405 box_rect = wx.Rect(rect.X - 2, rect.Y - 2, rect.Width + 4, rect.Height + 4)
406 self._draw_box(box_rect, event)
407 if self._event_displayed_as_point_event(rect):
408 self._draw_text(rect, event)
409 if view_properties.is_selected(event):
410 self._draw_selection_and_handles(rect, event)
411
422
424 return (event.is_subevent() and
425 self._event_displayed_as_point_event(rect))
426
428 self.dc.SetClippingRect(rect)
429 self.dc.SetBrush(self._get_box_brush(event))
430 self.dc.SetPen(self._get_box_pen(event))
431 self.dc.DrawRectangleRect(rect)
432 if event.fuzzy:
433 self._draw_fuzzy_edges(rect, event)
434 if event.locked:
435 self._draw_locked_edges(rect, event)
436 self.dc.DestroyClippingRegion()
437
439 self._draw_fuzzy_start(rect, event)
440 self._draw_fuzzy_end(rect, event)
441
443 """
444 p1 /p2 ----------
445 /
446 p3 <
447 \
448 p4 \p5 ----------
449 """
450 x1 = rect.x
451 x2 = rect.x + rect.height / 2
452 y1 = rect.y
453 y2 = rect.y + rect.height / 2
454 y3 = rect.y + rect.height
455 p1 = wx.Point(x1, y1)
456 p2 = wx.Point(x2, y1)
457 p3 = wx.Point(x1, y2)
458 p4 = wx.Point(x1, y3)
459 p5 = wx.Point(x2, y3)
460 self.draw_fuzzy(event, p1, p2, p3, p4, p5)
461
463 """
464 ---- P2\ p1
465 \
466 > p3
467 /
468 ---- p4/ p4
469 """
470 x1 = rect.x + rect.width - rect.height / 2
471 x2 = rect.x + rect.width
472 y1 = rect.y
473 y2 = rect.y + rect.height / 2
474 y3 = rect.y + rect.height
475 p1 = wx.Point(x2, y1)
476 p2 = wx.Point(x1, y1)
477 p3 = wx.Point(x2, y2)
478 p4 = wx.Point(x2, y3)
479 p5 = wx.Point(x1, y3)
480 self.draw_fuzzy(event, p1, p2, p3, p4, p5)
481
483 self._draw_fuzzy_polygon(p1, p2 ,p3)
484 self._draw_fuzzy_polygon(p3, p4 ,p5)
485 self._draw_fuzzy_border(event, p2, p3, p5)
486
488 self.dc.SetBrush(wx.WHITE_BRUSH)
489 self.dc.SetPen(wx.WHITE_PEN)
490 self.dc.DrawPolygon((p1, p2, p3))
491
493 gc = wx.GraphicsContext.Create(self.dc)
494 path = gc.CreatePath()
495 path.MoveToPoint(p1.x, p1.y)
496 path.AddLineToPoint(p2.x, p2.y)
497 path.AddLineToPoint(p3.x, p3.y)
498 gc.SetPen(self._get_box_pen(event))
499 gc.StrokePath(path)
500
502 self._draw_locked_start(event, rect)
503 self._draw_locked_end(event, rect)
504
506 x = rect.x
507 if event.fuzzy:
508 start_angle = -math.pi / 4
509 end_angle = math.pi / 4
510 else:
511 start_angle = -math.pi
512 end_angle = math.pi
513 self._draw_locked(event, rect, x, start_angle, end_angle)
514
516 x = rect.x + rect.width
517 if event.fuzzy:
518 start_angle = 3 * math.pi / 4
519 end_angle = 5 * math.pi / 4
520 else:
521 start_angle = math.pi / 2
522 end_angle = 3 * math.pi / 2
523 self._draw_locked(event, rect, x, start_angle, end_angle)
524
525 - def _draw_locked(self, event, rect, x, start_angle, end_angle):
526 y = rect.y + rect.height / 2
527 r = rect.height / 2.5
528 self.dc.SetBrush(wx.WHITE_BRUSH)
529 self.dc.SetPen(wx.WHITE_PEN)
530 self.dc.DrawCircle(x, y, r)
531 self.dc.SetPen(self._get_box_pen(event))
532 self.draw_segment(event, x, y, r, start_angle, end_angle)
533
534 - def draw_segment(self, event, x0, y0, r, start_angle, end_angle):
535 gc = wx.GraphicsContext.Create(self.dc)
536 path = gc.CreatePath()
537 segment_length = 2.0 * (end_angle - start_angle) * r
538 delta = (end_angle - start_angle) / segment_length
539 angle = start_angle
540 x1 = r * math.cos(angle) + x0
541 y1 = r * math.sin(angle) + y0
542 path.MoveToPoint(x1, y1)
543 while angle < end_angle:
544 angle += delta
545 if angle > end_angle:
546 angle = end_angle
547 x2 = r * math.cos(angle) + x0
548 y2 = r * math.sin(angle) + y0
549 path.AddLineToPoint(x2, y2)
550 x1 = x2
551 y1 = y2
552 gc.SetPen(self._get_box_pen(event))
553 gc.StrokePath(path)
554
555 - def _draw_text(self, rect, event):
556
557 rect_copy = wx.Rect(*rect)
558 rect_copy.Deflate(INNER_PADDING, INNER_PADDING)
559 if rect_copy.Width > 0:
560
561 self.dc.SetClippingRect(rect_copy)
562 text_x = rect.X + INNER_PADDING
563 if event.fuzzy or event.locked:
564 text_x += rect.Height / 2
565 text_y = rect.Y + INNER_PADDING
566 if text_x < INNER_PADDING:
567 text_x = INNER_PADDING
568 self._set_text_foreground_color(event)
569 self.dc.DrawText(event.text, text_x, text_y)
570 self.dc.DestroyClippingRegion()
571
573 if event.category is None:
574 fg_color = BLACK
575 elif event.category.font_color is None:
576 fg_color = BLACK
577 else:
578 font_color = event.category.font_color
579 fg_color = wx.Color(font_color[0], font_color[1], font_color[2])
580 self.dc.SetTextForeground(fg_color)
581
582 - def _draw_contents_indicator(self, event, rect):
583 """
584 The data contents indicator is a small triangle drawn in the upper
585 right corner of the event rectangle.
586 """
587 self.dc.SetClippingRect(rect)
588 corner_x = rect.X + rect.Width
589 if corner_x > self.scene.width:
590 corner_x = self.scene.width
591 points = (
592 wx.Point(corner_x - DATA_INDICATOR_SIZE, rect.Y),
593 wx.Point(corner_x, rect.Y),
594 wx.Point(corner_x, rect.Y + DATA_INDICATOR_SIZE),
595 )
596 self.dc.SetBrush(self._get_box_indicator_brush(event))
597 self.dc.SetPen(wx.TRANSPARENT_PEN)
598 self.dc.DrawPolygon(points)
599 self.dc.DestroyClippingRegion()
600
602 self.dc.SetClippingRect(rect)
603 small_rect = wx.Rect(*rect)
604 small_rect.Deflate(1, 1)
605 border_color = self._get_border_color(event)
606 border_color = darken_color(border_color)
607 pen = wx.Pen(border_color, 1, wx.SOLID)
608 self.dc.SetBrush(wx.TRANSPARENT_BRUSH)
609 self.dc.SetPen(pen)
610 self.dc.DrawRectangleRect(small_rect)
611 self._draw_handles(rect, event)
612 self.dc.DestroyClippingRegion()
613
615 SIZE = 4
616 big_rect = wx.Rect(rect.X - SIZE, rect.Y - SIZE, rect.Width + 2 * SIZE, rect.Height + 2 * SIZE)
617 self.dc.DestroyClippingRegion()
618 self.dc.SetClippingRect(big_rect)
619 y = rect.Y + rect.Height/2 - SIZE/2
620 x = rect.X - SIZE / 2
621 west_rect = wx.Rect(x + 1 , y, SIZE, SIZE)
622 center_rect = wx.Rect(x + rect.Width / 2, y, SIZE, SIZE)
623 east_rect = wx.Rect(x + rect.Width - 1, y, SIZE, SIZE)
624 self.dc.SetBrush(wx.Brush("BLACK", wx.SOLID))
625 self.dc.SetPen(wx.Pen("BLACK", 1, wx.SOLID))
626 if not event.locked:
627 self.dc.DrawRectangleRect(east_rect)
628 self.dc.DrawRectangleRect(west_rect)
629 if not event.locked and not event.ends_today:
630 self.dc.DrawRectangleRect(center_rect)
631
638
640 base_color = self._get_base_color(event)
641 border_color = darken_color(base_color)
642 return border_color
643
645 border_color = self._get_border_color(event)
646 pen = wx.Pen(border_color, 1, wx.SOLID)
647 return pen
648
650 base_color = self._get_base_color(event)
651 brush = wx.Brush(base_color, wx.SOLID)
652 return brush
653
655 base_color = self._get_base_color(event)
656 darker_color = darken_color(base_color, 0.6)
657 brush = wx.Brush(darker_color, wx.SOLID)
658 return brush
659
661 border_color = self._get_border_color(event)
662 brush = wx.Brush(border_color, wx.BDIAGONAL_HATCH)
663 return brush
664
666 """Draw ballons on selected events that has 'description' data."""
667 self.balloon_data = []
668 top_event = None
669 top_rect = None
670 self.dc.SetTextForeground(BLACK)
671 for (event, rect) in self.scene.event_data:
672 if (event.get_data("description") != None or
673 event.get_data("icon") != None):
674 sticky = view_properties.event_has_sticky_balloon(event)
675 if (view_properties.event_is_hovered(event) or sticky):
676 if not sticky:
677 top_event, top_rect = event, rect
678 self._draw_ballon(event, rect, sticky)
679
680 if top_event is not None:
681 self._draw_ballon(top_event, top_rect, False)
682
684 """Draw one ballon on a selected event that has 'description' data."""
685
686 MIN_TEXT_WIDTH = 200
687 MIN_WIDTH = 100
688 SLIDER_WIDTH = 20
689
690 inner_rect_w = 0
691 inner_rect_h = 0
692
693 (iw, ih) = (0, 0)
694 icon = event.get_data("icon")
695 if icon != None:
696 (iw, ih) = icon.Size
697 inner_rect_w = iw
698 inner_rect_h = ih
699 max_text_width = max(MIN_TEXT_WIDTH, (self.scene.width - SLIDER_WIDTH - event_rect.X - iw))
700
701 self.dc.SetFont(get_default_font(8))
702 font_h = self.dc.GetCharHeight()
703 (tw, th) = (0, 0)
704 description = event.get_data("description")
705 lines = None
706 if description != None:
707 lines = break_text(description, self.dc, max_text_width)
708 th = len(lines) * self.dc.GetCharHeight()
709 for line in lines:
710 (lw, lh) = self.dc.GetTextExtent(line)
711 tw = max(lw, tw)
712 if icon != None:
713 inner_rect_w += BALLOON_RADIUS
714 inner_rect_w += min(tw, max_text_width)
715 inner_rect_h = max(inner_rect_h, th)
716 inner_rect_w = max(MIN_WIDTH, inner_rect_w)
717 bounding_rect, x, y = self._draw_balloon_bg(
718 self.dc, (inner_rect_w, inner_rect_h),
719 (event_rect.X + event_rect.Width / 2,
720 event_rect.Y),
721 True, sticky)
722 if icon != None:
723 self.dc.DrawBitmap(icon, x, y, False)
724 x += iw + BALLOON_RADIUS
725 if lines != None:
726 ty = y
727 for line in lines:
728 self.dc.DrawText(line, x, ty)
729 ty += font_h
730 x += tw
731
732
733
734
735 self.balloon_data.append((event, bounding_rect))
736
738 """
739 Draw the balloon background leaving inner_size for content.
740
741 tip_pos determines where the tip of the ballon should be.
742
743 above determines if the balloon should be above the tip (True) or below
744 (False). This is not currently implemented.
745
746 W
747 |----------------|
748 ______________ _
749 / \ | R = Corner Radius
750 | | | AA = Left Arrow-leg angle
751 | W_ARROW | | H MARGIN = Text margin
752 | |--| | | * = Starting point
753 \____ ______/ _
754 / / |
755 /_/ | H_ARROW
756 * -
757 |----|
758 ARROW_OFFSET
759
760 Calculation of points starts at the tip of the arrow and continues
761 clockwise around the ballon.
762
763 Return (bounding_rect, x, y) where x and y is at top of inner region.
764 """
765
766 gc = wx.GraphicsContext.Create(self.dc)
767 path = gc.CreatePath()
768
769 R = BALLOON_RADIUS
770 W = 1 * R + inner_size[0]
771 H = 1 * R + inner_size[1]
772 H_ARROW = 14
773 W_ARROW = 15
774 W_ARROW_OFFSET = R + 25
775 AA = 20
776
777 (tipx, tipy) = tip_pos
778 p0 = wx.Point(tipx, tipy)
779 path.MoveToPoint(p0.x, p0.y)
780
781 p1 = wx.Point(p0.x + H_ARROW * math.tan(math.radians(AA)),
782 p0.y - H_ARROW)
783 path.AddLineToPoint(p1.x, p1.y)
784
785 p2 = wx.Point(p1.x - W_ARROW_OFFSET + R, p1.y)
786 path.AddLineToPoint(p2.x, p2.y)
787
788 p3 = wx.Point(p2.x, p2.y - R)
789 path.AddArc(p3.x, p3.y, R, math.radians(90), math.radians(180))
790
791 p4 = wx.Point(p3.x - R, p3.y - H + R)
792 left_x = p4.x
793 path.AddLineToPoint(p4.x, p4.y)
794
795 p5 = wx.Point(p4.x + R, p4.y)
796 path.AddArc(p5.x, p5.y, R, math.radians(180), math.radians(-90))
797
798 p6 = wx.Point(p5.x + W - R, p5.y - R)
799 top_y = p6.y
800 path.AddLineToPoint(p6.x, p6.y)
801
802 p7 = wx.Point(p6.x, p6.y + R)
803 path.AddArc(p7.x, p7.y, R, math.radians(-90), math.radians(0))
804
805 p8 = wx.Point(p7.x + R , p7.y + H - R)
806 path.AddLineToPoint(p8.x, p8.y)
807
808 p9 = wx.Point(p8.x - R, p8.y)
809 path.AddArc(p9.x, p9.y, R, math.radians(0), math.radians(90))
810
811 p10 = wx.Point(p9.x - W + W_ARROW + W_ARROW_OFFSET, p9.y + R)
812 path.AddLineToPoint(p10.x, p10.y)
813 path.CloseSubpath()
814
815
816 gc.Translate(0.5, 0.5)
817
818 BORDER_COLOR = wx.Color(127, 127, 127)
819 BG_COLOR = wx.Color(255, 255, 231)
820 PEN = wx.Pen(BORDER_COLOR, 1, wx.SOLID)
821 BRUSH = wx.Brush(BG_COLOR, wx.SOLID)
822 gc.SetPen(PEN)
823 gc.SetBrush(BRUSH)
824 gc.DrawPath(path)
825
826 if sticky:
827 pin = wx.Bitmap(os.path.join(ICONS_DIR, "stickypin.png"))
828 else:
829 pin = wx.Bitmap(os.path.join(ICONS_DIR, "unstickypin.png"))
830 self.dc.DrawBitmap(pin, p7.x -5, p6.y + 5, True)
831
832
833 bx = left_x
834 by = top_y
835 bw = W + R + 1
836 bh = H + R + H_ARROW + 1
837 bounding_rect = wx.Rect(bx, by, bw, bh)
838 return (bounding_rect, left_x + BALLOON_RADIUS, top_y + BALLOON_RADIUS)
839
840
841 -def break_text(text, dc, max_width_in_px):
842 """ Break the text into lines so that they fits within the given width."""
843 sentences = text.split("\n")
844 lines = []
845 for sentence in sentences:
846 w, h = dc.GetTextExtent(sentence)
847 if w <= max_width_in_px:
848 lines.append(sentence)
849
850 else:
851 break_sentence(dc, lines, sentence, max_width_in_px);
852 return lines
853
854
856 """Break a sentence into lines."""
857 line = []
858 max_word_len_in_ch = get_max_word_length(dc, max_width_in_px)
859 words = break_line(dc, sentence, max_word_len_in_ch)
860 for word in words:
861 w, h = dc.GetTextExtent("".join(line) + word + " ")
862
863 if w > max_width_in_px:
864 lines.append("".join(line))
865 line = []
866 line.append(word + " ")
867
868 if word.endswith('-'):
869 lines.append("".join(line))
870 line = []
871 if len(line) > 0:
872 lines.append("".join(line))
873
874
876 """Break a sentence into words."""
877 words = sentence.split(" ")
878 new_words = []
879 for word in words:
880 broken_words = break_word(dc, word, max_word_len_in_ch)
881 for broken_word in broken_words:
882 new_words.append(broken_word)
883 return new_words
884
885
887 """
888 Break words if they are too long.
889
890 If a single word is too long to fit we have to break it.
891 If not we just return the word given.
892 """
893 words = []
894 while len(word) > max_word_len_in_ch:
895 word1 = word[0:max_word_len_in_ch] + "-"
896 word = word[max_word_len_in_ch:]
897 words.append(word1)
898 words.append(word)
899 return words
900
901
903 TEMPLATE_CHAR = 'K'
904 word = [TEMPLATE_CHAR]
905 w, h = dc.GetTextExtent("".join(word))
906 while w < max_width_in_px:
907 word.append(TEMPLATE_CHAR)
908 w, h = dc.GetTextExtent("".join(word))
909 return len(word) - 1
910