Gnumed.timelinelib.wxgui.dialogs.mainframe

Package Gnumed :: Package timelinelib :: Package wxgui :: Package dialogs :: Module mainframe |
[frames] | no frames]

# Source Code for Module Gnumed.timelinelib.wxgui.dialogs.mainframe

       1  # Copyright (C) 2009, 2010, 2011  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.path
      20
      21  import wx
      22
      23  from timelinelib.application import TimelineApplication
      24  from timelinelib.config.dotfile import read_config
      25  from timelinelib.config.paths import ICONS_DIR
      26  from timelinelib.db.exceptions import TimelineIOError
      27  from timelinelib.db import db_open
      28  from timelinelib.db.objects import TimePeriod
      29  from timelinelib.export.bitmap import export_to_image
      30  from timelinelib.meta.about import APPLICATION_NAME
      31  from timelinelib.meta.about import display_about_dialog
      32  from timelinelib.meta.version import DEV
      33  from timelinelib.utils import ex_msg
      34  from timelinelib.wxgui.components.cattree import CategoriesTree
      35  from timelinelib.wxgui.components.hyperlinkbutton import HyperlinkButton
      36  from timelinelib.wxgui.components.search import SearchBar
      37  from timelinelib.wxgui.components.timelineview import DrawingAreaPanel
      38  from timelinelib.wxgui.dialogs.categorieseditor import CategoriesEditor
      39  from timelinelib.wxgui.dialogs.duplicateevent import open_duplicate_event_dialog_for_event
      40  from timelinelib.wxgui.dialogs.eventeditor import open_create_event_editor
      41  from timelinelib.wxgui.dialogs.helpbrowser import HelpBrowser
      42  from timelinelib.wxgui.dialogs.playframe import PlayFrame
      43  from timelinelib.wxgui.dialogs.preferences import PreferencesDialog
      44  from timelinelib.wxgui.dialogs.textdisplay import TextDisplayDialog
      45  from timelinelib.wxgui.dialogs.timeeditor import TimeEditorDialog
      46  from timelinelib.wxgui.utils import _ask_question
      47  from timelinelib.wxgui.utils import _display_error_message
      48  from timelinelib.wxgui.utils import WildcardHelper
      49  import timelinelib.printing as printing
      50  import timelinelib.wxgui.utils as gui_utils
      51
      52
      53 -class MainFrame(wx.Frame):
      54
      55 -    def __init__(self, application_arguments):
      56          self.config = read_config(application_arguments.get_config_file_path())
      57
      58          wx.Frame.__init__(self, None, size=self.config.get_window_size(),
      59                            pos=self.config.get_window_pos(),
      60                            style=wx.DEFAULT_FRAME_STYLE, name="main_frame")
      61
      62          # To enable translations of wx stock items.
      63          self.locale = wx.Locale(wx.LANGUAGE_DEFAULT)
      64
      65          self.help_browser = HelpBrowser(self)
      66          self.controller = TimelineApplication(self, db_open, self.config)
      67          self.menu_controller = MenuController()
      68
      69          self._set_initial_values_to_member_variables()
      70          self._create_print_data()
      71          self._create_gui()
      72
      73          self.Maximize(self.config.get_window_maximized())
      74          self.SetTitle(APPLICATION_NAME)
      75          self.SetIcons(self._load_icon_bundle())
      76
      77          self.main_panel.show_welcome_panel()
      78          self.enable_disable_menus()
      79
      80          self.controller.on_started(application_arguments)
      81          self._create_and_start_timer()
      82
      83 -    def week_starts_on_monday(self):
      84          return self.config.week_start == "monday"
      85
      86 -    def _create_and_start_timer(self):
      87          self.timer = wx.Timer(self)
      88          self.Bind(wx.EVT_TIMER, self._timer_tick, self.timer)
      89          self.timer.Start(10000)
      90          self.alert_dialog_open = False
      91
      92 -    def _set_initial_values_to_member_variables(self):
      93          self.timeline = None
      94          self.timeline_wildcard_helper = WildcardHelper(
      95              _("Timeline files"), ["timeline", "ics"])
      96          self.images_svg_wildcard_helper = WildcardHelper(
      97              _("SVG files"), ["svg"])
      98
      99 -    def _create_print_data(self):
     100          self.printData = wx.PrintData()
     101          self.printData.SetPaperId(wx.PAPER_A4)
     102          self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER)
     103          self.printData.SetOrientation(wx.LANDSCAPE)
     104
     105 -    def _create_gui(self):
     106          self._create_status_bar()
     107          self._create_main_panel()
     108          self._create_main_menu_bar()
     109          self._bind_frame_events()
     110
     111 -    def _create_status_bar(self):
     112          self.CreateStatusBar()
     113          self.status_bar_adapter = StatusBarAdapter(self.GetStatusBar())
     114
     115 -    def _create_main_panel(self):
     116          self.main_panel = MainPanel(self, self.config, self)
     117
     127
     146
     152
     159
     160 -    def _mnu_file_new_on_click(self, event):
     161          self._create_new_timeline()
     162
     163 -    def _create_new_timeline(self):
     164          wildcard = self.timeline_wildcard_helper.wildcard_string()
     165          dialog = wx.FileDialog(self, message=_("Create Timeline"),
     166                                 wildcard=wildcard, style=wx.FD_SAVE)
     167          if dialog.ShowModal() == wx.ID_OK:
     168              self._save_current_timeline_data()
     169              path = self.timeline_wildcard_helper.get_path(dialog)
     170              if os.path.exists(path):
     171                  msg_first_part = _("The specified timeline already exists.")
     172                  msg_second_part = _("Opening timeline instead of creating new.")
     173                  wx.MessageBox("%s\n\n%s" % (msg_first_part, msg_second_part),
     174                                _("Information"),
     175                                wx.OK|wx.ICON_INFORMATION, self)
     176              self.open_timeline(path)
     177          dialog.Destroy()
     178
     183
     184 -    def _mnu_file_new_dir_on_click(self, event):
     185          self._create_new_dir_timeline()
     186
     187 -    def _create_new_dir_timeline(self):
     188          dialog = wx.DirDialog(self, message=_("Create Timeline"))
     189          if dialog.ShowModal() == wx.ID_OK:
     190              self._save_current_timeline_data()
     191              self.open_timeline(dialog.GetPath())
     192          dialog.Destroy()
     193
     199
     200 -    def _mnu_file_open_on_click(self, event):
     201          self._open_existing_timeline()
     202
     203 -    def _open_existing_timeline(self):
     204          dir = ""
     205          if self.timeline is not None:
     206              dir = os.path.dirname(self.timeline.path)
     207          wildcard = self.timeline_wildcard_helper.wildcard_string()
     208          dialog = wx.FileDialog(self, message=_("Open Timeline"),
     209                                 defaultDir=dir,
     210                                 wildcard=wildcard, style=wx.FD_OPEN)
     211          if dialog.ShowModal() == wx.ID_OK:
     212              self._save_current_timeline_data()
     213              self.open_timeline(dialog.GetPath())
     214          dialog.Destroy()
     215
     220
     226
     227 -    def _mnu_file_print_setup_on_click(self, event):
     228          printing.print_setup(self)
     229
     235
     236 -    def _mnu_file_print_preview_on_click(self, event):
     237          printing.print_preview(self)
     238
     244
     245 -    def _mnu_file_print_on_click(self, event):
     246          printing.print_timeline(self)
     247
     253
     254 -    def _mnu_file_export_on_click(self, evt):
     255          export_to_image(self)
     256
     262
     263 -    def _mnu_file_export_svg_on_click(self, evt):
     264          self._export_to_svg_image()
     265
     266 -    def _export_to_svg_image(self):
     267          if not self._has_pysvg_module():
     268              _display_error_message(_("Could not find pysvg Python package. It is needed to export to SVG. See the Timeline website or the INSTALL file for instructions how to install it."), self)
     269              return
     270          import timelinelib.export.svg as svgexport
     271          wildcard = self.images_svg_wildcard_helper.wildcard_string()
     272          dialog = wx.FileDialog(self, message=_("Export to SVG"),
     273                                 wildcard=wildcard, style=wx.FD_SAVE)
     274          if dialog.ShowModal() == wx.ID_OK:
     275              path = self.images_svg_wildcard_helper.get_path(dialog)
     276              overwrite_question = _("File '%s' exists. Overwrite?") % path
     277              if (not os.path.exists(path) or
     278                  _ask_question(overwrite_question, self) == wx.YES):
     279                  svgexport.export(
     280                      path,
     281                      self.main_panel.drawing_area.get_drawer().scene,
     282                      self.main_panel.drawing_area.get_view_properties())
     283          dialog.Destroy()
     284
     285 -    def _has_pysvg_module(self):
     286          try:
     287              import pysvg
     288              return True
     289          except ImportError:
     290              return False
     291
     292 -    def _create_file_play(self, file_menu):
     293          mnu_play = file_menu.Append(
     294              wx.ID_ANY, _("Play timeline"), _("Play timeline as movie"))
     295          self.menu_controller.add_menu_requiring_timeline(mnu_play)
     296          self.Bind(wx.EVT_MENU, self._mnu_play_on_click, mnu_play)
     297
     298 -    def _mnu_play_on_click(self, file_menu):
     299          self.controller.on_play_clicked()
     300
     304
     305 -    def _mnu_file_exit_on_click(self, evt):
     306          self.Close()
     307
     314
     319
     320 -    def _mnu_edit_find_on_click(self, evt):
     321          self.main_panel.show_searchbar(True)
     322
     326
     327 -    def _mnu_edit_preferences_on_click(self, evt):
     328          dialog = PreferencesDialog(self, self.config)
     329          dialog.ShowModal()
     330          dialog.Destroy()
     331
     339
     346
     353
     360
     361 -    def _mnu_view_legend_on_click(self, evt):
     362          self.config.set_show_legend(evt.IsChecked())
     363          self.main_panel.drawing_area.show_hide_legend(evt.IsChecked())
     364
     370
     371 -    def _mnu_view_balloons_on_click(self, evt):
     372          self.config.set_balloon_on_hover(evt.IsChecked())
     373          self.main_panel.drawing_area.balloon_visibility_changed(evt.IsChecked())
     374
     382
     388
     389 -    def _mnu_timeline_create_event_on_click(self, evt):
     390          open_create_event_editor(
     391              self, self.config, self.timeline, self.handle_db_error)
     392
     399
     400 -    def _mnu_timeline_duplicate_event_on_click(self, evt):
     401          try:
     402              drawing_area = self.main_panel.drawing_area
     403              id = drawing_area.get_view_properties().get_selected_event_ids()[0]
     404              event = self.timeline.find_event_with_id(id)
     405          except IndexError, e:
     406              # No event selected so do nothing!
     407              return
     408          open_duplicate_event_dialog_for_event(
     409              self,
     410              self.timeline,
     411              self.handle_db_error,
     412              event)
     413
     421
     422 -    def _mnu_timeline_measure_distance_between_events_on_click(self, evt):
     423          self._measure_distance_between_events()
     424
     425 -    def _measure_distance_between_events(self):
     426          event1, event2 = self._get_selected_events()
     427          distance = self._calc_events_distance(event1, event2)
     428          self._display_distance(distance)
     429
     430 -    def _get_selected_events(self):
     431          view_properties = self.main_panel.drawing_area.get_view_properties()
     432          event_id_1 = view_properties.selected_event_ids[0]
     433          event_id_2 = view_properties.selected_event_ids[1]
     434          event1 = self.timeline.find_event_with_id(event_id_1)
     435          event2 = self.timeline.find_event_with_id(event_id_2)
     436          return event1, event2
     437
     438 -    def _calc_events_distance(self,event1, event2):
     439          if event1.time_period.start_time <= event2.time_period.start_time:
     440              distance = (event2.time_period.start_time -
     441                          event1.time_period.end_time)
     442          else:
     443              distance = (event1.time_period.start_time -
     444                          event2.time_period.end_time)
     445          return distance
     446
     447 -    def _display_distance(self, distance):
     448          header = _("Distance between selected events")
     449          distance_text = self.timeline.get_time_type().format_delta(distance)
     450          if distance_text == "0":
     451              distance_text = _("Events are overlapping or distance is 0")
     452          self._display_text(header, distance_text)
     453
     454 -    def _display_text(self, header, text):
     455          dialog = wx.MessageDialog(self, text, header,
     456                                    wx.OK | wx.ICON_INFORMATION)
     457          dialog.ShowModal()
     458          dialog.Destroy()
     459
     460 -    def _create_timeline_edit_categories(self, timeline_menu):
     461          edit_categories_item = timeline_menu.Append(
     462              wx.ID_ANY, _("Edit &Categories"), _("Edit categories"))
     463          self.Bind(wx.EVT_MENU, self._mnu_timeline_edit_categories_on_click,
     464                    edit_categories_item)
     465          self.menu_controller.add_menu_requiring_writable_timeline(edit_categories_item)
     466
     467 -    def _mnu_timeline_edit_categories_on_click(self, evt):
     468          self.edit_categories()
     469
     470 -    def edit_categories(self):
     471          def create_categories_editor():
     472              return CategoriesEditor(self, self.timeline)
     473          gui_utils.show_modal(create_categories_editor, self.handle_db_error)
     474
     486
     491
     492 -    def _mnu_navigate_find_first_on_click(self, evt):
     493          event = self.timeline.get_first_event()
     494          if event:
     495              start = event.time_period.start_time
     496              delta = self.main_panel.drawing_area.get_view_properties().displayed_period.delta()
     497              end   = start + delta
     498              margin_delta = self.timeline.get_time_type().margin_delta(delta)
     499              self._navigate_timeline(lambda tp: tp.update(start, end, -margin_delta))
     500
     505
     506 -    def _mnu_navigate_find_last_on_click(self, evt):
     507          event = self.timeline.get_last_event()
     508          if event:
     509              end = event.time_period.end_time
     510              delta = self.main_panel.drawing_area.get_view_properties().displayed_period.delta()
     511              start = end - delta
     512              margin_delta = self.timeline.get_time_type().margin_delta(delta)
     513              self._navigate_timeline(lambda tp: tp.update(start, end, end_delta=margin_delta))
     514
     519
     520 -    def _mnu_navigate_fit_all_events_on_click(self, evt):
     521          self._fit_all_events()
     522
     523 -    def _fit_all_events(self):
     524          all_period = self._period_for_all_visible_events()
     525          if all_period == None:
     526              return
     527          if all_period.is_period():
     528              all_period = all_period.zoom(-1)
     529              self._navigate_timeline(lambda tp: tp.update(all_period.start_time, all_period.end_time))
     530          else:
     531              self._navigate_timeline(lambda tp: tp.center(all_period.mean_time()))
     532
     533 -    def _period_for_all_visible_events(self):
     534          try:
     535              visible_events = self._all_visible_events()
     536              if len(visible_events) > 0:
     537                  time_type = self.timeline.get_time_type()
     538                  start = self._first_time(visible_events)
     539                  end = self._last_time(visible_events)
     540                  return TimePeriod(time_type, start, end)
     541              else:
     542                  return None
     543          except ValueError, ex:
     544              _display_error_message(ex.message)
     545          return None
     546
     547 -    def _all_visible_events(self):
     548          view_properties = self.main_panel.drawing_area.get_view_properties()
     549          all_events = self.timeline.get_all_events()
     550          visible_events = view_properties.filter_events(all_events)
     551          return visible_events
     552
     553 -    def _first_time(self, events):
     554          start_time = lambda event: event.time_period.start_time
     555          return start_time(min(events, key=start_time))
     556
     557 -    def _last_time(self, events):
     558          end_time = lambda event: event.time_period.end_time
     559          return end_time(max(events, key=end_time))
     560
     570
     571 -    def _create_help_contents_menu_item(self, help_menu):
     572          contents_item = help_menu.Append(wx.ID_HELP, _("&Contents\tF1"))
     573          self.Bind(wx.EVT_MENU, self._mnu_help_contents_on_click, contents_item)
     574
     575 -    def _mnu_help_contents_on_click(self, e):
     576          self.help_browser.show_page("contents")
     577
     581
     582 -    def _mnu_help_tutorial_on_click(self, e):
     583          self.open_timeline(":tutorial:")
     584
     588
     589 -    def _mnu_help_contact_on_click(self, e):
     590          self.help_browser.show_page("contact")
     591
     595
     596 -    def _mnu_help_about_on_click(self, e):
     597          display_about_dialog()
     598
     599 -    def _bind_frame_events(self):
     600          self.Bind(wx.EVT_CLOSE, self._window_on_close)
     601
     602 -    def _window_on_close(self, event):
     603          self._save_current_timeline_data()
     604          self._save_application_config()
     605          self.Destroy()
     606
     607 -    def _save_application_config(self):
     608          self.config.set_window_size(self.GetSize())
     609          self.config.set_window_pos(self.GetPosition())
     610          self.config.set_window_maximized(self.IsMaximized())
     611          self.config.set_sidebar_width(self.main_panel.get_sidebar_width())
     612          try:
     613              self.config.write()
     614          except IOError, ex:
     615              friendly = _("Unable to write configuration file.")
     616              msg = "%s\n\n%s" % (friendly, ex_msg(ex))
     617              _display_error_message(msg, self)
     618
     619 -    def open_timeline(self, input_file):
     620          self.controller.open_timeline(input_file)
     621          self._update_navigation_menu_items()
     622
     623 -    def handle_db_error(self, error):
     624          _display_error_message(ex_msg(error), self)
     625          self._switch_to_error_view(error)
     626
     627 -    def _switch_to_error_view(self, error):
     628          self.controller.set_no_timeline()
     629          self.main_panel.error_panel.populate(error)
     630          self.main_panel.show_error_panel()
     631          self.enable_disable_menus()
     632
     633 -    def _display_timeline(self, timeline):
     634          self.timeline = timeline
     635          self.menu_controller.on_timeline_change(timeline)
     636          if timeline == None:
     637              # Do this before the next line so that we still have a timeline to
     638              # unregister
     639              self.main_panel.cattree.initialize_from_timeline_view(None)
     640              self.main_panel.searchbar.set_view(None)
     641          self.main_panel.drawing_area.set_timeline(self.timeline)
     642          self.status_bar_adapter.set_read_only_text("")
     643          if timeline == None:
     644              self.main_panel.show_welcome_panel()
     645              self.SetTitle(APPLICATION_NAME)
     646          else:
     647              self.main_panel.cattree.initialize_from_timeline_view(self.main_panel.drawing_area)
     648              self.main_panel.searchbar.set_view(self.main_panel.drawing_area)
     649              self.main_panel.show_timeline_panel()
     650              self.SetTitle("%s (%s) - %s" % (
     651                  os.path.basename(self.timeline.path),
     652                  os.path.dirname(os.path.abspath(self.timeline.path)),
     653                  APPLICATION_NAME))
     654              if timeline.is_read_only():
     655                  self.status_bar_adapter.set_read_only_text(_("read-only"))
     656
     657 -    def _add_ellipses_to_menuitem(self, id):
     658          plain = wx.GetStockLabel(id,
     659                  wx.STOCK_WITH_ACCELERATOR|wx.STOCK_WITH_MNEMONIC)
     660          # format of plain 'xxx[\tyyy]', example '&New\tCtrl+N'
     661          tab_index = plain.find("\t")
     662          if tab_index != -1:
     663              return plain[:tab_index] + "..." + plain[tab_index:]
     664          return plain + "..."
     665
     670
     675
     688
     692
     693 -    def _update_open_recent_submenu(self):
     694          # Clear items
     695          for item in self.mnu_file_open_recent_submenu.GetMenuItems():
     696              self.mnu_file_open_recent_submenu.DeleteItem(item)
     697          # Create new items and map (item id > path)
     698          self.open_recent_map = {}
     699          for path in self.config.get_recently_opened():
     700              name = "%s (%s)" % (
     701                  os.path.basename(path),
     702                  os.path.dirname(os.path.abspath(path)))
     703              item = self.mnu_file_open_recent_submenu.Append(wx.ID_ANY, name)
     704              self.open_recent_map[item.GetId()] = path
     705              self.Bind(wx.EVT_MENU, self._mnu_file_open_recent_item_on_click,
     706                        item)
     707
     708 -    def _mnu_file_open_recent_item_on_click(self, event):
     709          path = self.open_recent_map[event.GetId()]
     710          self.open_timeline_if_exists(path)
     711
     712 -    def open_timeline_if_exists(self, path):
     713          if os.path.exists(path):
     714              self.open_timeline(path)
     715          else:
     716              _display_error_message(_("File '%s' does not exist.") % path, self)
     717
     718 -    def enable_disable_menus(self):
     719          self.menu_controller.enable_disable_menus(self.main_panel.timeline_panel_visible())
     720          self._enable_disable_duplicate_event_menu()
     721          self._enable_disable_measure_distance_between_two_events_menu()
     722          self._enable_disable_searchbar()
     723
     728
     733
     734 -    def _enable_disable_searchbar(self):
     735          if self.timeline == None:
     736              self.main_panel.show_searchbar(False)
     737
     738 -    def _save_current_timeline_data(self):
     739          if self.timeline:
     740              try:
     741                  self.timeline.save_view_properties(self.main_panel.drawing_area.get_view_properties())
     742              except TimelineIOError, e:
     743                  self.handle_db_error(e)
     744
     745 -    def _load_icon_bundle(self):
     746          bundle = wx.IconBundle()
     747          for size in ["16", "32", "48"]:
     748              iconpath = os.path.join(ICONS_DIR, "%s.png" % size)
     749              icon = wx.IconFromBitmap(wx.BitmapFromImage(wx.Image(iconpath)))
     750              bundle.AddIcon(icon)
     751          return bundle
     752
     753 -    def _navigate_timeline(self, navigation_fn):
     754          return self.main_panel.drawing_area.navigate_timeline(navigation_fn)
     755
     756 -    def _get_time_period(self):
     757          return self.main_panel.drawing_area.get_time_period()
     758
     759 -    def display_time_editor_dialog(self, time_type, initial_time,
     760                                     handle_new_time_fn, title):
     761          dialog = TimeEditorDialog(self, self.config, time_type, initial_time, title)
     762          if dialog.ShowModal() == wx.ID_OK:
     763              handle_new_time_fn(dialog.time)
     764          dialog.Destroy()
     765
     766 -    def open_play_frame(self, timeline):
     767          dialog = PlayFrame(timeline, self.config)
     768          dialog.ShowModal()
     769          dialog.Destroy()
     770
     771 -    def _timer_tick(self, evt):
     772          self._handle_event_alerts()
     773
     774 -    def _handle_event_alerts(self):
     775          if self.timeline is None:
     776              return
     777          if self.alert_dialog_open:
     778              return
     779          self._display_events_alerts()
     780          self.alert_dialog_open = False
     781
     782 -    def _display_events_alerts(self):
     783          self.alert_dialog_open = True
     784          all_events = self.timeline.get_all_events()
     785          AlertController().display_events_alerts(all_events, self.timeline.get_time_type())
     786
     787
     788 -class AlertController(object):
     789
     790 -    def display_events_alerts(self, all_events, time_type):
     791          self.time_type = time_type
     792          for event in all_events:
     793              alert = event.get_data("alert")
     794              if alert is not None:
     795                  if self._time_has_expired(alert[0]):
     796                      self._display_and_delete_event_alert(event, alert)
     797
     798 -    def _display_and_delete_event_alert(self, event, alert):
     799          self._display_alert_dialog(alert, event)
     800          event.set_data("alert", None)
     801
     802 -    def _alert_time_as_text(self, alert):
     803          return "%s" % alert[0]
     804
     805 -    def _time_has_expired(self, time):
     806          return time <= self.time_type.now()
     807
     808
     809 -    def _display_alert_dialog(self, alert, event):
     810          text = self._format_alert_text(alert, event)
     811          dialog = TextDisplayDialog("Alert", text)
     812          dialog.ShowModal()
     813          dialog.Destroy()
     814
     815 -    def _format_alert_text(self, alert, event):
     816          text1 = "Trigger time: %s\n\n" % alert[0]
     817          text2 = "Event: %s\n\n" % event.get_label()
     818          text = "%s%s%s" % (text1, text2, alert[1])
     819          return text
     820
     821
     822 -class MenuController(object):
     823
     824 -    def __init__(self):
     825          self.current_timeline = None
     826          self.menus_requiring_timeline = []
     827          self.menus_requiring_writable_timeline = []
     828          self.menus_requiring_visible_timeline_view = []
     829
     830 -    def on_timeline_change(self, timeline):
     831          self.current_timeline = timeline
     832
     835
     838
     841
     842 -    def enable_disable_menus(self, timeline_view_visible):
     843          for menu in self.menus_requiring_writable_timeline:
     844              self._enable_disable_menu_requiring_writable_timeline(menu)
     845          for menu in self.menus_requiring_timeline:
     846              self._enable_disable_menu_requiring_timeline(menu)
     847          for menu in self.menus_requiring_visible_timeline_view:
     848              self._enable_disable_menu_requiring_visible_timeline_view(menu, timeline_view_visible)
     849
     857
     860
     864
     865
     866 -class MainPanel(wx.Panel):
     867      """
     868      Panel that covers the whole client area of MainFrame.
     869
     870      Displays one of the following panels:
     871
     872        * The welcome panel (show_welcome_panel)
     873        * A splitter with sidebar and DrawingAreaPanel (show_timeline_panel)
     874        * The error panel (show_error_panel)
     875
     876      Also displays the search bar.
     877      """
     878
     879 -    def __init__(self, parent, config, main_frame):
     880          wx.Panel.__init__(self, parent)
     881          self.config = config
     882          self.main_frame = main_frame
     883          self._create_gui()
     884          # Install variables for backwards compatibility
     885          self.cattree = self.timeline_panel.sidebar.cattree
     886          self.drawing_area = self.timeline_panel.drawing_area
     887          self.show_sidebar = self.timeline_panel.show_sidebar
     888          self.hide_sidebar = self.timeline_panel.hide_sidebar
     889          self.get_sidebar_width = self.timeline_panel.get_sidebar_width
     890
     891 -    def timeline_panel_visible(self):
     892          return self.timeline_panel.IsShown()
     893
     894 -    def show_welcome_panel(self):
     895          self._show_panel(self.welcome_panel)
     896
     897 -    def show_timeline_panel(self):
     898          self._show_panel(self.timeline_panel)
     899
     900 -    def show_error_panel(self):
     901          self._show_panel(self.error_panel)
     902
     903 -    def show_searchbar(self, show=True):
     904          self.searchbar.Show(show)
     905          if show == True:
     906              self.searchbar.search.SetFocus()
     907          self.GetSizer().Layout()
     908
     909 -    def _create_gui(self):
     910          # Search bar
     911          def search_close():
     912              self.show_searchbar(False)
     913          self.searchbar = SearchBar(self, search_close)
     914          self.searchbar.Show(False)
     915          # Panels
     916          self.welcome_panel = WelcomePanel(self, self.main_frame)
     917          self.timeline_panel = TimelinePanel(
     918              self, self.config, self.main_frame.handle_db_error,
     919              self.main_frame.status_bar_adapter, self.main_frame)
     920          self.error_panel = ErrorPanel(self, self.main_frame)
     921          # Layout
     922          self.sizerOuter = wx.BoxSizer(wx.VERTICAL)
     923          self.sizer = wx.BoxSizer(wx.HORIZONTAL)
     924          self.sizer.Add(self.welcome_panel, flag=wx.GROW, proportion=1)
     925          self.sizer.Add(self.timeline_panel, flag=wx.GROW, proportion=1)
     926          self.sizer.Add(self.error_panel, flag=wx.GROW, proportion=1)
     927          self.sizerOuter.Add(self.sizer, flag=wx.GROW, proportion=1)
     928          self.sizerOuter.Add(self.searchbar, flag=wx.GROW, proportion=0)
     929          self.SetSizer(self.sizerOuter)
     930
     931 -    def _show_panel(self, panel):
     932          # Hide all panels
     933          for panel_to_hide in [self.welcome_panel, self.timeline_panel,
     934                                self.error_panel]:
     935              panel_to_hide.Show(False)
     936          # Show this one
     937          panel.Show(True)
     938          self.sizerOuter.Layout()
     939          panel.activated()
     940
     941
     942 -class WelcomePanel(wx.Panel):
     943
     944 -    def __init__(self, parent, main_frame):
     945          wx.Panel.__init__(self, parent)
     946          self.main_frame = main_frame
     947          self._create_gui()
     948
     949 -    def _create_gui(self):
     950          vsizer = wx.BoxSizer(wx.VERTICAL)
     951          # Text 1
     952          t1 = wx.StaticText(self, label=_("No timeline opened."))
     953          vsizer.Add(t1, flag=wx.ALIGN_CENTER_HORIZONTAL)
     954          # Spacer
     955          vsizer.AddSpacer(20)
     956          # Text 2
     957          t2 = wx.StaticText(self, label=_("First time using Timeline?"))
     958          vsizer.Add(t2, flag=wx.ALIGN_CENTER_HORIZONTAL)
     959          # Button
     960          btn_tutorial = HyperlinkButton(self, _("Getting started tutorial"))
     961          self.Bind(wx.EVT_HYPERLINK, self._btn_tutorial_on_click, btn_tutorial)
     962          vsizer.Add(btn_tutorial, flag=wx.ALIGN_CENTER_HORIZONTAL)
     963          # Sizer
     964          hsizer = wx.BoxSizer(wx.HORIZONTAL)
     965          hsizer.Add(vsizer, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, proportion=1)
     966          self.SetSizer(hsizer)
     967
     968 -    def _btn_tutorial_on_click(self, e):
     969          self.main_frame.open_timeline(":tutorial:")
     970
     971 -    def activated(self):
     972          pass
     973
     974
     975 -class TimelinePanel(wx.Panel):
     976
     977 -    def __init__(self, parent, config, handle_db_error, status_bar_adapter,
     978              main_frame):
     979          wx.Panel.__init__(self, parent)
     980          self.config = config
     981          self.handle_db_error = handle_db_error
     982          self.status_bar_adapter = status_bar_adapter
     983          self.main_frame = main_frame
     984          self.sidebar_width = self.config.get_sidebar_width()
     985          self._create_gui()
     986
     987 -    def _create_gui(self):
     988          self._create_divider_line_slider()
     989          self._create_splitter()
     990          self._layout_components()
     991
     992 -    def _create_divider_line_slider(self):
     993          self.divider_line_slider = wx.Slider(
     994              self, value=50, size=(20, -1), style=wx.SL_LEFT|wx.SL_VERTICAL)
     995
     996 -    def _create_splitter(self):
     997          self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
     998          self.splitter.SetMinimumPaneSize(50)
     999          self.Bind(
    1000              wx.EVT_SPLITTER_SASH_POS_CHANGED,
    1001              self._splitter_on_splitter_sash_pos_changed, self.splitter)
    1002          self._create_sidebar()
    1003          self._create_drawing_area()
    1004          self.splitter.Initialize(self.drawing_area)
    1005
    1006 -    def _splitter_on_splitter_sash_pos_changed(self, event):
    1007          if self.IsShown():
    1008              self.sidebar_width = self.splitter.GetSashPosition()
    1009
    1012
    1013 -    def _create_drawing_area(self):
    1014          self.drawing_area = DrawingAreaPanel(
    1015              self.splitter,
    1016              self.status_bar_adapter,
    1017              self.divider_line_slider,
    1018              self.handle_db_error,
    1019              self.config,
    1020              self.main_frame)
    1021
    1022 -    def _layout_components(self):
    1023          sizer = wx.BoxSizer(wx.HORIZONTAL)
    1024          sizer.Add(self.splitter, proportion=1, flag=wx.EXPAND)
    1025          sizer.Add(self.divider_line_slider, proportion=0, flag=wx.EXPAND)
    1026          self.SetSizer(sizer)
    1027
    1030
    1035
    1038
    1039 -    def activated(self):
    1040          if self.config.get_show_sidebar():
    1041              self.show_sidebar()
    1042
    1043
    1044 -class ErrorPanel(wx.Panel):
    1045
    1046 -    def __init__(self, parent, main_frame):
    1047          wx.Panel.__init__(self, parent)
    1048          self.main_frame = main_frame
    1049          self._create_gui()
    1050
    1051 -    def populate(self, error):
    1052          self.txt_error.SetLabel(ex_msg(error))
    1053
    1054 -    def _create_gui(self):
    1055          vsizer = wx.BoxSizer(wx.VERTICAL)
    1056          # Error text
    1057          self.txt_error = wx.StaticText(self, label="")
    1058          vsizer.Add(self.txt_error, flag=wx.ALIGN_CENTER_HORIZONTAL)
    1059          # Spacer
    1060          vsizer.AddSpacer(20)
    1061          # Help text
    1062          txt_help = wx.StaticText(self, label=_("Relevant help topics:"))
    1063          vsizer.Add(txt_help, flag=wx.ALIGN_CENTER_HORIZONTAL)
    1064          # Button
    1065          btn_contact = HyperlinkButton(self, _("Contact"))
    1066          self.Bind(wx.EVT_HYPERLINK, self._btn_contact_on_click, btn_contact)
    1067          vsizer.Add(btn_contact, flag=wx.ALIGN_CENTER_HORIZONTAL)
    1068          # Sizer
    1069          hsizer = wx.BoxSizer(wx.HORIZONTAL)
    1070          hsizer.Add(vsizer, flag=wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL, proportion=1)
    1071          self.SetSizer(hsizer)
    1072
    1073 -    def _btn_contact_on_click(self, e):
    1074          self.main_frame.help_browser.show_page("contact")
    1075
    1076 -    def activated(self):
    1077          pass
    1078
    1079
    1097
    1098
    1099 -class StatusBarAdapter(object):
    1100
    1101      HIDDEN_EVENT_COUNT_COLUMN = 1
    1102      READ_ONLY_COLUMN = 2
    1103
    1104 -    def __init__(self, wx_status_bar):
    1105          self.wx_status_bar = wx_status_bar
    1106          self.wx_status_bar.SetFieldsCount(3)
    1107          self.wx_status_bar.SetStatusWidths([-1, 200, 150])
    1108
    1109 -    def set_text(self, text):
    1110          self.wx_status_bar.SetStatusText(text)
    1111
    1112 -    def set_hidden_event_count_text(self, text):
    1113          self.wx_status_bar.SetStatusText(text, self.HIDDEN_EVENT_COUNT_COLUMN)
    1114
    1115 -    def set_read_only_text(self, text):
    1116          self.wx_status_bar.SetStatusText(text, self.READ_ONLY_COLUMN)
    1117