1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 import wx
20
21 from timelinelib.canvas.events import create_divider_position_changed_event
22 from timelinelib.canvas.timelinecanvascontroller import TimelineCanvasController
23 from timelinelib.wxgui.keyboard import Keyboard
24 from timelinelib.wxgui.cursor import Cursor
25 from timelinelib.canvas.data import TimePeriod
26
27
28 MOVE_HANDLE = 0
29 LEFT_RESIZE_HANDLE = 1
30 RIGHT_RESIZE_HANDLE = 2
31
32 HIT_REGION_PX_WITH = 5
33 HSCROLL_STEP = 25
34
35
37
38 """
39 This is the surface on which a timeline is drawn. It is also the object that handles user
40 input events such as mouse and keyboard actions.
41 """
42
43 HORIZONTAL = 8
44 VERTICAL = 16
45 BOTH = 32
46
47 START = 0
48 DRAG = 1
49 STOP = 2
50
52 wx.Panel.__init__(self, parent, style=wx.NO_BORDER | wx.WANTS_CHARS)
53 self.controller = TimelineCanvasController(self)
54 self.surface_bitmap = None
55 self._create_gui()
56 self.SetDividerPosition(50)
57 self._highlight_timer = wx.Timer(self)
58 self.Bind(wx.EVT_TIMER, self._on_highlight_timer, self._highlight_timer)
59 self._last_balloon_event = None
60 self._waiting = False
61
64
67
69 return self._divider_position
70
75
78
81
84
87
91
94
97
100
103
106
107 - def Snap(self, time):
109
110 - def PostEvent(self, event):
111 wx.PostEvent(self, event)
112
116
119
122
125
128
131
134
136 selected_events = self.GetSelectedEvents()
137 if len(selected_events) == 1:
138 return selected_events[0]
139 return None
140
143
146
149
152
155
159
160 - def GetEventAt(self, cursor, prefer_container=False):
162
165
180
184
187
190
193
196
199
202
204 wx.ImageFromBitmap(self.surface_bitmap).SaveFile(path, wx.BITMAP_TYPE_PNG)
205
210
214
217
220
223
226
228 width, height = self.GetSizeTuple()
229 self.surface_bitmap = wx.EmptyBitmap(width, height)
230 memdc = wx.MemoryDC()
231 memdc.SelectObject(self.surface_bitmap)
232 memdc.BeginDrawing()
233 memdc.SetBackground(wx.Brush(wx.WHITE, wx.PENSTYLE_SOLID))
234 memdc.Clear()
235 fn_draw(memdc)
236 memdc.EndDrawing()
237 del memdc
238 self.Refresh()
239 self.Update()
240
242 self.SetCursor(wx.StockCursor(wx.CURSOR_IBEAM))
243
245 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZEWE))
246
248 self.SetCursor(wx.StockCursor(wx.CURSOR_SIZING))
249
251 self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW))
252
254 self.Zoom(1, self._get_half_width())
255
257 self.Zoom(-1, self._get_half_width())
258
259 - def Zoom(self, direction, x):
260 """ zoom time line at position x """
261 width, _ = self.GetSizeTuple()
262 x_percent_of_width = float(x) / width
263 self.Navigate(lambda tp: tp.zoom(direction, x_percent_of_width))
264
267
270
276
283
284
285
287 self._start_time = self.GetTimeAt(evt.GetX())
288
290 rotation = evt.GetWheelRotation()
291 return 1 if rotation > 0 else -1 if rotation < 0 else 0
292
294 self.Zoom(self._direction(evt), evt.GetX())
295
301
304
307
310
312
313 def cursor_has_left_event():
314
315
316 if wx.GetMouseState().LeftIsDown():
317 return False
318 else:
319 return self.GetEventAtCursor() != self._last_balloon_event
320
321 def no_balloon_at_cursor():
322 return not self.GetBalloonAtCursor()
323
324 def update_last_seen_event():
325 if self._last_balloon_event is None:
326 self._last_balloon_event = self.GetEventAtCursor()
327 elif cursor_has_left_event() and no_balloon_at_cursor():
328 self._last_balloon_event = None
329 return self._last_balloon_event
330
331 def delayed_call():
332 self.SetHoveredEvent(self._last_balloon_event)
333 self._waiting = False
334
335
336
337 if not self._waiting:
338 update_last_seen_event()
339 self._wating = True
340 wx.CallLater(500, delayed_call)
341
342 - def GetTimelineInfoText(self, evt):
343
344 def format_current_pos_time_string(x):
345 tm = self.GetTimeAt(x)
346 return self.GetTimeType().format_period(TimePeriod(tm, tm))
347
348 event = self.GetEventAtCursor()
349 if event:
350 return event.get_label(self.GetTimeType())
351 else:
352 return format_current_pos_time_string(evt.GetX())
353
355
356 def get_cursor():
357 return Cursor(evt.GetX(), evt.GetY())
358
359 def get_keyboard():
360 return Keyboard(evt.ControlDown(), evt.ShiftDown(), evt.AltDown())
361
362 def hit_resize_handle():
363 try:
364 event, hit_info = self.GetEventWithHitInfoAt(get_cursor(), get_keyboard())
365 if event.get_locked():
366 return None
367 if event.is_milestone():
368 return None
369 if not self.IsEventSelected(event):
370 return None
371 if hit_info == LEFT_RESIZE_HANDLE:
372 return wx.LEFT
373 if hit_info == RIGHT_RESIZE_HANDLE:
374 return wx.RIGHT
375 return None
376 except:
377 return None
378
379 def hit_move_handle():
380 event_and_hit_info = self.GetEventWithHitInfoAt(get_cursor(), get_keyboard())
381 if event_and_hit_info is None:
382 return False
383 (event, hit_info) = event_and_hit_info
384 if event.get_locked():
385 return False
386 if not self.IsEventSelected(event):
387 return False
388 return hit_info == MOVE_HANDLE
389
390 def over_resize_handle():
391 return hit_resize_handle() is not None
392
393 def over_move_handle():
394 return hit_move_handle() and not self.GetEventAtCursor(False).get_ends_today()
395
396 if over_resize_handle():
397 self.set_size_cursor()
398 elif over_move_handle():
399 self.set_move_cursor()
400 else:
401 self.set_default_cursor()
402
406
408
409 def get_cursor():
410 return Cursor(evt.GetX(), evt.GetY())
411
412 event = self.GetEventAt(get_cursor(), evt.AltDown())
413 if event:
414 self.SetEventSelected(event, not self.IsEventSelected(event))
415
419
425
435
438
440 self._selecting = False
441
443 self._selecting = True
444 self._cursor = self.GetCursor(evt)
445
451
453 return Cursor(evt.GetX(), evt.GetY())
454
460
462 self._zooming = False
463
465 self._zooming = True
466 self._start_time = self.GetTimeAt(evt.GetX())
467 self._end_time = self.GetTimeAt(evt.GetX())
468
473
478
480 self._period_select = False
481
483 self._period_select = True
484 self._start_time = self.GetTimeAt(evt.GetX())
485 self._end_time = self.GetTimeAt(evt.GetX())
486
491
493 self._period_select = False
494 self.SetPeriodSelection(None)
495 return self._start_time, self._end_time
496
497 - def InitDrag(self, scroll=None, zoom=None, period_select=None, event_select=None):
515
516 def init_zoom():
517 if zoom not in self._methods:
518 self.InitZoomSelect()
519 self._methods[zoom] = (self.StartZoomSelect,
520 self.DragZoom,
521 self.StopDragZoom)
522
523 def init_period_select():
524 if not period_select in self._methods:
525 self.InitDragPeriodSelect()
526 self._methods[period_select] = (self.StartDragPeriodSelect,
527 self.DragPeriodSelect,
528 self.StopDragPeriodSelect)
529
530 def init_event_select():
531 if not event_select in self._methods:
532 self.InitDragEventSelect()
533 self._methods[event_select] = (self.StartDragEventSelect,
534 self.DragEventSelect,
535 self.StopDragEventSelect)
536
537 self._drag_scroll = scroll
538 self._drag_zoom = zoom
539 self._drag_period_select = period_select
540 self._drag_event_select = event_select
541 self._methods = {}
542
543 if scroll:
544 init_scroll()
545 if zoom:
546 init_zoom()
547 if period_select:
548 init_period_select()
549 if event_select:
550 init_event_select()
551
552
554
555 def calc_cotrol_keys_value(evt):
556 combo = 0
557 if evt.ControlDown():
558 combo += Keyboard.CTRL
559 if evt.ShiftDown():
560 combo += Keyboard.SHIFT
561 if evt.AltDown():
562 combo += Keyboard.ALT
563 return combo
564
565 combo = calc_cotrol_keys_value(evt)
566 if combo in self._methods:
567 if index == self.STOP:
568 self._methods[combo][index]()
569 else:
570 self._methods[combo][index](evt)
571
572
573
576
579
582
584 self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)
585 self.Bind(wx.EVT_PAINT, self._on_paint)
586 self.Bind(wx.EVT_SIZE, self._on_size)
587
591
593 dc = wx.AutoBufferedPaintDC(self)
594 dc.BeginDrawing()
595 if self.surface_bitmap:
596 dc.DrawBitmap(self.surface_bitmap, 0, 0, True)
597 else:
598 pass
599 dc.EndDrawing()
600
603
608
614