Gnumed.timelinelib.wxgui.frames.mainframe.mainframe

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

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

      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.path
     20
     21  import wx.lib.newevent
     22
     23  from timelinelib.canvas.data import TimePeriod
     24  from timelinelib.config.dotfile import read_config
     25  from timelinelib.config.paths import ICONS_DIR
     26  from timelinelib.config.paths import LOCALE_DIR
     27  from timelinelib.dataexport.timelinexml import export_db_to_timeline_xml
     28  from timelinelib.db import db_open
     29  from timelinelib.features.experimental.experimentalfeatures import ExperimentalFeatures
     30  from timelinelib.meta.about import APPLICATION_NAME
     31  from timelinelib.calendar.bosparanian.timetype import BosparanianTimeType
     32  from timelinelib.calendar.num.timetype import NumTimeType
     33  from timelinelib.utils import ex_msg
     34  from timelinelib.wxgui.dialogs.changenowdate.view import ChangeNowDateDialog
     35  from timelinelib.wxgui.dialogs.eraseditor.view import ErasEditorDialog
     36  from timelinelib.wxgui.dialogs.setcategory.view import SetCategoryDialog
     37  from timelinelib.wxgui.dialogs.timeeditor.view import TimeEditorDialog
     38  from timelinelib.wxgui.frames.helpbrowserframe.helpbrowserframe import HelpBrowserFrame
     39  from timelinelib.wxgui.frames.mainframe.mainframecontroller import LockedException
     40  from timelinelib.wxgui.frames.mainframe.mainframecontroller import MainFrameController
     41  from timelinelib.wxgui.timer import TimelineTimer
     42  from timelinelib.wxgui.utils import display_categories_editor_moved_message
     43  from timelinelib.wxgui.utils import display_error_message
     44  from timelinelib.wxgui.utils import display_information_message
     45  from timelinelib.wxgui.utils import WildcardHelper
     46  import timelinelib.wxgui.frames.mainframe.guicreator as guic
     47  from timelinelib.wxgui.frames.mainframe.controllerapi import MainFrameApiUsedByController
     48  from timelinelib.wxgui.frames.mainframe.alertcontroller import AlertController
     49  from timelinelib.wxgui.frames.mainframe.menucontroller import MenuController
     50
     51  CatsViewChangedEvent, EVT_CATS_VIEW_CHANGED = wx.lib.newevent.NewCommandEvent()
     52
     53
     54 -class MainFrame(wx.Frame, guic.GuiCreator, MainFrameApiUsedByController):
     55
     56 -    def __init__(self, application_arguments):
     57          self.config = read_config(application_arguments.get_config_file_path())
     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          self.Bind(EVT_CATS_VIEW_CHANGED, self._on_cats_view_changed)
     62          # To enable translations of wx stock items.
     63          self.locale = wx.Locale(wx.LANGUAGE_DEFAULT)
     64          self.locale.AddCatalogLookupPathPrefix(LOCALE_DIR)
     65          self.locale.AddCatalog("wxstd")
     66          self.help_browser = HelpBrowserFrame(self)
     67          self.controller = MainFrameController(self, db_open, self.config)
     68          self.menu_controller = MenuController()
     69          self._set_initial_values_to_member_variables()
     70          self._create_print_data()
     71          self._set_experimental_features()
     72          self._create_gui()
     73          self.Maximize(self.config.window_maximized)
     74          self.SetTitle(APPLICATION_NAME)
     75          self.SetIcons(self._load_icon_bundle())
     76          self.main_panel.show_welcome_panel()
     77          self.enable_disable_menus()
     78          self.controller.on_started(application_arguments)
     79          self._create_and_start_timer()
     80          self.prev_time_period = None
     81
     82 -    def DisplayErrorMessage(self, message):
     83          display_error_message(message, parent=self)
     84
     85 -    def DisplayStatus(self, message):
     86          self.status_bar_adapter.set_text(message)
     87
     88      # API:s used by time types
     89 -    def week_starts_on_monday(self):
     90          return self.controller.week_starts_on_monday()
     91
     92 -    def display_time_editor_dialog(self, time_type, initial_time,
     93                                     handle_new_time_fn, title):
     94          dialog = TimeEditorDialog(self, self.config, time_type, initial_time, title)
     95          dialog.ShowModal()
     96          result = dialog.GetReturnCode()
     97          if result == wx.ID_OK:
     98              handle_new_time_fn(dialog.GetTime())
     99          dialog.Destroy()
    100
    101 -    def display_now_date_editor_dialog(self, handle_new_time_fn, title):
    102          dialog = ChangeNowDateDialog(self, self.config, self.timeline, handle_new_time_fn, title)
    103          dialog.Show()
    104
    105 -    def save_time_period(self):
    106          self.prev_time_period = self.main_panel.get_time_period()
    107
    108      # Concurrent editing
    109 -    def ok_to_edit(self):
    110          try:
    111              return self.controller.ok_to_edit()
    112          except LockedException:
    113              return False
    114
    115 -    def get_lockfile_path(self):
    116          return self.controller._get_lockpath()
    117
    118 -    def edit_ends(self):
    119          self.controller.edit_ends()
    120
    121 -    def get_view_properties(self, ):
    122          return self.main_panel.get_view_properties()
    123
    124 -    def _on_cats_view_changed(self, evt):
    125          self.main_panel.get_view_properties().change_view_cats_individually(evt.GetClientData())
    126
    127      # Creation process methods
    128 -    def _set_initial_values_to_member_variables(self):
    129          self.timeline = None
    130          self.timeline_wildcard_helper = WildcardHelper(
    131              _("Timeline files"), ["timeline", "ics"])
    132          self.images_svg_wildcard_helper = WildcardHelper(
    133              _("SVG files"), ["svg"])
    134
    135 -    def _create_print_data(self):
    136          self.printData = wx.PrintData()
    137          self.printData.SetPaperId(wx.PAPER_A4)
    138          self.printData.SetPrintMode(wx.PRINT_MODE_PRINTER)
    139          self.printData.SetOrientation(wx.LANDSCAPE)
    140
    141 -    def _create_and_start_timer(self):
    142          self.alert_dialog_open = False
    143          self.timer = TimelineTimer(self)
    144          self.timer.register(self._timer_tick)
    145          self.timer.start(10000)
    146
    147 -    def _timer_tick(self, evt):
    148          self._handle_event_alerts()
    149
    150 -    def _set_experimental_features(self):
    151          ExperimentalFeatures().set_active_state_on_all_features_from_config_string(self.config.experimental_features)
    152
    153 -    def _load_icon_bundle(self):
    154          bundle = wx.IconBundle()
    155          for size in ["16", "32", "48"]:
    156              iconpath = os.path.join(ICONS_DIR, "%s.png" % size)
    157              icon = wx.IconFromBitmap(wx.BitmapFromImage(wx.Image(iconpath)))
    158              bundle.AddIcon(icon)
    159          return bundle
    160
    161      # File Menu action handlers
    162 -    def _create_new_timeline(self):
    163          path = self._get_file_path()
    164          if path is not None:
    165              self.controller.open_timeline(path)
    166
    167 -    def _create_new_bosparanian_timeline(self):
    168          path = self._get_file_path()
    169          if path is not None:
    170              timetype = BosparanianTimeType()
    171              self.controller.open_timeline(path, timetype)
    172
    173 -    def _create_new_numeric_timeline(self):
    174          path = self._get_file_path()
    175          if path is not None:
    176              timetype = NumTimeType()
    177              self.controller.open_timeline(path, timetype)
    178
    179 -    def _get_file_path(self):
    180          path = None
    181          wildcard = self.timeline_wildcard_helper.wildcard_string()
    182          dialog = wx.FileDialog(self, message=_("Create Timeline"),
    183                                 wildcard=wildcard, style=wx.FD_SAVE)
    184          if dialog.ShowModal() == wx.ID_OK:
    185              path = self.timeline_wildcard_helper.get_path(dialog)
    186              if os.path.exists(path):
    187                  msg_first_part = _("The specified timeline already exists.")
    188                  msg_second_part = _("Opening timeline instead of creating new.")
    189                  wx.MessageBox("%s\n\n%s" % (msg_first_part, msg_second_part),
    190                                _("Information"),
    191                                wx.OK | wx.ICON_INFORMATION, self)
    192          dialog.Destroy()
    193          return path
    194
    195 -    def _create_new_dir_timeline(self):
    196          dialog = wx.DirDialog(self, message=_("Create Timeline"))
    197          if dialog.ShowModal() == wx.ID_OK:
    198              self.controller.open_timeline(dialog.GetPath())
    199          dialog.Destroy()
    200
    201 -    def _open_existing_timeline(self):
    202          directory = ""
    203          if self.timeline is not None:
    204              directory = os.path.dirname(self.timeline.path)
    205          wildcard = self.timeline_wildcard_helper.wildcard_string()
    206          dialog = wx.FileDialog(self, message=_("Open Timeline"),
    207                                 defaultDir=directory,
    208                                 wildcard=wildcard, style=wx.FD_OPEN)
    209          if dialog.ShowModal() == wx.ID_OK:
    210              self.controller.open_timeline(dialog.GetPath())
    211          dialog.Destroy()
    212
    213 -    def _save_as(self):
    214          new_timeline_path = self._get_new_timeline_path_from_user()
    215          self._save_timeline_to_new_path(new_timeline_path)
    216
    217 -    def _get_new_timeline_path_from_user(self):
    218          defaultDir = os.path.dirname(self.timeline.path)
    219          wildcard_helper = WildcardHelper(_("Timeline files"), ["timeline"])
    220          wildcard = wildcard_helper.wildcard_string()
    221          style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT
    222          message = _("Save Timeline As")
    223          dialog = wx.FileDialog(self, message=message, defaultDir=defaultDir,
    224                                 wildcard=wildcard, style=style)
    225          if dialog.ShowModal() == wx.ID_OK:
    226              new_timeline_path = wildcard_helper.get_path(dialog)
    227          else:
    228              new_timeline_path = None
    229          dialog.Destroy()
    230          return new_timeline_path
    231
    232 -    def _save_timeline_to_new_path(self, new_timeline_path):
    233          if new_timeline_path is not None:
    234              assert new_timeline_path.endswith(".timeline")
    235              export_db_to_timeline_xml(self.timeline, new_timeline_path)
    236              self.controller.open_timeline(new_timeline_path)
    237
    238 -    def _window_on_close(self, event):
    239          self.timer.stop()
    240          self._save_application_config()
    241          try:
    242              if self.ok_to_edit():
    243                  self.save_current_timeline_data()
    244          finally:
    245              self.edit_ends()
    246          self.Destroy()
    247
    248 -    def _save_application_config(self):
    249          self.config.set_window_size(self.GetSize())
    250          self.config.set_window_pos(self.GetPosition())
    251          self.config.window_maximized = self.IsMaximized()
    252          self.config.sidebar_width = self.main_panel.get_sidebar_width()
    253          try:
    254              self.config.write()
    255          except IOError as ex:
    256              friendly = _("Unable to write configuration file.")
    257              msg = "%s\n\n%s" % (friendly, ex_msg(ex))
    258              display_error_message(msg, self)
    259
    260 -    def save_current_timeline_data(self):
    261          if self.timeline:
    262              self.main_panel.save_view_properties(self.timeline)
    263
    264      # Timeline Menu action handlers
    265 -    def _measure_distance_between_events(self):
    266          event1, event2 = self._get_selected_events()
    267          self._display_distance(event1.distance_to(event2))
    268
    269 -    def _get_selected_events(self):
    270          event_id_1, event_id_2 = self.main_panel.get_ids_of_two_first_selected_events()
    271          event1 = self.timeline.find_event_with_id(event_id_1)
    272          event2 = self.timeline.find_event_with_id(event_id_2)
    273          return event1, event2
    274
    275 -    def _display_distance(self, distance):
    276          caption = _("Distance between selected events")
    277          if distance is None:
    278              distance_text = _("Events are overlapping or distance is 0")
    279          else:
    280              distance_text = self.timeline.get_time_type().format_delta(distance)
    281          display_information_message(caption, distance_text)
    282
    283 -    def _set_category(self):
    284          dialog = SetCategoryDialog(self, self.timeline)
    285          dialog.ShowModal()
    286          dialog.Destroy()
    287          self.main_panel.redraw_timeline()
    288
    289 -    def _set_category_to_selected_events(self):
    290          selected_event_ids = self.main_panel.get_selected_event_ids()
    291          dialog = SetCategoryDialog(self, self.timeline, selected_event_ids)
    292          dialog.ShowModal()
    293          dialog.Destroy()
    294
    295 -    def _edit_categories(self):
    296          display_categories_editor_moved_message(self)
    297
    298 -    def _edit_eras(self):
    299          dialog = ErasEditorDialog(self, self.timeline, self.config)
    300          dialog.ShowModal()
    301          dialog.Destroy()
    302          self.main_panel.redraw_timeline()
    303
    304 -    def _fit_all_events(self):
    305          all_period = self._period_for_all_visible_events()
    306          if all_period is None:
    307              return
    308          if all_period.is_period():
    309              self.main_panel.Navigate(lambda tp: tp.update(all_period.start_time, all_period.end_time))
    310          else:
    311              self.main_panel.Navigate(lambda tp: tp.center(all_period.mean_time()))
    312
    313 -    def _period_for_all_visible_events(self):
    314          try:
    315              visible_events = self._all_visible_events()
    316              if len(visible_events) > 0:
    317                  start = self._first_time(visible_events)
    318                  end = self._last_time(visible_events)
    319                  return TimePeriod(start, end).zoom(-1)
    320              else:
    321                  return None
    322          except ValueError as ex:
    323              display_error_message(ex.message)
    324          return None
    325
    326 -    def _all_visible_events(self):
    327          all_events = self.timeline.get_all_events()
    328          return self.main_panel.get_visible_events(all_events)
    329
    330 -    def _first_time(self, events):
    331          start_time = lambda event: event.get_start_time()
    332          return start_time(min(events, key=start_time))
    333
    334 -    def _last_time(self, events):
    335          end_time = lambda event: event.get_end_time()
    336          return end_time(max(events, key=end_time))
    337
    338 -    def get_export_periods(self):
    339          events = self._all_visible_events()
    340          first_time = self._first_time(events)
    341          last_time = self._last_time(events)
    342          return self.main_panel.get_export_periods(first_time, last_time)
    343
    344      # Timer event handlers
    345 -    def _handle_event_alerts(self):
    346          if self.timeline is None:
    347              return
    348          if self.alert_dialog_open:
    349              return
    350          self._display_events_alerts()
    351          self.alert_dialog_open = False
    352
    353 -    def _display_events_alerts(self):
    354          self.alert_dialog_open = True
    355          all_events = self.timeline.get_all_events()
    356          AlertController(self).display_events_alerts(all_events, self.timeline.get_time_type())
    357