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
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
118 - def _create_main_menu_bar(self):
119 main_menu_bar = wx.MenuBar() 120 self._create_file_menu(main_menu_bar) 121 self._create_edit_menu(main_menu_bar) 122 self._create_view_menu(main_menu_bar) 123 self._create_timeline_menu(main_menu_bar) 124 self._create_navigate_menu(main_menu_bar) 125 self._create_help_menu(main_menu_bar) 126 self.SetMenuBar(main_menu_bar)
127
128 - def _create_file_menu(self, main_menu_bar):
129 file_menu = wx.Menu() 130 self._create_file_new_menu(file_menu) 131 self._create_file_open_menu_item(file_menu) 132 self._create_file_open_recent_menu(file_menu) 133 file_menu.AppendSeparator() 134 self._create_file_page_setup_menu_item(file_menu) 135 self._create_file_print_preview_menu_item(file_menu) 136 self._create_file_print_menu_item(file_menu) 137 file_menu.AppendSeparator() 138 self._create_file_export_to_image_menu_item(file_menu) 139 self._create_file_export_to_svg_menu_item(file_menu) 140 file_menu.AppendSeparator() 141 if DEV: 142 self._create_file_play(file_menu) 143 file_menu.AppendSeparator() 144 self._create_file_exit_menu_item(file_menu) 145 main_menu_bar.Append(file_menu, _("&File"))
146
147 - def _create_file_new_menu(self, file_menu):
148 file_new_menu = wx.Menu() 149 self._create_file_new_timeline_menu_item(file_new_menu) 150 self._create_file_new_dir_timeline_menu_item(file_new_menu) 151 file_menu.AppendMenu(wx.ID_ANY, _("New"), file_new_menu, _("Create a new timeline"))
152
153 - def _create_file_new_timeline_menu_item(self, file_new_menu):
154 accel = wx.GetStockLabel(wx.ID_NEW, wx.STOCK_WITH_ACCELERATOR|wx.STOCK_WITH_MNEMONIC) 155 accel = accel.split("\t", 1)[1] 156 file_new_menu.Append( 157 wx.ID_NEW, _("File Timeline...") + "\t" + accel, _("File Timeline...")) 158 self.Bind(wx.EVT_MENU, self._mnu_file_new_on_click, id=wx.ID_NEW)
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
179 - def _create_file_new_dir_timeline_menu_item(self, file_new_menu):
180 mnu_file_new_dir = file_new_menu.Append( 181 wx.ID_ANY, _("Directory Timeline..."), _("Directory Timeline...")) 182 self.Bind(wx.EVT_MENU, self._mnu_file_new_dir_on_click, mnu_file_new_dir)
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
194 - def _create_file_open_menu_item(self, file_menu):
195 file_menu.Append( 196 wx.ID_OPEN, self._add_ellipses_to_menuitem(wx.ID_OPEN), 197 _("Open an existing timeline")) 198 self.Bind(wx.EVT_MENU, self._mnu_file_open_on_click, id=wx.ID_OPEN)
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
216 - def _create_file_open_recent_menu(self, file_menu):
217 self.mnu_file_open_recent_submenu = wx.Menu() 218 file_menu.AppendMenu(wx.ID_ANY, _("Open &Recent"), self.mnu_file_open_recent_submenu) 219 self._update_open_recent_submenu()
220
221 - def _create_file_page_setup_menu_item(self, file_menu):
222 mnu_file_print_setup = file_menu.Append( 223 wx.ID_PRINT_SETUP, _("Page Set&up..."), _("Setup page for printing")) 224 self.menu_controller.add_menu_requiring_timeline(mnu_file_print_setup) 225 self.Bind(wx.EVT_MENU, self._mnu_file_print_setup_on_click, id=wx.ID_PRINT_SETUP)
226
227 - def _mnu_file_print_setup_on_click(self, event):
228 printing.print_setup(self)
229
230 - def _create_file_print_preview_menu_item(self, file_menu):
231 mnu_file_print_preview = file_menu.Append( 232 wx.ID_PREVIEW, "", _("Print Preview")) 233 self.menu_controller.add_menu_requiring_timeline(mnu_file_print_preview) 234 self.Bind(wx.EVT_MENU, self._mnu_file_print_preview_on_click, id=wx.ID_PREVIEW)
235
236 - def _mnu_file_print_preview_on_click(self, event):
238
239 - def _create_file_print_menu_item(self, file_menu):
240 mnu_file_print = file_menu.Append( 241 wx.ID_PRINT, self._add_ellipses_to_menuitem(wx.ID_PRINT), _("Print")) 242 self.menu_controller.add_menu_requiring_timeline(mnu_file_print) 243 self.Bind(wx.EVT_MENU, self._mnu_file_print_on_click, id=wx.ID_PRINT)
244
245 - def _mnu_file_print_on_click(self, event):
247
248 - def _create_file_export_to_image_menu_item(self, file_menu):
249 mnu_file_export = file_menu.Append( 250 wx.ID_ANY, _("&Export to Image..."), _("Export the current view to a PNG image")) 251 self.menu_controller.add_menu_requiring_timeline(mnu_file_export) 252 self.Bind(wx.EVT_MENU, self._mnu_file_export_on_click, mnu_file_export)
253
254 - def _mnu_file_export_on_click(self, evt):
255 export_to_image(self)
256
257 - def _create_file_export_to_svg_menu_item(self, file_menu):
258 mnu_file_export_svg = file_menu.Append( 259 wx.ID_ANY, _("&Export to SVG..."), _("Export the current view to a SVG image")) 260 self.menu_controller.add_menu_requiring_timeline(mnu_file_export_svg) 261 self.Bind(wx.EVT_MENU, self._mnu_file_export_svg_on_click, mnu_file_export_svg)
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
301 - def _create_file_exit_menu_item(self, file_menu):
302 file_menu.Append(wx.ID_EXIT, "", _("Exit the program")) 303 self.Bind(wx.EVT_MENU, self._mnu_file_exit_on_click, id=wx.ID_EXIT)
304
305 - def _mnu_file_exit_on_click(self, evt):
306 self.Close()
307
308 - def _create_edit_menu(self, main_menu_bar):
309 edit_menu = wx.Menu() 310 self._create_edit_find_menu_item(edit_menu) 311 edit_menu.AppendSeparator() 312 self._create_edit_preferences_menu_item(edit_menu) 313 main_menu_bar.Append(edit_menu, _("&Edit"))
314
315 - def _create_edit_find_menu_item(self, edit_menu):
316 find_menu_item = edit_menu.Append(wx.ID_FIND) 317 self.Bind(wx.EVT_MENU, self._mnu_edit_find_on_click, find_menu_item) 318 self.menu_controller.add_menu_requiring_timeline(find_menu_item)
319
320 - def _mnu_edit_find_on_click(self, evt):
321 self.main_panel.show_searchbar(True)
322
323 - def _create_edit_preferences_menu_item(self, edit_menu):
324 preferences_item = edit_menu.Append(wx.ID_PREFERENCES) 325 self.Bind(wx.EVT_MENU, self._mnu_edit_preferences_on_click, preferences_item)
326
327 - def _mnu_edit_preferences_on_click(self, evt):
328 dialog = PreferencesDialog(self, self.config) 329 dialog.ShowModal() 330 dialog.Destroy()
331
332 - def _create_view_menu(self, main_menu_bar):
333 view_menu = wx.Menu() 334 self._create_view_sidebar_menu_item(view_menu) 335 self._create_view_legend_menu_item(view_menu) 336 view_menu.AppendSeparator() 337 self._create_view_balloons_menu_item(view_menu) 338 main_menu_bar.Append(view_menu, _("&View"))
339
340 - def _create_view_sidebar_menu_item(self, view_menu):
341 view_sidebar_item = view_menu.Append( 342 wx.ID_ANY, _("&Sidebar\tCtrl+I"), kind=wx.ITEM_CHECK) 343 self.Bind(wx.EVT_MENU, self._mnu_view_sidebar_on_click, view_sidebar_item) 344 self.menu_controller.add_menu_requiring_visible_timeline_view(view_sidebar_item) 345 view_sidebar_item.Check(self.config.get_show_sidebar())
346
347 - def _mnu_view_sidebar_on_click(self, evt):
348 self.config.set_show_sidebar(evt.IsChecked()) 349 if evt.IsChecked(): 350 self.main_panel.show_sidebar() 351 else: 352 self.main_panel.hide_sidebar()
353
354 - def _create_view_legend_menu_item(self, view_menu):
355 view_legend_item = view_menu.Append( 356 wx.ID_ANY, _("&Legend"), kind=wx.ITEM_CHECK) 357 self.Bind(wx.EVT_MENU, self._mnu_view_legend_on_click, view_legend_item) 358 self.menu_controller.add_menu_requiring_timeline(view_legend_item) 359 view_legend_item.Check(self.config.get_show_legend())
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
365 - def _create_view_balloons_menu_item(self, view_menu):
366 view_balloons_item = view_menu.Append( 367 wx.ID_ANY, _("&Balloons on hover"), kind=wx.ITEM_CHECK) 368 self.Bind(wx.EVT_MENU, self._mnu_view_balloons_on_click, view_balloons_item) 369 view_balloons_item.Check(self.config.get_balloon_on_hover())
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
375 - def _create_timeline_menu(self, main_menu_bar):
376 timeline_menu = wx.Menu() 377 self._create_timeline_create_event_menu_item(timeline_menu) 378 self._create_timeline_duplicate_event_menu_item(timeline_menu) 379 self._create_timeline_measure_distance_between_events_menu_item(timeline_menu) 380 self._create_timeline_edit_categories(timeline_menu) 381 main_menu_bar.Append(timeline_menu, _("&Timeline"))
382
383 - def _create_timeline_create_event_menu_item(self, timeline_menu):
384 create_event_item = timeline_menu.Append( 385 wx.ID_ANY, _("Create &Event..."), _("Create a new event")) 386 self.Bind(wx.EVT_MENU, self._mnu_timeline_create_event_on_click, create_event_item) 387 self.menu_controller.add_menu_requiring_writable_timeline(create_event_item)
388
390 open_create_event_editor( 391 self, self.config, self.timeline, self.handle_db_error)
392
393 - def _create_timeline_duplicate_event_menu_item(self, timeline_menu):
394 self.mnu_timeline_duplicate_event = timeline_menu.Append( 395 wx.ID_ANY, _("&Duplicate Selected Event..."), _("Duplicate the Selected Event")) 396 self.Bind(wx.EVT_MENU, self._mnu_timeline_duplicate_event_on_click, 397 self.mnu_timeline_duplicate_event) 398 self.menu_controller.add_menu_requiring_writable_timeline(self.mnu_timeline_duplicate_event)
399
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
415 self.mnu_timeline_measure_distance_between_events = timeline_menu.Append( 416 wx.ID_ANY, _("&Measure Distance between two Events..."), 417 _("Measure the Distance between two Events")) 418 self.Bind(wx.EVT_MENU, self._mnu_timeline_measure_distance_between_events_on_click, 419 self.mnu_timeline_measure_distance_between_events) 420 self.menu_controller.add_menu_requiring_writable_timeline(self.mnu_timeline_measure_distance_between_events)
421
423 self._measure_distance_between_events()
424
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
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
475 - def _create_navigate_menu(self, main_menu_bar):
476 navigate_menu = wx.Menu() 477 self._navigation_menu_items = [] 478 self._navigation_functions_by_menu_item_id = {} 479 self._update_navigation_menu_items() 480 navigate_menu.AppendSeparator() 481 self._create_navigate_find_first_event_menu_item(navigate_menu) 482 self._create_navigate_find_last_event_menu_item(navigate_menu) 483 self._create_navigate_fit_all_events_menu_item(navigate_menu) 484 main_menu_bar.Append(navigate_menu, _("&Navigate")) 485 self.mnu_navigate = navigate_menu
486
487 - def _create_navigate_find_first_event_menu_item(self, navigate_menu):
488 find_first = navigate_menu.Append(wx.ID_ANY, _("Find First Event")) 489 self.Bind(wx.EVT_MENU, self._mnu_navigate_find_first_on_click, find_first) 490 self.menu_controller.add_menu_requiring_timeline(find_first)
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
501 - def _create_navigate_find_last_event_menu_item(self, navigate_menu):
502 find_last = navigate_menu.Append(wx.ID_ANY, _("Find Last Event")) 503 self.Bind(wx.EVT_MENU, self._mnu_navigate_find_last_on_click, find_last) 504 self.menu_controller.add_menu_requiring_timeline(find_last)
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
515 - def _create_navigate_fit_all_events_menu_item(self, navigate_menu):
516 fit_all_events = navigate_menu.Append(wx.ID_ANY, _("Fit All Events")) 517 self.Bind(wx.EVT_MENU, self._mnu_navigate_fit_all_events_on_click, fit_all_events) 518 self.menu_controller.add_menu_requiring_timeline(fit_all_events)
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
561 - def _create_help_menu(self, main_menu_bar):
562 help_menu = wx.Menu() 563 self._create_help_contents_menu_item(help_menu) 564 help_menu.AppendSeparator() 565 self._create_help_tutorial_menu_item(help_menu) 566 self._create_help_contact_menu_item(help_menu) 567 help_menu.AppendSeparator() 568 self._create_help_about_menu_item(help_menu) 569 main_menu_bar.Append(help_menu, _("&Help"))
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
578 - def _create_help_tutorial_menu_item(self, help_menu):
579 tutorial_item = help_menu.Append(wx.ID_ANY, _("Getting started tutorial")) 580 self.Bind(wx.EVT_MENU, self._mnu_help_tutorial_on_click, tutorial_item)
581
582 - def _mnu_help_tutorial_on_click(self, e):
583 self.open_timeline(":tutorial:")
584
585 - def _create_help_contact_menu_item(self, help_menu):
586 contact_item = help_menu.Append(wx.ID_ANY, _("Contact")) 587 self.Bind(wx.EVT_MENU, self._mnu_help_contact_on_click, contact_item)
588
589 - def _mnu_help_contact_on_click(self, e):
590 self.help_browser.show_page("contact")
591
592 - def _create_help_about_menu_item(self, help_menu):
593 about_item = help_menu.Append(wx.ID_ABOUT) 594 self.Bind(wx.EVT_MENU, self._mnu_help_about_on_click, about_item)
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
666 - def _update_navigation_menu_items(self):
667 self._clear_navigation_menu_items() 668 if self.timeline: 669 self._create_navigation_menu_items()
670
671 - def _clear_navigation_menu_items(self):
672 while self._navigation_menu_items: 673 self.mnu_navigate.RemoveItem(self._navigation_menu_items.pop()) 674 self._navigation_functions_by_menu_item_id.clear()
675
676 - def _create_navigation_menu_items(self):
677 item_data = self.timeline.get_time_type().get_navigation_functions() 678 pos = 0 679 for (itemstr, fn) in item_data: 680 if itemstr == "SEP": 681 item = self.mnu_navigate.InsertSeparator(pos) 682 else: 683 item = self.mnu_navigate.Insert(pos, wx.ID_ANY, itemstr) 684 self._navigation_functions_by_menu_item_id[item.GetId()] = fn 685 self.Bind(wx.EVT_MENU, self._navigation_menu_item_on_click, item) 686 self._navigation_menu_items.append(item) 687 pos += 1
688
689 - def _navigation_menu_item_on_click(self, evt):
690 fn = self._navigation_functions_by_menu_item_id[evt.GetId()] 691 fn(self, self._get_time_period(), self._navigate_timeline)
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
724 - def _enable_disable_duplicate_event_menu(self):
725 view_properties = self.main_panel.drawing_area.get_view_properties() 726 one_event_selected = len(view_properties.selected_event_ids) == 1 727 self.mnu_timeline_duplicate_event.Enable(one_event_selected)
728
729 - def _enable_disable_measure_distance_between_two_events_menu(self):
730 view_properties = self.main_panel.drawing_area.get_view_properties() 731 two_events_selected = len(view_properties.selected_event_ids) == 2 732 self.mnu_timeline_measure_distance_between_events.Enable(two_events_selected)
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 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
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
1007 if self.IsShown(): 1008 self.sidebar_width = self.splitter.GetSashPosition()
1009
1010 - def _create_sidebar(self):
1011 self.sidebar = Sidebar(self.splitter, self.handle_db_error)
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
1028 - def get_sidebar_width(self):
1029 return self.sidebar_width
1030
1031 - def show_sidebar(self):
1032 self.splitter.SplitVertically( 1033 self.sidebar, self.drawing_area, self.sidebar_width) 1034 self.splitter.SetSashPosition(self.sidebar_width)
1035
1036 - def hide_sidebar(self):
1037 self.splitter.Unsplit(self.sidebar)
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