Package Gnumed :: Package timelinelib :: Package wxgui :: Package components :: Module timelinepanel
[frames] | no frames]

Source Code for Module Gnumed.timelinelib.wxgui.components.timelinepanel

  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 webbrowser 
 21   
 22  import wx 
 23   
 24  from timelinelib.canvas import EVT_DIVIDER_POSITION_CHANGED 
 25  from timelinelib.canvas import EVT_TIMELINE_REDRAWN 
 26  from timelinelib.db.utils import safe_locking 
 27  from timelinelib.general.observer import Listener 
 28  from timelinelib.wxgui.components.maincanvas.createperiodeventbydrag import CreatePeriodEventByDragInputHandler 
 29  from timelinelib.wxgui.components.maincanvas.maincanvas import MainCanvas 
 30  from timelinelib.wxgui.components.maincanvas.movebydrag import MoveByDragInputHandler 
 31  from timelinelib.wxgui.components.maincanvas.noop import NoOpInputHandler 
 32  from timelinelib.wxgui.components.maincanvas.resizebydrag import ResizeByDragInputHandler 
 33  from timelinelib.wxgui.components.maincanvas.scrollbydrag import ScrollByDragInputHandler 
 34  from timelinelib.wxgui.components.maincanvas.zoombydrag import ZoomByDragInputHandler 
 35  from timelinelib.wxgui.components.maincanvas.selectevents import SelectEventsInputHandler 
 36  from timelinelib.wxgui.components.messagebar import MessageBar 
 37  from timelinelib.wxgui.components.sidebar import Sidebar 
 38  from timelinelib.wxgui.dialogs.duplicateevent.view import open_duplicate_event_dialog_for_event 
 39  from timelinelib.wxgui.dialogs.editevent.view import open_create_event_editor 
 40  from timelinelib.wxgui.dialogs.editevent.view import open_event_editor_for 
 41  from timelinelib.wxgui.dialogs.milestone.view import open_milestone_editor_for 
 42  from timelinelib.wxgui.frames.mainframe.toolbar import ToolbarCreator 
 43  from timelinelib.wxgui.utils import _ask_question 
 44  from timelinelib.config.paths import ICONS_DIR 
 45  from timelinelib.wxgui.cursor import Cursor 
 46   
 47   
 48  LEFT_RIGHT_SCROLL_FACTOR = 1 / 200.0 
 49   
 50   
