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