51 -class TimelinePanelGuiCreator(wx.Panel):
52
53 - def __init__(self, parent):
54 self.sidebar_width = self.config.sidebar_width 55 wx.Panel.__init__(self, parent) 56 self._create_gui()
57
58 - def BitmapFromIcon(self, icon):
59 return wx.Bitmap(os.path.join(ICONS_DIR, icon))
60
61 - def CreateToolbar(self):
62 return wx.ToolBar(self, wx.ID_ANY)
63
64 - def _create_gui(self):
65 self._create_toolbar() 66 self._create_warning_bar() 67 self._create_divider_line_slider() 68 self._create_splitter() 69 self._layout_components()
70
71 - def _create_toolbar(self):
72 self.tool_bar = ToolbarCreator(self, self.config).create()
73
74 - def _create_warning_bar(self):
75 self.message_bar = MessageBar(self)
76
78 79 def on_slider(evt): 80 self.config.divider_line_slider_pos = evt.GetPosition()
81 82 style = wx.SL_LEFT | wx.SL_VERTICAL 83 self.divider_line_slider = wx.Slider(self, size=(20, -1), style=style) 84 self.Bind(wx.EVT_SCROLL, on_slider, self.divider_line_slider) 85 86 self.divider_line_slider.Bind(wx.EVT_SLIDER, self._slider_on_slider) 87 self.divider_line_slider.Bind(wx.EVT_CONTEXT_MENU, self._slider_on_context_menu)
88
89 - def _slider_on_slider(self, evt):
90 self.timeline_canvas.SetDividerPosition(self.divider_line_slider.GetValue())
91
92 - def _slider_on_context_menu(self, evt):
93 menu = wx.Menu() 94 menu_item = wx.MenuItem(menu, wx.NewId(), _("Center")) 95 self.Bind(wx.EVT_MENU, self._context_menu_on_menu_center, id=menu_item.GetId()) 96 menu.Append(menu_item) 97 self.PopupMenu(menu) 98 menu.Destroy()
99
100 - def _context_menu_on_menu_center(self, evt):
101 self.timeline_canvas.SetDividerPosition(50)
102
103 - def _create_splitter(self):
104 self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE) 105 self.splitter.SetMinimumPaneSize(50) 106 self.Bind( 107 wx.EVT_SPLITTER_SASH_POS_CHANGED, 108 self._splitter_on_splitter_sash_pos_changed, self.splitter) 109 self._create_sidebar() 110 self._create_timeline_canvas() 111 self.splitter.Initialize(self.timeline_canvas)
112
113 - def _splitter_on_splitter_sash_pos_changed(self, event):
114 if self.IsShown(): 115 self.sidebar_width = self.splitter.GetSashPosition()
116
117 - def _create_sidebar(self):
118 self.sidebar = Sidebar(self._edit_controller, self.splitter)
119
120 - def _create_timeline_canvas(self):
121 self.timeline_canvas = MainCanvas( 122 self.splitter, self._edit_controller, self.status_bar_adapter) 123 self.timeline_canvas.Bind( 124 wx.EVT_LEFT_DCLICK, 125 self._timeline_canvas_on_double_clicked 126 ) 127 self.timeline_canvas.Bind( 128 wx.EVT_RIGHT_DOWN, 129 self._timeline_canvas_on_right_down 130 ) 131 self.timeline_canvas.Bind( 132 wx.EVT_KEY_DOWN, 133 self._timeline_canvas_on_key_down 134 ) 135 self.timeline_canvas.Bind( 136 EVT_DIVIDER_POSITION_CHANGED, 137 self._timeline_canvas_on_divider_position_changed 138 ) 139 self.timeline_canvas.Bind( 140 EVT_TIMELINE_REDRAWN, 141 self._timeline_canvas_on_timeline_redrawn 142 ) 143 self.timeline_canvas.SetDividerPosition(self.config.divider_line_slider_pos) 144 self.timeline_canvas.SetEventBoxDrawer(self._get_saved_event_box_drawer()) 145 self.timeline_canvas.SetInputHandler(NoOpInputHandler( 146 InputHandlerState( 147 self.timeline_canvas, self.status_bar_adapter, 148 self._edit_controller, self.config), 149 self.timeline_canvas)) 150 151 def update_appearance(): 152 appearance = self.timeline_canvas.GetAppearance() 153 appearance.set_legend_visible(self.config.show_legend) 154 appearance.set_balloons_visible(self.config.balloon_on_hover) 155 appearance.set_hide_events_done(self.config.hide_events_done) 156 appearance.set_minor_strip_divider_line_colour(self.config.minor_strip_divider_line_colour) 157 appearance.set_major_strip_divider_line_colour(self.config.major_strip_divider_line_colour) 158 appearance.set_now_line_colour(self.config.now_line_colour) 159 appearance.set_weekend_colour(self.config.weekend_colour) 160 appearance.set_bg_colour(self.config.bg_colour) 161 appearance.set_colorize_weekends(self.config.colorize_weekends) 162 appearance.set_draw_period_events_to_right(self.config.draw_point_events_to_right) 163 appearance.set_text_below_icon(self.config.text_below_icon) 164 appearance.set_minor_strip_font(self.config.minor_strip_font) 165 appearance.set_major_strip_font(self.config.major_strip_font) 166 appearance.set_balloon_font(self.config.balloon_font) 167 appearance.set_legend_font(self.config.legend_font) 168 appearance.set_center_event_texts(self.config.center_event_texts) 169 appearance.set_never_show_period_events_as_point_events(self.config.never_show_period_events_as_point_events) 170 appearance.set_week_start(self.config.get_week_start()) 171 appearance.set_use_inertial_scrolling(self.config.use_inertial_scrolling) 172 appearance.set_fuzzy_icon(self.config.fuzzy_icon) 173 appearance.set_locked_icon(self.config.locked_icon) 174 appearance.set_hyperlink_icon(self.config.hyperlink_icon) 175 appearance.set_vertical_space_between_events(self.config.vertical_space_between_events) 176 appearance.set_skip_s_in_decade_text(self.config.skip_s_in_decade_text) 177 appearance.set_display_checkmark_on_events_done(self.config.display_checkmark_on_events_done) 178 appearance.set_never_use_time(self.config.never_use_time) 179 appearance.set_legend_pos(self.config.legend_pos) 180 appearance.set_time_scale_pos(self.config.time_scale_pos) 181 appearance.set_use_bold_nowline(self.config.use_bold_nowline)
182 self.config.listen_for_any(update_appearance) 183 update_appearance() 184
185 - def _get_saved_event_box_drawer(self):
186 from timelinelib.plugin import factory 187 from timelinelib.plugin.factory import EVENTBOX_DRAWER 188 from timelinelib.plugin.plugins.eventboxdrawers.defaulteventboxdrawer import DefaultEventBoxDrawer 189 plugin = factory.get_plugin(EVENTBOX_DRAWER, self.config.get_selected_event_box_drawer()) or DefaultEventBoxDrawer() 190 return plugin.run()
191
192 - def _timeline_canvas_on_double_clicked(self, event):
193 if self.timeline_canvas.GetDb().is_read_only(): 194 return 195 cursor = Cursor(event.GetX(), event.GetY()) 196 timeline_event = self.timeline_canvas.GetEventAt(cursor) 197 time = self.timeline_canvas.GetTimeAt(cursor.x) 198 if timeline_event is not None: 199 if timeline_event.is_milestone(): 200 self.open_milestone_editor(timeline_event) 201 else: 202 self.open_event_editor(timeline_event) 203 else: 204 open_create_event_editor( 205 self._edit_controller, 206 self, 207 self.config, 208 self.timeline_canvas.GetDb(), 209 time, 210 time) 211 event.Skip()
212
213 - def _timeline_canvas_on_right_down(self, event):
214 cursor = Cursor(event.GetX(), event.GetY()) 215 timeline_event = self.timeline_canvas.GetEventAt(cursor) 216 if timeline_event is not None and not self.timeline_canvas.GetDb().is_read_only(): 217 self.timeline_canvas.SetEventSelected(timeline_event, True) 218 self._display_event_context_menu() 219 else: 220 display_timeline_context_menu = getattr(self.main_frame, "display_timeline_context_menu", None) 221 if callable(display_timeline_context_menu): 222 display_timeline_context_menu() 223 event.Skip()
224
225 - def _timeline_canvas_on_key_down(self, event):
226 if event.GetKeyCode() == wx.WXK_DELETE: 227 self._delete_selected_events() 228 elif event.GetKeyCode() == wx.WXK_UP: 229 self.move_selected_event_up() 230 elif event.GetKeyCode() == wx.WXK_DOWN: 231 self.move_selected_event_down() 232 elif event.AltDown() and event.GetKeyCode() in (wx.WXK_RIGHT, wx.WXK_NUMPAD_RIGHT): 233 self.timeline_canvas.Scroll(LEFT_RIGHT_SCROLL_FACTOR) 234 elif event.AltDown() and event.GetKeyCode() in (wx.WXK_LEFT, wx.WXK_NUMPAD_LEFT): 235 self.timeline_canvas.Scroll(-LEFT_RIGHT_SCROLL_FACTOR) 236 else: 237 event.Skip()
238
239 - def move_selected_event_up(self):
240 self._try_move_event_vertically(True)
241
242 - def move_selected_event_down(self):
243 self._try_move_event_vertically(False)
244
245 - def _try_move_event_vertically(self, up=True):
246 event = self.timeline_canvas.GetSelectedEvent() 247 if event is not None: 248 self._move_event_vertically(event, up)
249
250 - def _move_event_vertically(self, event, up=True):
251 def edit_function(): 252 ( 253 overlapping_event, 254 direction 255 ) = self.timeline_canvas.GetClosestOverlappingEvent(event, up=up) 256 if overlapping_event is None: 257 pass 258 elif direction > 0: 259 self.timeline_canvas.GetDb().place_event_after_event( 260 event, 261 overlapping_event 262 ) 263 else: 264 self.timeline_canvas.GetDb().place_event_before_event( 265 event, 266 overlapping_event 267 ) 268 # GetClosestOverlappingEvent inflates rectangles, so subsequent 269 # calls do calculations on incorrect rectangles. A redraw 270 # recalculates all rectangles. 271 self.redraw_timeline()
272 safe_locking(self._edit_controller, edit_function) 273
274 - def _display_event_context_menu(self):
275 menu_definitions = [ 276 (_("Delete"), self._context_menu_on_delete_event, None), 277 ] 278 nbr_of_selected_events = len(self.timeline_canvas.GetSelectedEvents()) 279 if nbr_of_selected_events == 1: 280 menu_definitions.insert(0, (_("Edit"), self._context_menu_on_edit_event, None)) 281 menu_definitions.insert(1, (_("Duplicate..."), self._context_menu_on_duplicate_event, None)) 282 menu_definitions.append((_("Mark Event as Done"), self._context_menu_on_done_event, None)) 283 menu_definitions.append((_("Select Category..."), self._context_menu_on_select_category, None)) 284 if nbr_of_selected_events == 1 and self.timeline_canvas.GetSelectedEvent().has_data(): 285 menu_definitions.append((_("Sticky Balloon"), self._context_menu_on_sticky_balloon_event, None)) 286 if nbr_of_selected_events == 1: 287 hyperlinks = self.timeline_canvas.GetSelectedEvent().get_data("hyperlink") 288 if hyperlinks is not None: 289 imp = wx.Menu() 290 menuid = 0 291 for hyperlink in hyperlinks.split(";"): 292 imp.Append(menuid, hyperlink) 293 menuid += 1 294 menu_definitions.append((_("Goto URL"), self._context_menu_on_goto_hyperlink_event, imp)) 295 menu = wx.Menu() 296 mid = 0 297 for menu_definition in menu_definitions: 298 text, method, imp = menu_definition 299 menu_item = wx.MenuItem(menu, mid, text) 300 if imp is not None: 301 for menu_item in imp.GetMenuItems(): 302 self.Bind(wx.EVT_MENU, method, id=menu_item.GetId()) 303 menu.AppendMenu(wx.ID_ANY, text, imp) 304 else: 305 self.Bind(wx.EVT_MENU, method, id=menu_item.GetId()) 306 menu.Append(menu_item) 307 mid += 1 308 self.PopupMenu(menu) 309 menu.Destroy()
310
311 - def _context_menu_on_delete_event(self, evt):
312 self._delete_selected_events()
313
314 - def _delete_selected_events(self):
315 selected_events = self.timeline_canvas.GetSelectedEvents() 316 number_of_selected_events = len(selected_events) 317 318 def edit_function(): 319 if user_ack(): 320 with self.timeline_canvas.GetDb().transaction("Delete events"): 321 for event in selected_events: 322 event.delete() 323 self.timeline_canvas.ClearSelectedEvents()
324 325 def user_ack(): 326 if number_of_selected_events > 1: 327 text = _("Are you sure you want to delete %d events?" % 328 number_of_selected_events) 329 else: 330 text = _("Are you sure you want to delete this event?") 331 return _ask_question(text) == wx.YES 332 safe_locking(self._edit_controller, edit_function) 333
334 - def _context_menu_on_edit_event(self, evt):
335 self.open_event_editor(self.timeline_canvas.GetSelectedEvent())
336
337 - def _context_menu_on_duplicate_event(self, evt):
338 open_duplicate_event_dialog_for_event( 339 self._edit_controller, 340 self, 341 self.timeline_canvas.GetDb(), 342 self.timeline_canvas.GetSelectedEvent())
343 344 # @experimental_feature(EVENT_DONE)
345 - def _context_menu_on_done_event(self, evt):
346 def edit_function(): 347 with self.timeline_canvas.GetDb().transaction("Mark events done"): 348 for event in self.timeline_canvas.GetSelectedEvents(): 349 if not event.is_container(): 350 event.set_progress(100) 351 event.save() 352 self.timeline_canvas.ClearSelectedEvents()
353 safe_locking(self._edit_controller, edit_function) 354
355 - def _context_menu_on_select_category(self, evt):
356 # TODO: Disable the context menu if the main_frame don't have 357 # a set_category_on_selected() method 358 set_category_on_selected = getattr(self.main_frame, 'set_category_on_selected', None) 359 if callable(set_category_on_selected): 360 set_category_on_selected()
361
362 - def _context_menu_on_sticky_balloon_event(self, evt):
363 self.timeline_canvas.SetEventStickyBalloon(self.timeline_canvas.GetSelectedEvent(), True)
364 369
370 - def _timeline_canvas_on_divider_position_changed(self, event):
371 self.divider_line_slider.SetValue(self.timeline_canvas.GetDividerPosition()) 372 self.config.divider_line_slider_pos = self.timeline_canvas.GetDividerPosition()
373
374 - def _timeline_canvas_on_timeline_redrawn(self, event):
375 text = _("%s events hidden") % self.timeline_canvas.GetHiddenEventCount() 376 self.status_bar_adapter.set_hidden_event_count_text(text) 377 enable_disable_menus = getattr(self.main_frame, 'enable_disable_menus') 378 if callable(enable_disable_menus): 379 enable_disable_menus()
380
381 - def _layout_components(self):
382 hsizer = wx.BoxSizer(wx.HORIZONTAL) 383 hsizer.Add(self.splitter, proportion=1, flag=wx.EXPAND) 384 hsizer.Add(self.divider_line_slider, proportion=0, flag=wx.EXPAND) 385 vsizer = wx.BoxSizer(wx.VERTICAL) 386 vsizer.Add(self.tool_bar, proportion=0, flag=wx.EXPAND) 387 vsizer.Add(self.message_bar, proportion=0, flag=wx.EXPAND) 388 vsizer.Add(hsizer, proportion=1, flag=wx.EXPAND) 389 self.SetSizer(vsizer)
390 391
392 -class TimelinePanel(TimelinePanelGuiCreator):
393
394 - def __init__(self, parent, config, status_bar_adapter, main_frame):
395 self.config = config 396 self.status_bar_adapter = status_bar_adapter 397 self.main_frame = main_frame 398 self._edit_controller = main_frame 399 TimelinePanelGuiCreator.__init__(self, parent) 400 self._db_listener = Listener(self._on_db_changed)
401
402 - def SetDb(self, db):
403 self.timeline_canvas.SetDb(db) 404 self._db_listener.set_observable(db)
405
406 - def get_timeline_canvas(self):
407 return self.timeline_canvas
408
409 - def get_time_period(self):
410 return self.timeline_canvas.GetTimePeriod()
411
412 - def open_event_editor(self, event):
413 if event.is_milestone(): 414 self.open_milestone_editor(event) 415 else: 416 open_event_editor_for( 417 self._edit_controller, 418 self, 419 self.config, 420 self.timeline_canvas.GetDb(), 421 event)
422
423 - def open_milestone_editor(self, event):
424 open_milestone_editor_for( 425 self._edit_controller, 426 self, 427 self.config, 428 self.timeline_canvas.GetDb(), 429 event)
430
431 - def redraw_timeline(self):
432 self.timeline_canvas.Redraw()
433
434 - def Navigate(self, navigation_fn):
435 return self.timeline_canvas.Navigate(navigation_fn)
436
437 - def get_view_properties(self):
438 return self.timeline_canvas.GetViewProperties()
439
440 - def get_sidebar_width(self):
441 return self.sidebar_width
442
443 - def show_sidebar(self):
444 self.splitter.SplitVertically(self.sidebar, self.timeline_canvas, self.sidebar_width) 445 self.splitter.SetSashPosition(self.sidebar_width) 446 self.splitter.SetMinimumPaneSize(self.sidebar.GetBestSize()[0])
447
448 - def hide_sidebar(self):
449 self.splitter.Unsplit(self.sidebar)
450
451 - def activated(self):
452 if self.config.show_sidebar: 453 self.show_sidebar()
454
455 - def _on_db_changed(self, db):
456 if db.is_read_only(): 457 header = _("This timeline is read-only.") 458 body = _("To edit this timeline, save it to a new file: File -> Save As.") 459 self.message_bar.ShowInformationMessage("%s\n%s" % (header, body)) 460 elif not db.is_saved(): 461 header = _("This timeline is not being saved.") 462 body = _("To save this timeline, save it to a new file: File -> Save As.") 463 self.message_bar.ShowWarningMessage("%s\n%s" % (header, body)) 464 else: 465 self.message_bar.ShowNoMessage()
466 467
468 -class InputHandlerState(object):
469
470 - def __init__(self, canvas, status_bar, edit_controller, config):
471 self._timeline_canvas = canvas 472 self._status_bar = status_bar 473 self._edit_controller = edit_controller 474 self._config = config
475
476 - def ok_to_edit(self):
477 return self._edit_controller.ok_to_edit()
478
479 - def edit_ends(self):
480 return self._edit_controller.edit_ends()
481
482 - def display_status(self, message):
483 return self._edit_controller.DisplayStatus(message)
484
485 - def change_to_no_op(self):
486 self._timeline_canvas.SetInputHandler( 487 NoOpInputHandler(self, self._timeline_canvas))
488
489 - def change_to_move_by_drag(self, event, start_drag_time):
490 self._timeline_canvas.SetInputHandler( 491 MoveByDragInputHandler(self, self._timeline_canvas, event, start_drag_time))
492
493 - def change_to_zoom_by_drag(self, cursor):
494 start_time = self._timeline_canvas.GetTimeAt(cursor.x) 495 self._timeline_canvas.SetInputHandler( 496 ZoomByDragInputHandler(self, self._timeline_canvas, start_time))
497
498 - def change_to_select(self, cursor):
499 self._timeline_canvas.SetInputHandler( 500 SelectEventsInputHandler(self, self._timeline_canvas, cursor))
501
502 - def change_to_resize_by_drag(self, event, direction):
503 self._timeline_canvas.SetInputHandler( 504 ResizeByDragInputHandler(self, self._timeline_canvas, event, direction))
505
506 - def change_to_scroll_by_drag(self, cursor):
507 start_time = self._timeline_canvas.GetTimeAt(cursor.x) 508 y = cursor.y 509 self._timeline_canvas.SetInputHandler( 510 ScrollByDragInputHandler(self, self._timeline_canvas, start_time, y))
511
513 time_at_x = self._timeline_canvas.GetTimeAt(cursor.x) 514 self._timeline_canvas.SetInputHandler( 515 CreatePeriodEventByDragInputHandler(self, self._timeline_canvas, self._config, time_at_x))
516