source: src/mlx/gui/gui.py@ 931:8286bd44662d

python3
Last change on this file since 931:8286bd44662d was 931:8286bd44662d, checked in by István Váradi <ivaradi@…>, 5 years ago

Setting visual for CEF (re #347).

File size: 71.6 KB
Line 
1# -*- coding: utf-8 -*-
2
3from .statusicon import StatusIcon
4from .statusbar import Statusbar
5from .info import FlightInfo
6from .update import Updater
7from mlx.gui.common import *
8from mlx.gui.flight import Wizard
9from mlx.gui.monitor import MonitorWindow
10from mlx.gui.weighthelp import WeightHelp
11from mlx.gui.gates import FleetGateStatus
12from mlx.gui.prefs import Preferences
13from mlx.gui.checklist import ChecklistEditor
14from mlx.gui.callouts import ApproachCalloutsEditor
15from mlx.gui.flightlist import AcceptedFlightsWindow
16from mlx.gui.pirep import PIREPViewer, PIREPEditor
17from mlx.gui.bugreport import BugReportDialog
18from mlx.gui.acars import ACARS
19from mlx.gui.timetable import TimetableWindow
20from . import cef
21
22import mlx.const as const
23import mlx.fs as fs
24import mlx.flight as flight
25import mlx.logger as logger
26import mlx.acft as acft
27import mlx.web as web
28import mlx.singleton as singleton
29import mlx.airports as airports
30from mlx.i18n import xstr, getLanguage
31from mlx.pirep import PIREP
32
33import time
34import threading
35import sys
36import datetime
37import webbrowser
38
39#------------------------------------------------------------------------------
40
41## @package mlx.gui.gui
42#
43# The main GUI class.
44#
45# The \ref GUI class is the main class of the GUI. It is a connection listener,
46# and aggregates all the windows, the menu, etc. It maintains the connection to
47# the simulator as well as the flight object.
48
49#------------------------------------------------------------------------------
50
51class GUI(fs.ConnectionListener):
52 """The main GUI class."""
53 _authors = [ ("Váradi", "István", "prog_test"),
54 ("Galyassy", "Tamás", "negotiation"),
55 ("Kurják", "Ákos", "test"),
56 ("Nagy", "Dániel", "test"),
57 ("Radó", "Iván", "test"),
58 ("Petrovszki", "Gábor", "test"),
59 ("Serfőző", "Tamás", "test"),
60 ("Szebenyi", "Bálint", "test"),
61 ("Zsebényi-Loksa", "Gergely", "test") ]
62
63 def __init__(self, programDirectory, config):
64 """Construct the GUI."""
65 gobject.threads_init()
66
67 self._programDirectory = programDirectory
68 self.config = config
69 self._connecting = False
70 self._reconnecting = False
71 self._connected = False
72 self._logger = logger.Logger(self)
73 self._flight = None
74 self._simulator = None
75 self._fsType = None
76 self._monitoring = False
77
78 self._fleet = None
79
80 self._fleetCallback = None
81
82 self._updatePlaneCallback = None
83 self._updatePlaneTailNumber = None
84 self._updatePlaneStatus = None
85 self._updatePlaneGateNumber = None
86
87 self._stdioLock = threading.Lock()
88 self._stdioText = ""
89 self._stdioStartingLine = True
90
91 self._sendPIREPCallback = None
92 self._sendBugReportCallback = None
93
94 self._credentialsCondition = threading.Condition()
95 self._credentialsAvailable = False
96 self._credentialsUserName = None
97 self._credentialsPassword = None
98
99 self._bookFlightsUserCallback = None
100 self._bookFlightsBusyCallback = None
101
102 self.webHandler = web.Handler(config, self._getCredentialsCallback)
103 self.webHandler.start()
104
105 self.toRestart = False
106
107 def build(self, iconDirectory):
108 """Build the GUI."""
109
110 self._mainWindow = window = gtk.Window()
111 if os.name!="nt":
112 window.set_visual(window.get_screen().lookup_visual(0x21))
113 window.set_title(WINDOW_TITLE_BASE)
114 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
115 window.set_resizable(False)
116 window.connect("delete-event", self.deleteMainWindow)
117 window.connect("window-state-event", self._handleMainWindowState)
118 if os.name=="nt":
119 window.connect("leave-notify-event", self._handleLeaveNotify)
120 accelGroup = gtk.AccelGroup()
121 window.add_accel_group(accelGroup)
122 window.realize()
123
124 mainVBox = gtk.VBox()
125 window.add(mainVBox)
126
127 self._preferences = Preferences(self)
128 self._timetableWindow = TimetableWindow(self)
129 self._timetableWindow.connect("delete-event", self._hideTimetableWindow)
130 self._flightsWindow = AcceptedFlightsWindow(self)
131 self._flightsWindow.connect("delete-event", self._hideFlightsWindow)
132 self._checklistEditor = ChecklistEditor(self)
133 self._approachCalloutsEditor = ApproachCalloutsEditor(self)
134 self._bugReportDialog = BugReportDialog(self)
135
136 menuBar = self._buildMenuBar(accelGroup)
137 mainVBox.pack_start(menuBar, False, False, 0)
138
139 self._notebook = gtk.Notebook()
140 mainVBox.pack_start(self._notebook, True, True, 4)
141
142 self._wizard = Wizard(self)
143 label = gtk.Label(xstr("tab_flight"))
144 label.set_use_underline(True)
145 label.set_tooltip_text(xstr("tab_flight_tooltip"))
146 self._notebook.append_page(self._wizard, label)
147
148 self._flightInfo = FlightInfo(self)
149 label = gtk.Label(xstr("tab_flight_info"))
150 label.set_use_underline(True)
151 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
152 self._notebook.append_page(self._flightInfo, label)
153 self._flightInfo.disable()
154
155 self._weightHelp = WeightHelp(self)
156 label = gtk.Label(xstr("tab_weight_help"))
157 label.set_use_underline(True)
158 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
159 self._notebook.append_page(self._weightHelp, label)
160
161 (logWidget, self._logView) = self._buildLogWidget()
162 addFaultTag(self._logView.get_buffer())
163 label = gtk.Label(xstr("tab_log"))
164 label.set_use_underline(True)
165 label.set_tooltip_text(xstr("tab_log_tooltip"))
166 self._notebook.append_page(logWidget, label)
167
168 self._fleetGateStatus = FleetGateStatus(self)
169 label = gtk.Label(xstr("tab_gates"))
170 label.set_use_underline(True)
171 label.set_tooltip_text(xstr("tab_gates_tooltip"))
172 self._notebook.append_page(self._fleetGateStatus, label)
173
174 self._acars = ACARS(self)
175 label = gtk.Label("ACARS")
176 label.set_use_underline(True)
177 self._notebook.append_page(self._acars, label)
178
179 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
180 self._debugLogWidget.show_all()
181
182 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
183
184 self._statusbar = Statusbar(iconDirectory)
185 mainVBox.pack_start(self._statusbar, False, False, 0)
186
187 self._notebook.connect("switch-page", self._notebookPageSwitch)
188
189 self._monitorWindow = MonitorWindow(self, iconDirectory)
190 self._monitorWindow.add_accel_group(accelGroup)
191 self._monitorWindowX = None
192 self._monitorWindowY = None
193 self._selfToggling = False
194
195 self._pirepViewer = PIREPViewer(self)
196 self._messagedPIREPViewer = PIREPViewer(self, showMessages = True)
197
198 self._pirepEditor = PIREPEditor(self)
199
200 window.show_all()
201 if os.name=="nt":
202 window.get_window().focus()
203
204 self._wizard.grabDefault()
205 self._weightHelp.reset()
206 self._weightHelp.disable()
207
208 self._statusIcon = StatusIcon(iconDirectory, self)
209
210 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
211 else gdk.WATCH)
212
213 self._loadPIREPDialog = None
214 self._lastLoadedPIREP = None
215
216 self._hotkeySetID = None
217 self._pilotHotkeyIndex = None
218 self._checklistHotkeyIndex = None
219
220 self._aboutDialog = None
221
222 @property
223 def mainWindow(self):
224 """Get the main window of the GUI."""
225 return self._mainWindow
226
227 @property
228 def logger(self):
229 """Get the logger used by us."""
230 return self._logger
231
232 @property
233 def simulator(self):
234 """Get the simulator used by us."""
235 return self._simulator
236
237 @property
238 def flight(self):
239 """Get the flight being performed."""
240 return self._flight
241
242 @property
243 def fsType(self):
244 """Get the flight simulator type."""
245 return self._fsType
246
247 @property
248 def entranceExam(self):
249 """Get whether an entrance exam is about to be taken."""
250 return self._wizard.entranceExam
251
252 @property
253 def loggedIn(self):
254 """Indicate if the user has logged in properly."""
255 return self._wizard.loggedIn
256
257 @property
258 def loginResult(self):
259 """Get the result of the login."""
260 return self._wizard.loginResult
261
262 @property
263 def bookedFlight(self):
264 """Get the booked flight selected, if any."""
265 return self._wizard.bookedFlight
266
267 @property
268 def numCrew(self):
269 """Get the number of crew members."""
270 return self._wizard.numCrew
271
272 @property
273 def numPassengers(self):
274 """Get the number of passengers."""
275 return self._wizard.numPassengers
276
277 @property
278 def bagWeight(self):
279 """Get the bag weight."""
280 return self._wizard.bagWeight
281
282 @property
283 def cargoWeight(self):
284 """Get the cargo weight."""
285 return self._wizard.cargoWeight
286
287 @property
288 def mailWeight(self):
289 """Get the mail weight."""
290 return self._wizard.mailWeight
291
292 @property
293 def zfw(self):
294 """Get Zero-Fuel Weight calculated for the current flight."""
295 return self._wizard.zfw
296
297 @property
298 def filedCruiseAltitude(self):
299 """Get cruise altitude filed for the current flight."""
300 return self._wizard.filedCruiseAltitude
301
302 @property
303 def cruiseAltitude(self):
304 """Get cruise altitude set for the current flight."""
305 return self._wizard.cruiseAltitude
306
307 @property
308 def loggableCruiseAltitude(self):
309 """Get the cruise altitude that can be logged."""
310 return self._wizard.loggableCruiseAltitude
311
312 @property
313 def route(self):
314 """Get the flight route."""
315 return self._wizard.route
316
317 @property
318 def departureMETAR(self):
319 """Get the METAR of the deprature airport."""
320 return self._wizard.departureMETAR
321
322 @property
323 def arrivalMETAR(self):
324 """Get the METAR of the deprature airport."""
325 return self._wizard.arrivalMETAR
326
327 @property
328 def departureRunway(self):
329 """Get the name of the departure runway."""
330 return self._wizard.departureRunway
331
332 @property
333 def sid(self):
334 """Get the SID."""
335 return self._wizard.sid
336
337 @property
338 def v1(self):
339 """Get the V1 speed calculated for the flight."""
340 return self._wizard.v1
341
342 @property
343 def vr(self):
344 """Get the Vr speed calculated for the flight."""
345 return self._wizard.vr
346
347 @property
348 def v2(self):
349 """Get the V2 speed calculated for the flight."""
350 return self._wizard.v2
351
352 @property
353 def derate(self):
354 """Get the derate value calculated for the flight."""
355 return self._wizard.derate
356
357 @property
358 def takeoffAntiIceOn(self):
359 """Get whether the anti-ice system was on during take-off."""
360 return self._wizard.takeoffAntiIceOn
361
362 @takeoffAntiIceOn.setter
363 def takeoffAntiIceOn(self, value):
364 """Set the anti-ice on indicator."""
365 gobject.idle_add(self._setTakeoffAntiIceOn, value)
366
367 @property
368 def rtoIndicated(self):
369 """Get whether the pilot has indicated than an RTO has occured."""
370 return self._wizard.rtoIndicated
371
372 @property
373 def arrivalRunway(self):
374 """Get the arrival runway."""
375 return self._wizard.arrivalRunway
376
377 @property
378 def star(self):
379 """Get the STAR."""
380 return self._wizard.star
381
382 @property
383 def transition(self):
384 """Get the transition."""
385 return self._wizard.transition
386
387 @property
388 def approachType(self):
389 """Get the approach type."""
390 return self._wizard.approachType
391
392 @property
393 def vref(self):
394 """Get the Vref speed calculated for the flight."""
395 return self._wizard.vref
396
397 @property
398 def landingAntiIceOn(self):
399 """Get whether the anti-ice system was on during landing."""
400 return self._wizard.landingAntiIceOn
401
402 @landingAntiIceOn.setter
403 def landingAntiIceOn(self, value):
404 """Set the anti-ice on indicator."""
405 gobject.idle_add(self._setLandingAntiIceOn, value)
406
407 @property
408 def flightType(self):
409 """Get the flight type."""
410 return self._wizard.flightType
411
412 @property
413 def online(self):
414 """Get whether the flight was online or not."""
415 return self._wizard.online
416
417 @property
418 def comments(self):
419 """Get the comments."""
420 return self._flightInfo.comments
421
422 @property
423 def hasComments(self):
424 """Indicate whether there is a comment."""
425 return self._flightInfo.hasComments
426
427 @property
428 def flightDefects(self):
429 """Get the flight defects."""
430 return self._flightInfo.faultsAndExplanations
431
432 @property
433 def delayCodes(self):
434 """Get the delay codes."""
435 return self._flightInfo.delayCodes
436
437 @property
438 def hasDelayCode(self):
439 """Determine if there is at least one delay code selected."""
440 return self._flightInfo.hasDelayCode
441
442 @property
443 def faultsFullyExplained(self):
444 """Determine if all the faults have been fully explained by the
445 user."""
446 return self._flightInfo.faultsFullyExplained
447
448 if pygobject:
449 @property
450 def backgroundColour(self):
451 """Get the background colour of the main window."""
452 return self._mainWindow.get_style_context().\
453 get_background_color(gtk.StateFlags.NORMAL)
454
455 def run(self):
456 """Run the GUI."""
457 if self.config.autoUpdate:
458 self._updater = Updater(self,
459 self._programDirectory,
460 self.config.updateURL,
461 self._mainWindow)
462 self._updater.start()
463 else:
464 self.updateDone()
465
466 cef.initialize(self._cefInitialized)
467
468 singleton.raiseCallback = self.raiseCallback
469 gtk.main()
470 singleton.raiseCallback = None
471
472 cef.finalize()
473
474 self._disconnect()
475
476 def updateDone(self):
477 """Called when the update is done (and there is no need to restart)."""
478 gobject.idle_add(self._updateDone)
479
480 def connected(self, fsType, descriptor):
481 """Called when we have connected to the simulator."""
482 self._connected = True
483 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
484 (const.VERSION, descriptor))
485 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
486 "Welcome to MAVA Logger X " + const.VERSION)
487 gobject.idle_add(self._handleConnected, fsType, descriptor)
488
489 def _handleConnected(self, fsType, descriptor):
490 """Called when the connection to the simulator has succeeded."""
491 self._statusbar.updateConnection(self._connecting, self._connected)
492 self.endBusy()
493 if not self._reconnecting:
494 self._wizard.connected(fsType, descriptor)
495 self._reconnecting = False
496 self._fsType = fsType
497 self._listenHotkeys()
498
499 def connectionFailed(self):
500 """Called when the connection failed."""
501 self._logger.untimedMessage("Connection to the simulator failed")
502 gobject.idle_add(self._connectionFailed)
503
504 def _connectionFailed(self):
505 """Called when the connection failed."""
506 self.endBusy()
507 self._statusbar.updateConnection(self._connecting, self._connected)
508
509 dialog = gtk.MessageDialog(parent = self._mainWindow,
510 type = MESSAGETYPE_ERROR,
511 message_format = xstr("conn_failed"))
512
513 dialog.set_title(WINDOW_TITLE_BASE)
514 dialog.format_secondary_markup(xstr("conn_failed_sec"))
515
516 dialog.add_button(xstr("button_cancel"), 0)
517 dialog.add_button(xstr("button_tryagain"), 1)
518 dialog.set_default_response(1)
519
520 result = dialog.run()
521 dialog.hide()
522 if result == 1:
523 self.beginBusy(xstr("connect_busy"))
524 self._simulator.reconnect()
525 else:
526 self.reset()
527
528 def disconnected(self):
529 """Called when we have disconnected from the simulator."""
530 self._connected = False
531 self._logger.untimedMessage("Disconnected from the simulator")
532 if self._flight is not None:
533 self._flight.disconnected()
534
535 gobject.idle_add(self._disconnected)
536
537 def _disconnected(self):
538 """Called when we have disconnected from the simulator unexpectedly."""
539 self._statusbar.updateConnection(self._connecting, self._connected)
540
541 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
542 message_format = xstr("conn_broken"),
543 parent = self._mainWindow)
544 dialog.set_title(WINDOW_TITLE_BASE)
545 dialog.format_secondary_markup(xstr("conn_broken_sec"))
546
547 dialog.add_button(xstr("button_cancel"), 0)
548 dialog.add_button(xstr("button_reconnect"), 1)
549 dialog.set_default_response(1)
550
551 result = dialog.run()
552 dialog.hide()
553 if result == 1:
554 self.beginBusy(xstr("connect_busy"))
555 self._reconnecting = True
556 self._simulator.reconnect()
557 else:
558 self.reset()
559
560 def enableFlightInfo(self, aircraftType):
561 """Enable the flight info tab."""
562 self._flightInfo.enable(aircraftType)
563
564 def bookFlights(self, callback, flightIDs, date, tailNumber,
565 busyCallback = None):
566 """Initiate the booking of flights with the given timetable IDs and
567 other data"""
568 self._bookFlightsUserCallback = callback
569 self._bookFlightsBusyCallback = busyCallback
570
571 self.beginBusy(xstr("bookflights_busy"))
572 if busyCallback is not None:
573 busyCallback(True)
574
575 self.webHandler.bookFlights(self._bookFlightsCallback,
576 flightIDs, date, tailNumber)
577
578 def _bookFlightsCallback(self, returned, result):
579 """Called when the booking of flights has finished."""
580 gobject.idle_add(self._handleBookFlightsResult, returned, result)
581
582 def _handleBookFlightsResult(self, returned, result):
583 """Called when the booking of flights is done.
584
585 If it was successful, the booked flights are added to the list of the
586 flight selector."""
587 if self._bookFlightsBusyCallback is not None:
588 self._bookFlightsBusyCallback(False)
589 self.endBusy()
590
591 if returned:
592 for bookedFlight in result.bookedFlights:
593 self._wizard.addFlight(bookedFlight)
594
595 self._bookFlightsUserCallback(returned, result)
596
597 def cancelFlight(self):
598 """Cancel the current file, if the user confirms it."""
599 dialog = gtk.MessageDialog(parent = self._mainWindow,
600 type = MESSAGETYPE_QUESTION,
601 message_format = xstr("cancelFlight_question"))
602
603 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
604 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
605
606 dialog.set_title(WINDOW_TITLE_BASE)
607 result = dialog.run()
608 dialog.hide()
609
610 if result==RESPONSETYPE_YES:
611 self.reset()
612
613 def reset(self):
614 """Reset the GUI."""
615 self._disconnect()
616
617 self._simulator = None
618
619 self._flightInfo.reset()
620 self._flightInfo.disable()
621 self.resetFlightStatus()
622
623 self._weightHelp.reset()
624 self._weightHelp.disable()
625 self._notebook.set_current_page(0)
626
627 self._logView.get_buffer().set_text("")
628
629 if self.loggedIn:
630 self._wizard.cancelFlight(self._handleReloadResult)
631 else:
632 self._wizard.reset(None)
633
634 def _handleReloadResult(self, returned, result):
635 """Handle the result of the reloading of the flights."""
636 self._wizard.reset(result if returned and result.loggedIn else None)
637
638 def _disconnect(self, closingMessage = None, duration = 3):
639 """Disconnect from the simulator if connected."""
640 self.stopMonitoring()
641 self._clearHotkeys()
642
643 if self._connected:
644 if closingMessage is None:
645 self._flight.simulator.disconnect()
646 else:
647 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
648 closingMessage, duration,
649 disconnect = True)
650 self._connected = False
651
652 self._connecting = False
653 self._reconnecting = False
654 self._statusbar.updateConnection(False, False)
655 self._weightHelp.disable()
656
657 return True
658
659 def insertFlightLogLine(self, index, timestampString, text, isFault):
660 """Insert the flight log line with the given data."""
661 gobject.idle_add(self._insertFlightLogLine, index,
662 formatFlightLogLine(timestampString, text),
663 isFault)
664
665 def _insertFlightLogLine(self, index, line, isFault):
666 """Perform the real insertion.
667
668 To be called from the event loop."""
669 buffer = self._logView.get_buffer()
670 lineIter = buffer.get_iter_at_line(index)
671 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
672 self._logView.scroll_mark_onscreen(buffer.get_insert())
673
674 def removeFlightLogLine(self, index):
675 """Remove the flight log line with the given index."""
676 gobject.idle_add(self._removeFlightLogLine, index)
677
678 def addFault(self, id, timestampString, text):
679 """Add a fault to the list of faults."""
680 faultText = formatFlightLogLine(timestampString, text).strip()
681 gobject.idle_add(self._flightInfo.addFault, id, faultText)
682
683 def updateFault(self, id, timestampString, text):
684 """Update a fault in the list of faults."""
685 faultText = formatFlightLogLine(timestampString, text).strip()
686 gobject.idle_add(self._flightInfo.updateFault, id, faultText)
687
688 def clearFault(self, id):
689 """Clear a fault in the list of faults."""
690 gobject.idle_add(self._flightInfo.clearFault, id)
691
692 def _removeFlightLogLine(self, index):
693 """Perform the real removal."""
694 buffer = self._logView.get_buffer()
695 startIter = buffer.get_iter_at_line(index)
696 endIter = buffer.get_iter_at_line(index+1)
697 buffer.delete(startIter, endIter)
698 self._logView.scroll_mark_onscreen(buffer.get_insert())
699
700 def check(self, flight, aircraft, logger, oldState, state):
701 """Update the data."""
702 gobject.idle_add(self._monitorWindow.setData, state)
703 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
704
705 def resetFlightStatus(self):
706 """Reset the status of the flight."""
707 self._statusbar.resetFlightStatus()
708 self._statusbar.updateTime()
709 self._statusIcon.resetFlightStatus()
710
711 def setStage(self, stage):
712 """Set the stage of the flight."""
713 gobject.idle_add(self._setStage, stage)
714
715 def _setStage(self, stage):
716 """Set the stage of the flight."""
717 self._statusbar.setStage(stage)
718 self._statusIcon.setStage(stage)
719 self._wizard.setStage(stage)
720 if stage==const.STAGE_END:
721 welcomeMessage = \
722 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
723 self._disconnect(closingMessage =
724 "Flight plan closed. " + welcomeMessage,
725 duration = 5)
726
727 def setRating(self, rating):
728 """Set the rating of the flight."""
729 gobject.idle_add(self._setRating, rating)
730
731 def _setRating(self, rating):
732 """Set the rating of the flight."""
733 self._statusbar.setRating(rating)
734 self._statusIcon.setRating(rating)
735
736 def setNoGo(self, reason):
737 """Set the rating of the flight to No-Go with the given reason."""
738 gobject.idle_add(self._setNoGo, reason)
739
740 def _setNoGo(self, reason):
741 """Set the rating of the flight."""
742 self._statusbar.setNoGo(reason)
743 self._statusIcon.setNoGo(reason)
744
745 def _handleMainWindowState(self, window, event):
746 """Hande a change in the state of the window"""
747 iconified = gdk.WindowState.ICONIFIED if pygobject \
748 else gdk.WINDOW_STATE_ICONIFIED
749
750 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
751 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
752 self._statusIcon.mainWindowHidden()
753 else:
754 self._statusIcon.mainWindowShown()
755
756 if self.config.hideMinimizedWindow and not pygobject and \
757 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
758 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
759 self.hideMainWindow(savePosition = False)
760 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
761 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
762 self._mainWindow.present()
763
764 def _handleLeaveNotify(self, widget, event):
765 """Handle the leave-notify event.
766
767 Here we reset the focus to the main window as CEF might have acquired
768 it earlier."""
769 self._mainWindow.get_window().focus()
770
771 def raiseCallback(self):
772 """Callback for the singleton handling code."""
773 gobject.idle_add(self.raiseMainWindow)
774
775 def raiseMainWindow(self):
776 """Show the main window if invisible, and raise it."""
777 if not self._mainWindow.get_visible():
778 self.showMainWindow()
779 self._mainWindow.present()
780
781 def deleteMainWindow(self, window, event):
782 """Handle the delete event for the main window."""
783 if self.config.quitOnClose:
784 self._quit()
785 else:
786 self.hideMainWindow()
787 return True
788
789 def hideMainWindow(self, savePosition = True):
790 """Hide the main window and save its position."""
791 if savePosition:
792 (self._mainWindowX, self._mainWindowY) = \
793 self._mainWindow.get_window().get_root_origin()
794 else:
795 self._mainWindowX = self._mainWindowY = None
796 self._mainWindow.hide()
797 return True
798
799 def showMainWindow(self):
800 """Show the main window at its former position."""
801 if self._mainWindowX is not None and self._mainWindowY is not None:
802 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
803
804 if pygobject:
805 self._mainWindow.show()
806 else:
807 self._mainWindow.present()
808 self._mainWindow.deiconify()
809
810 def toggleMainWindow(self):
811 """Toggle the main window."""
812 if self._mainWindow.get_visible():
813 self.hideMainWindow()
814 else:
815 self.showMainWindow()
816
817 def hideMonitorWindow(self, savePosition = True):
818 """Hide the monitor window."""
819 if savePosition:
820 (self._monitorWindowX, self._monitorWindowY) = \
821 self._monitorWindow.get_window().get_root_origin()
822 else:
823 self._monitorWindowX = self._monitorWindowY = None
824 self._monitorWindow.hide()
825 self._statusIcon.monitorWindowHidden()
826 if self._showMonitorMenuItem.get_active():
827 self._selfToggling = True
828 self._showMonitorMenuItem.set_active(False)
829 return True
830
831 def showMonitorWindow(self):
832 """Show the monitor window."""
833 if self._monitorWindowX is not None and self._monitorWindowY is not None:
834 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
835 self._monitorWindow.show_all()
836 self._statusIcon.monitorWindowShown()
837 if not self._showMonitorMenuItem.get_active():
838 self._selfToggling = True
839 self._showMonitorMenuItem.set_active(True)
840
841 def _toggleMonitorWindow(self, menuItem):
842 if self._selfToggling:
843 self._selfToggling = False
844 elif self._monitorWindow.get_visible():
845 self.hideMonitorWindow()
846 else:
847 self.showMonitorWindow()
848
849 def restart(self):
850 """Quit and restart the application."""
851 self.toRestart = True
852 self._quit(force = True)
853
854 def flushStdIO(self):
855 """Flush any text to the standard error that could not be logged."""
856 if self._stdioText:
857 sys.__stderr__.write(self._stdioText)
858
859 def writeStdIO(self, text):
860 """Write the given text into standard I/O log."""
861 with self._stdioLock:
862 self._stdioText += text
863
864 gobject.idle_add(self._writeStdIO)
865
866 def beginBusy(self, message):
867 """Begin a period of background processing."""
868 self._wizard.set_sensitive(False)
869 self._weightHelp.set_sensitive(False)
870 self._mainWindow.get_window().set_cursor(self._busyCursor)
871 self._statusbar.updateBusyState(message)
872
873 def updateBusyState(self, message):
874 """Update the busy state."""
875 self._statusbar.updateBusyState(message)
876
877 def endBusy(self):
878 """End a period of background processing."""
879 self._mainWindow.get_window().set_cursor(None)
880 self._weightHelp.set_sensitive(True)
881 self._wizard.set_sensitive(True)
882 self._statusbar.updateBusyState(None)
883
884 def initializeWeightHelp(self):
885 """Initialize the weight help tab."""
886 self._weightHelp.reset()
887 self._weightHelp.enable()
888
889 def getFleetAsync(self, callback = None, force = None):
890 """Get the fleet asynchronously."""
891 gobject.idle_add(self.getFleet, callback, force)
892
893 def getFleet(self, callback = None, force = False, busyCallback = None):
894 """Get the fleet.
895
896 If force is False, and we already have a fleet retrieved,
897 that one will be used."""
898 if self._fleet is None or force:
899 self._fleetCallback = callback
900 self._fleetBusyCallback = busyCallback
901 if busyCallback is not None:
902 busyCallback(True)
903 self.beginBusy(xstr("fleet_busy"))
904 self.webHandler.getFleet(self._fleetResultCallback)
905 else:
906 callback(self._fleet)
907
908 def commentsChanged(self):
909 """Indicate that the comments have changed."""
910 self._wizard.commentsChanged()
911
912 def delayCodesChanged(self):
913 """Called when the delay codes have changed."""
914 self._wizard.delayCodesChanged()
915
916 def faultExplanationsChanged(self):
917 """Called when the status of the explanations of the faults have
918 changed."""
919 self._wizard.faultExplanationsChanged()
920
921 def updateRTO(self, inLoop = False):
922 """Indicate that the RTO state should be updated."""
923 if inLoop:
924 self._wizard.updateRTO()
925 else:
926 gobject.idle_add(self.updateRTO, True)
927
928 def rtoToggled(self, indicated):
929 """Called when the user has toggled the RTO checkbox."""
930 self._flight.rtoToggled(indicated)
931
932 def _fleetResultCallback(self, returned, result):
933 """Called when the fleet has been queried."""
934 gobject.idle_add(self._handleFleetResult, returned, result)
935
936 def _handleFleetResult(self, returned, result):
937 """Handle the fleet result."""
938 self.endBusy()
939 if self._fleetBusyCallback is not None:
940 self._fleetBusyCallback(False)
941 if returned:
942 self._fleet = result.fleet
943 else:
944 self._fleet = None
945
946 dialog = gtk.MessageDialog(parent = self.mainWindow,
947 type = MESSAGETYPE_ERROR,
948 message_format = xstr("fleet_failed"))
949 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
950 dialog.set_title(WINDOW_TITLE_BASE)
951 dialog.run()
952 dialog.hide()
953
954 callback = self._fleetCallback
955 self._fleetCallback = None
956 self._fleetBusyCallback = None
957 if callback is not None:
958 callback(self._fleet)
959 self._fleetGateStatus.handleFleet(self._fleet)
960
961 def updatePlane(self, tailNumber, status,
962 gateNumber = None, callback = None):
963 """Update the status of the given plane."""
964 self.beginBusy(xstr("fleet_update_busy"))
965
966 self._updatePlaneCallback = callback
967
968 self._updatePlaneTailNumber = tailNumber
969 self._updatePlaneStatus = status
970 self._updatePlaneGateNumber = gateNumber
971
972 self.webHandler.updatePlane(self._updatePlaneResultCallback,
973 tailNumber, status, gateNumber)
974
975 def _updatePlaneResultCallback(self, returned, result):
976 """Called when the status of a plane has been updated."""
977 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
978
979 def _handleUpdatePlaneResult(self, returned, result):
980 """Handle the plane update result."""
981 self.endBusy()
982 if returned:
983 success = result.success
984 if success:
985 if self._fleet is not None:
986 self._fleet.updatePlane(self._updatePlaneTailNumber,
987 self._updatePlaneStatus,
988 self._updatePlaneGateNumber)
989 self._fleetGateStatus.handleFleet(self._fleet)
990 else:
991 dialog = gtk.MessageDialog(parent = self.mainWindow,
992 type = MESSAGETYPE_ERROR,
993 message_format = xstr("fleet_update_failed"))
994 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
995 dialog.set_title(WINDOW_TITLE_BASE)
996 dialog.run()
997 dialog.hide()
998
999 success = None
1000
1001 callback = self._updatePlaneCallback
1002 self._updatePlaneCallback = None
1003 if callback is not None:
1004 callback(success)
1005
1006 def _writeStdIO(self):
1007 """Perform the real writing."""
1008 with self._stdioLock:
1009 text = self._stdioText
1010 self._stdioText = ""
1011 if not text: return
1012
1013 lines = text.splitlines()
1014 if text[-1]=="\n":
1015 text = ""
1016 else:
1017 text = lines[-1]
1018 lines = lines[:-1]
1019
1020 now = datetime.datetime.now()
1021 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
1022
1023 for line in lines:
1024 #print >> sys.__stdout__, line
1025 if self._stdioStartingLine:
1026 self._writeLog(timeStr, self._debugLogView)
1027 self._writeLog(line + "\n", self._debugLogView)
1028 self._stdioStartingLine = True
1029
1030 if text:
1031 #print >> sys.__stdout__, text,
1032 if self._stdioStartingLine:
1033 self._writeLog(timeStr, self._debugLogView)
1034 self._writeLog(text, self._debugLogView)
1035 self._stdioStartingLine = False
1036
1037 def connectSimulator(self, bookedFlight, simulatorType):
1038 """Connect to the simulator for the first time."""
1039 self._logger.reset()
1040
1041 self._flight = flight.Flight(self._logger, self)
1042 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
1043 self._flight.aircraftType = bookedFlight.aircraftType
1044 self._flight.aircraft = acft.Aircraft.create(self._flight, bookedFlight)
1045 self._flight.aircraft._checkers.append(self)
1046
1047 if self._simulator is None:
1048 self._simulator = fs.createSimulator(simulatorType, self)
1049 fs.setupMessageSending(self.config, self._simulator)
1050 self._setupTimeSync()
1051
1052 self._flight.simulator = self._simulator
1053
1054 self.beginBusy(xstr("connect_busy"))
1055 self._statusbar.updateConnection(self._connecting, self._connected)
1056
1057 self._connecting = True
1058 self._simulator.connect(self._flight.aircraft)
1059
1060 def startMonitoring(self):
1061 """Start monitoring."""
1062 if not self._monitoring:
1063 self.simulator.startMonitoring()
1064 self._monitoring = True
1065
1066 def stopMonitoring(self):
1067 """Stop monitoring."""
1068 if self._monitoring:
1069 self.simulator.stopMonitoring()
1070 self._monitoring = False
1071
1072 def cruiseLevelChanged(self):
1073 """Called when the cruise level is changed in the flight wizard."""
1074 if self._flight is not None:
1075 return self._flight.cruiseLevelChanged()
1076 else:
1077 return False
1078
1079 def _buildMenuBar(self, accelGroup):
1080 """Build the main menu bar."""
1081 menuBar = gtk.MenuBar()
1082
1083 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
1084 fileMenu = gtk.Menu()
1085 fileMenuItem.set_submenu(fileMenu)
1086 menuBar.append(fileMenuItem)
1087
1088 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
1089 loadPIREPMenuItem.set_use_stock(True)
1090 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
1091 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
1092 ord(xstr("menu_file_loadPIREP_key")),
1093 CONTROL_MASK, ACCEL_VISIBLE)
1094 loadPIREPMenuItem.connect("activate", self._loadPIREP)
1095 fileMenu.append(loadPIREPMenuItem)
1096
1097 fileMenu.append(gtk.SeparatorMenuItem())
1098
1099 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
1100 quitMenuItem.set_use_stock(True)
1101 quitMenuItem.set_label(xstr("menu_file_quit"))
1102 quitMenuItem.add_accelerator("activate", accelGroup,
1103 ord(xstr("menu_file_quit_key")),
1104 CONTROL_MASK, ACCEL_VISIBLE)
1105 quitMenuItem.connect("activate", self._quit)
1106 fileMenu.append(quitMenuItem)
1107
1108 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
1109 toolsMenu = gtk.Menu()
1110 toolsMenuItem.set_submenu(toolsMenu)
1111 menuBar.append(toolsMenuItem)
1112
1113 self._timetableMenuItem = timetableMenuItem = \
1114 gtk.ImageMenuItem(gtk.STOCK_INDENT)
1115 timetableMenuItem.set_use_stock(True)
1116 timetableMenuItem.set_label(xstr("menu_tools_timetable"))
1117 timetableMenuItem.add_accelerator("activate", accelGroup,
1118 ord(xstr("menu_tools_timetable_key")),
1119 CONTROL_MASK, ACCEL_VISIBLE)
1120 timetableMenuItem.connect("activate", self.showTimetable)
1121 self._timetableMenuItem.set_sensitive(False)
1122 toolsMenu.append(timetableMenuItem)
1123
1124 self._flightsMenuItem = flightsMenuItem = \
1125 gtk.ImageMenuItem(gtk.STOCK_SPELL_CHECK)
1126 flightsMenuItem.set_use_stock(True)
1127 flightsMenuItem.set_label(xstr("menu_tools_flights"))
1128 flightsMenuItem.add_accelerator("activate", accelGroup,
1129 ord(xstr("menu_tools_flights_key")),
1130 CONTROL_MASK, ACCEL_VISIBLE)
1131 flightsMenuItem.connect("activate", self.showFlights)
1132 self._flightsMenuItem.set_sensitive(False)
1133 toolsMenu.append(flightsMenuItem)
1134
1135 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
1136 checklistMenuItem.set_use_stock(True)
1137 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
1138 checklistMenuItem.add_accelerator("activate", accelGroup,
1139 ord(xstr("menu_tools_chklst_key")),
1140 CONTROL_MASK, ACCEL_VISIBLE)
1141 checklistMenuItem.connect("activate", self._editChecklist)
1142 toolsMenu.append(checklistMenuItem)
1143
1144 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
1145 approachCalloutsMenuItem.set_use_stock(True)
1146 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
1147 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
1148 ord(xstr("menu_tools_callouts_key")),
1149 CONTROL_MASK, ACCEL_VISIBLE)
1150 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
1151 toolsMenu.append(approachCalloutsMenuItem)
1152
1153 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
1154 prefsMenuItem.set_use_stock(True)
1155 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
1156 prefsMenuItem.add_accelerator("activate", accelGroup,
1157 ord(xstr("menu_tools_prefs_key")),
1158 CONTROL_MASK, ACCEL_VISIBLE)
1159 prefsMenuItem.connect("activate", self._editPreferences)
1160 toolsMenu.append(prefsMenuItem)
1161
1162 toolsMenu.append(gtk.SeparatorMenuItem())
1163
1164 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
1165 bugReportMenuItem.set_use_stock(True)
1166 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
1167 bugReportMenuItem.add_accelerator("activate", accelGroup,
1168 ord(xstr("menu_tools_bugreport_key")),
1169 CONTROL_MASK, ACCEL_VISIBLE)
1170 bugReportMenuItem.connect("activate", self._reportBug)
1171 toolsMenu.append(bugReportMenuItem)
1172
1173 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
1174 viewMenu = gtk.Menu()
1175 viewMenuItem.set_submenu(viewMenu)
1176 menuBar.append(viewMenuItem)
1177
1178 self._showMonitorMenuItem = gtk.CheckMenuItem()
1179 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
1180 self._showMonitorMenuItem.set_use_underline(True)
1181 self._showMonitorMenuItem.set_active(False)
1182 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
1183 ord(xstr("menu_view_monitor_key")),
1184 CONTROL_MASK, ACCEL_VISIBLE)
1185 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1186 viewMenu.append(self._showMonitorMenuItem)
1187
1188 showDebugMenuItem = gtk.CheckMenuItem()
1189 showDebugMenuItem.set_label(xstr("menu_view_debug"))
1190 showDebugMenuItem.set_use_underline(True)
1191 showDebugMenuItem.set_active(False)
1192 showDebugMenuItem.add_accelerator("activate", accelGroup,
1193 ord(xstr("menu_view_debug_key")),
1194 CONTROL_MASK, ACCEL_VISIBLE)
1195 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1196 viewMenu.append(showDebugMenuItem)
1197
1198 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1199 helpMenu = gtk.Menu()
1200 helpMenuItem.set_submenu(helpMenu)
1201 menuBar.append(helpMenuItem)
1202
1203 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1204 manualMenuItem.set_use_stock(True)
1205 manualMenuItem.set_label(xstr("menu_help_manual"))
1206 manualMenuItem.add_accelerator("activate", accelGroup,
1207 ord(xstr("menu_help_manual_key")),
1208 CONTROL_MASK, ACCEL_VISIBLE)
1209 manualMenuItem.connect("activate", self._showManual)
1210 helpMenu.append(manualMenuItem)
1211
1212 helpMenu.append(gtk.SeparatorMenuItem())
1213
1214 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1215 aboutMenuItem.set_use_stock(True)
1216 aboutMenuItem.set_label(xstr("menu_help_about"))
1217 aboutMenuItem.add_accelerator("activate", accelGroup,
1218 ord(xstr("menu_help_about_key")),
1219 CONTROL_MASK, ACCEL_VISIBLE)
1220 aboutMenuItem.connect("activate", self._showAbout)
1221 helpMenu.append(aboutMenuItem)
1222
1223 return menuBar
1224
1225 def _toggleDebugLog(self, menuItem):
1226 """Toggle the debug log."""
1227 if menuItem.get_active():
1228 label = gtk.Label(xstr("tab_debug_log"))
1229 label.set_use_underline(True)
1230 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
1231 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1232 self._notebook.set_current_page(self._debugLogPage)
1233 else:
1234 self._notebook.remove_page(self._debugLogPage)
1235
1236 def _buildLogWidget(self):
1237 """Build the widget for the log."""
1238 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1239
1240 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1241 padding_left = 16, padding_right = 16)
1242
1243 logScroller = gtk.ScrolledWindow()
1244 # FIXME: these should be constants in common
1245 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1246 else gtk.POLICY_AUTOMATIC,
1247 gtk.PolicyType.AUTOMATIC if pygobject
1248 else gtk.POLICY_AUTOMATIC)
1249 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1250 else gtk.SHADOW_IN)
1251 logView = gtk.TextView()
1252 logView.set_editable(False)
1253 logView.set_cursor_visible(False)
1254 logScroller.add(logView)
1255
1256 logBox = gtk.VBox()
1257 logBox.pack_start(logScroller, True, True, 0)
1258 logBox.set_size_request(-1, 200)
1259
1260 alignment.add(logBox)
1261
1262 return (alignment, logView)
1263
1264 def _writeLog(self, msg, logView, isFault = False):
1265 """Write the given message to the log."""
1266 buffer = logView.get_buffer()
1267 appendTextBuffer(buffer, msg, isFault = isFault)
1268 logView.scroll_mark_onscreen(buffer.get_insert())
1269
1270 def _quit(self, what = None, force = False):
1271 """Quit from the application."""
1272 if force:
1273 result=RESPONSETYPE_YES
1274 else:
1275 dialog = gtk.MessageDialog(parent = self._mainWindow,
1276 type = MESSAGETYPE_QUESTION,
1277 message_format = xstr("quit_question"))
1278
1279 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1280 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1281
1282 dialog.set_title(WINDOW_TITLE_BASE)
1283 result = dialog.run()
1284 dialog.hide()
1285
1286 if result==RESPONSETYPE_YES:
1287 self._statusIcon.destroy()
1288 return gtk.main_quit()
1289
1290 def _notebookPageSwitch(self, notebook, page, page_num):
1291 """Called when the current page of the notebook has changed."""
1292 if page_num==0:
1293 gobject.idle_add(self._wizard.grabDefault)
1294 else:
1295 self._mainWindow.set_default(None)
1296
1297 def loginSuccessful(self):
1298 """Called when the login is successful."""
1299 self._flightsMenuItem.set_sensitive(True)
1300 self._timetableMenuItem.set_sensitive(True)
1301
1302 def isWizardActive(self):
1303 """Determine if the flight wizard is active."""
1304 return self._notebook.get_current_page()==0
1305
1306 def showTimetable(self, menuItem = None):
1307 """Callback for showing the timetable."""
1308 if self._timetableWindow.hasFlightPairs:
1309 self._timetableWindow.show_all()
1310 else:
1311 date = datetime.date.today()
1312 self._timetableWindow.setTypes(self.loginResult.types)
1313 self._timetableWindow.setDate(date)
1314 self.updateTimeTable(date)
1315 self.beginBusy(xstr("timetable_query_busy"))
1316
1317 def updateTimeTable(self, date):
1318 """Update the time table for the given date."""
1319 self.beginBusy(xstr("timetable_query_busy"))
1320 self._timetableWindow.set_sensitive(False)
1321 window = self._timetableWindow.get_window()
1322 if window is not None:
1323 window.set_cursor(self._busyCursor)
1324 self.webHandler.getTimetable(self._timetableCallback, date,
1325 self.loginResult.types)
1326
1327 def _timetableCallback(self, returned, result):
1328 """Called when the timetable has been received."""
1329 gobject.idle_add(self._handleTimetable, returned, result)
1330
1331 def _handleTimetable(self, returned, result):
1332 """Handle the result of the query for the timetable."""
1333 self.endBusy()
1334 window = self._timetableWindow.get_window()
1335 if window is not None:
1336 window.set_cursor(None)
1337 self._timetableWindow.set_sensitive(True)
1338 if returned:
1339 self._timetableWindow.setFlightPairs(result.flightPairs)
1340 self._timetableWindow.show_all()
1341 else:
1342 dialog = gtk.MessageDialog(parent = self.mainWindow,
1343 type = MESSAGETYPE_ERROR,
1344 message_format = xstr("timetable_failed"))
1345 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1346 dialog.set_title(WINDOW_TITLE_BASE)
1347 dialog.run()
1348 dialog.hide()
1349 self._timetableWindow.clear()
1350
1351 def showFlights(self, menuItem):
1352 """Callback for showing the flight list."""
1353 if self._flightsWindow.hasFlights:
1354 self._flightsWindow.show_all()
1355 else:
1356 self.beginBusy(xstr("acceptedflt_query_busy"))
1357 self.webHandler.getAcceptedFlights(self._acceptedFlightsCallback)
1358
1359 def _acceptedFlightsCallback(self, returned, result):
1360 """Called when the accepted flights have been received."""
1361 gobject.idle_add(self._handleAcceptedFlights, returned, result)
1362
1363 def _handleAcceptedFlights(self, returned, result):
1364 """Handle the result of the query for accepted flights."""
1365 self.endBusy()
1366 if returned:
1367 self._flightsWindow.clear()
1368 for flight in result.flights:
1369 self._flightsWindow.addFlight(flight)
1370 self._flightsWindow.show_all()
1371 else:
1372 dialog = gtk.MessageDialog(parent = self.mainWindow,
1373 type = MESSAGETYPE_ERROR,
1374 message_format = xstr("acceptedflt_failed"))
1375 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1376 dialog.set_title(WINDOW_TITLE_BASE)
1377 dialog.run()
1378 dialog.hide()
1379
1380 def _hideTimetableWindow(self, window, event):
1381 """Hide the window of the timetable."""
1382 self._timetableWindow.hide()
1383 return True
1384
1385 def _hideFlightsWindow(self, window, event):
1386 """Hide the window of the accepted flights."""
1387 self._flightsWindow.hide()
1388 return True
1389
1390 def _editChecklist(self, menuItem):
1391 """Callback for editing the checklists."""
1392 self._checklistEditor.run()
1393
1394 def _editApproachCallouts(self, menuItem):
1395 """Callback for editing the approach callouts."""
1396 self._approachCalloutsEditor.run()
1397
1398 def _editPreferences(self, menuItem):
1399 """Callback for editing the preferences."""
1400 self._clearHotkeys()
1401 self._preferences.run(self.config)
1402 self._setupTimeSync()
1403 self._listenHotkeys()
1404
1405 def _reportBug(self, menuItem):
1406 """Callback for reporting a bug."""
1407 self._bugReportDialog.run()
1408
1409 def _setupTimeSync(self):
1410 """Enable or disable the simulator time synchronization based on the
1411 configuration."""
1412 simulator = self._simulator
1413 if simulator is not None:
1414 if self.config.syncFSTime:
1415 simulator.enableTimeSync()
1416 else:
1417 simulator.disableTimeSync()
1418
1419 def viewPIREP(self, pirep):
1420 """Display the PIREP viewer window with the given PIREP."""
1421 self._pirepViewer.setPIREP(pirep)
1422 self._pirepViewer.show_all()
1423 self._pirepViewer.run()
1424 self._pirepViewer.hide()
1425
1426 def viewMessagedPIREP(self, pirep):
1427 """Display the PIREP viewer window with the given PIREP containing
1428 messages as well."""
1429 self._messagedPIREPViewer.setPIREP(pirep)
1430 self._messagedPIREPViewer.show_all()
1431 self._messagedPIREPViewer.run()
1432 self._messagedPIREPViewer.hide()
1433
1434 def editPIREP(self, pirep):
1435 """Display the PIREP editor window and allow editing the PIREP."""
1436 self._pirepEditor.setPIREP(pirep)
1437 self._pirepEditor.show_all()
1438 if self._pirepEditor.run()==RESPONSETYPE_OK:
1439 self.beginBusy(xstr("pirepEdit_save_busy"))
1440 self.webHandler.sendPIREP(self._pirepUpdatedCallback, pirep,
1441 update = True)
1442 else:
1443 self._pirepEditor.hide()
1444
1445 def _pirepUpdatedCallback(self, returned, result):
1446 """Callback for the PIREP updating result."""
1447 gobject.idle_add(self._handlePIREPUpdated, returned, result)
1448
1449 def _handlePIREPUpdated(self, returned, result):
1450 """Callback for the PIREP updating result."""
1451 self.endBusy()
1452 secondaryMarkup = None
1453 type = MESSAGETYPE_ERROR
1454 if returned:
1455 if result.success:
1456 type = None
1457 elif result.alreadyFlown:
1458 messageFormat = xstr("sendPIREP_already")
1459 secondaryMarkup = xstr("sendPIREP_already_sec")
1460 elif result.notAvailable:
1461 messageFormat = xstr("sendPIREP_notavail")
1462 else:
1463 messageFormat = xstr("sendPIREP_unknown")
1464 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1465 else:
1466 print("PIREP sending failed", result)
1467 messageFormat = xstr("sendPIREP_failed")
1468 secondaryMarkup = xstr("sendPIREP_failed_sec")
1469
1470 if type is not None:
1471 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1472 type = type, message_format = messageFormat)
1473 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1474 dialog.set_title(WINDOW_TITLE_BASE)
1475 if secondaryMarkup is not None:
1476 dialog.format_secondary_markup(secondaryMarkup)
1477
1478 dialog.run()
1479 dialog.hide()
1480
1481 self._pirepEditor.hide()
1482
1483 def _loadPIREP(self, menuItem):
1484 """Load a PIREP for sending."""
1485 dialog = self._getLoadPirepDialog()
1486
1487 if self._lastLoadedPIREP:
1488 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1489 else:
1490 pirepDirectory = self.config.pirepDirectory
1491 if pirepDirectory is not None:
1492 dialog.set_current_folder(pirepDirectory)
1493
1494 result = dialog.run()
1495 dialog.hide()
1496
1497 if result==RESPONSETYPE_OK:
1498 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1499
1500 pirep = PIREP.load(self._lastLoadedPIREP)
1501 if pirep is None:
1502 dialog = gtk.MessageDialog(parent = self._mainWindow,
1503 type = MESSAGETYPE_ERROR,
1504 message_format = xstr("loadPIREP_failed"))
1505 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1506 dialog.set_title(WINDOW_TITLE_BASE)
1507 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1508 dialog.run()
1509 dialog.hide()
1510 else:
1511 dialog = self._getSendLoadedDialog(pirep)
1512 dialog.show_all()
1513 while True:
1514 result = dialog.run()
1515
1516 if result==RESPONSETYPE_OK:
1517 self.sendPIREP(pirep)
1518 elif result==1:
1519 self.viewPIREP(pirep)
1520 else:
1521 break
1522
1523 dialog.hide()
1524
1525 def _getLoadPirepDialog(self):
1526 """Get the PIREP loading file chooser dialog.
1527
1528 If it is not created yet, it will be created."""
1529 if self._loadPIREPDialog is None:
1530 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1531 xstr("loadPIREP_browser_title"),
1532 action = FILE_CHOOSER_ACTION_OPEN,
1533 buttons = (gtk.STOCK_CANCEL,
1534 RESPONSETYPE_CANCEL,
1535 gtk.STOCK_OK, RESPONSETYPE_OK),
1536 parent = self._mainWindow)
1537 dialog.set_modal(True)
1538
1539
1540 filter = gtk.FileFilter()
1541 filter.set_name(xstr("file_filter_pireps"))
1542 filter.add_pattern("*.pirep")
1543 dialog.add_filter(filter)
1544
1545 filter = gtk.FileFilter()
1546 filter.set_name(xstr("file_filter_all"))
1547 filter.add_pattern("*.*")
1548 dialog.add_filter(filter)
1549
1550 self._loadPIREPDialog = dialog
1551
1552 return self._loadPIREPDialog
1553
1554 def _getSendLoadedDialog(self, pirep):
1555 """Get a dialog displaying the main information of the flight from the
1556 PIREP and providing Cancel and Send buttons."""
1557 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1558 xstr("loadPIREP_send_title"),
1559 parent = self._mainWindow,
1560 flags = DIALOG_MODAL)
1561
1562 contentArea = dialog.get_content_area()
1563
1564 label = gtk.Label(xstr("loadPIREP_send_help"))
1565 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1566 xscale = 0.0, yscale = 0.0)
1567 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1568 padding_left = 48, padding_right = 48)
1569 alignment.add(label)
1570 contentArea.pack_start(alignment, False, False, 8)
1571
1572 table = gtk.Table(5, 2)
1573 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1574 xscale = 0.0, yscale = 0.0)
1575 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1576 padding_left = 48, padding_right = 48)
1577 table.set_row_spacings(4)
1578 table.set_col_spacings(16)
1579 tableAlignment.add(table)
1580 contentArea.pack_start(tableAlignment, True, True, 8)
1581
1582 bookedFlight = pirep.bookedFlight
1583
1584 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1585 label.set_use_markup(True)
1586 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1587 xscale = 0.0, yscale = 0.0)
1588 labelAlignment.add(label)
1589 table.attach(labelAlignment, 0, 1, 0, 1)
1590
1591 label = gtk.Label(bookedFlight.callsign)
1592 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1593 xscale = 0.0, yscale = 0.0)
1594 labelAlignment.add(label)
1595 table.attach(labelAlignment, 1, 2, 0, 1)
1596
1597 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1598 label.set_use_markup(True)
1599 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1600 xscale = 0.0, yscale = 0.0)
1601 labelAlignment.add(label)
1602 table.attach(labelAlignment, 0, 1, 1, 2)
1603
1604 label = gtk.Label(str(bookedFlight.departureTime.date()))
1605 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1606 xscale = 0.0, yscale = 0.0)
1607 labelAlignment.add(label)
1608 table.attach(labelAlignment, 1, 2, 1, 2)
1609
1610 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1611 label.set_use_markup(True)
1612 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1613 xscale = 0.0, yscale = 0.0)
1614 labelAlignment.add(label)
1615 table.attach(labelAlignment, 0, 1, 2, 3)
1616
1617 label = gtk.Label(bookedFlight.departureICAO)
1618 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1619 xscale = 0.0, yscale = 0.0)
1620 labelAlignment.add(label)
1621 table.attach(labelAlignment, 1, 2, 2, 3)
1622
1623 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1624 label.set_use_markup(True)
1625 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1626 xscale = 0.0, yscale = 0.0)
1627 labelAlignment.add(label)
1628 table.attach(labelAlignment, 0, 1, 3, 4)
1629
1630 label = gtk.Label(bookedFlight.arrivalICAO)
1631 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1632 xscale = 0.0, yscale = 0.0)
1633 labelAlignment.add(label)
1634 table.attach(labelAlignment, 1, 2, 3, 4)
1635
1636 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1637 label.set_use_markup(True)
1638 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1639 xscale = 0.0, yscale = 0.0)
1640 labelAlignment.add(label)
1641 table.attach(labelAlignment, 0, 1, 4, 5)
1642
1643 rating = pirep.rating
1644 label = gtk.Label()
1645 if rating<0:
1646 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1647 else:
1648 label.set_text("%.1f %%" % (rating,))
1649
1650 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1651 xscale = 0.0, yscale = 0.0)
1652 labelAlignment.add(label)
1653 table.attach(labelAlignment, 1, 2, 4, 5)
1654
1655 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1656 dialog.add_button(xstr("viewPIREP"), 1)
1657 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1658
1659 return dialog
1660
1661 def sendPIREP(self, pirep, callback = None):
1662 """Send the given PIREP."""
1663 self.beginBusy(xstr("sendPIREP_busy"))
1664 self._sendPIREPCallback = callback
1665 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1666
1667 def _pirepSentCallback(self, returned, result):
1668 """Callback for the PIREP sending result."""
1669 gobject.idle_add(self._handlePIREPSent, returned, result)
1670
1671 def _handlePIREPSent(self, returned, result):
1672 """Callback for the PIREP sending result."""
1673 self.endBusy()
1674 secondaryMarkup = None
1675 type = MESSAGETYPE_ERROR
1676 if returned:
1677 if result.success:
1678 type = MESSAGETYPE_INFO
1679 messageFormat = xstr("sendPIREP_success")
1680 secondaryMarkup = xstr("sendPIREP_success_sec")
1681 elif result.alreadyFlown:
1682 messageFormat = xstr("sendPIREP_already")
1683 secondaryMarkup = xstr("sendPIREP_already_sec")
1684 elif result.notAvailable:
1685 messageFormat = xstr("sendPIREP_notavail")
1686 else:
1687 messageFormat = xstr("sendPIREP_unknown")
1688 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1689 else:
1690 print("PIREP sending failed", result)
1691 messageFormat = xstr("sendPIREP_failed")
1692 secondaryMarkup = xstr("sendPIREP_failed_sec")
1693
1694 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1695 type = type, message_format = messageFormat)
1696 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1697 dialog.set_title(WINDOW_TITLE_BASE)
1698 if secondaryMarkup is not None:
1699 dialog.format_secondary_markup(secondaryMarkup)
1700
1701 dialog.run()
1702 dialog.hide()
1703
1704 callback = self._sendPIREPCallback
1705 self._sendPIREPCallback = None
1706 if callback is not None:
1707 callback(returned, result)
1708
1709 def sendBugReport(self, summary, description, email, callback = None):
1710 """Send the bug report with the given data."""
1711 description += "\n\n" + ("=" * 40)
1712 description += "\n\nThe contents of the log:\n\n"
1713
1714 for (timestampString, text) in self._logger.lines:
1715 description += str(formatFlightLogLine(timestampString, text))
1716
1717 description += "\n\n" + ("=" * 40)
1718 description += "\n\nThe contents of the debug log:\n\n"
1719
1720 buffer = self._debugLogView.get_buffer()
1721 description += buffer.get_text(buffer.get_start_iter(),
1722 buffer.get_end_iter(), True)
1723
1724 self.beginBusy(xstr("sendBugReport_busy"))
1725 self._sendBugReportCallback = callback
1726 self.webHandler.sendBugReport(self._bugReportSentCallback,
1727 summary, description, email)
1728
1729 def _cefInitialized(self):
1730 """Called when CEF has been initialized."""
1731 self._acars.start()
1732 cef.initializeSimBrief()
1733
1734 def _bugReportSentCallback(self, returned, result):
1735 """Callback function for the bug report sending result."""
1736 gobject.idle_add(self._handleBugReportSent, returned, result)
1737
1738 def _handleBugReportSent(self, returned, result):
1739 """Callback for the bug report sending result."""
1740 self.endBusy()
1741 secondaryMarkup = None
1742 type = MESSAGETYPE_ERROR
1743 if returned:
1744 if result.success:
1745 type = MESSAGETYPE_INFO
1746 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1747 secondaryMarkup = xstr("sendBugReport_success_sec")
1748 else:
1749 messageFormat = xstr("sendBugReport_error")
1750 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1751 else:
1752 messageFormat = xstr("sendBugReport_error")
1753 secondaryMarkup = xstr("sendBugReport_error_sec")
1754
1755 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1756 type = type, message_format = messageFormat)
1757 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1758 dialog.set_title(WINDOW_TITLE_BASE)
1759 if secondaryMarkup is not None:
1760 dialog.format_secondary_markup(secondaryMarkup)
1761
1762 dialog.run()
1763 dialog.hide()
1764
1765 callback = self._sendBugReportCallback
1766 self._sendBugReportCallback = None
1767 if callback is not None:
1768 callback(returned, result)
1769
1770 def _listenHotkeys(self):
1771 """Setup the hotkeys based on the configuration."""
1772 if self._hotkeySetID is None and self._simulator is not None:
1773 self._pilotHotkeyIndex = None
1774 self._checklistHotkeyIndex = None
1775
1776 hotkeys = []
1777
1778 config = self.config
1779 if config.enableSounds and config.pilotControlsSounds:
1780 self._pilotHotkeyIndex = len(hotkeys)
1781 hotkeys.append(config.pilotHotkey)
1782
1783 if config.enableChecklists:
1784 self._checklistHotkeyIndex = len(hotkeys)
1785 hotkeys.append(config.checklistHotkey)
1786
1787 if hotkeys:
1788 self._hotkeySetID = \
1789 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1790
1791 def _clearHotkeys(self):
1792 """Clear the hotkeys."""
1793 if self._hotkeySetID is not None:
1794 self._hotkeySetID=None
1795 self._simulator.clearHotkeys()
1796
1797 def _handleHotkeys(self, id, hotkeys):
1798 """Handle the hotkeys."""
1799 if id==self._hotkeySetID:
1800 for index in hotkeys:
1801 if index==self._pilotHotkeyIndex:
1802 print("gui.GUI._handleHotkeys: pilot hotkey pressed")
1803 self._flight.pilotHotkeyPressed()
1804 elif index==self._checklistHotkeyIndex:
1805 print("gui.GUI._handleHotkeys: checklist hotkey pressed")
1806 self._flight.checklistHotkeyPressed()
1807 else:
1808 print("gui.GUI._handleHotkeys: unhandled hotkey index:", index)
1809
1810 def _showManual(self, menuitem):
1811 """Show the user's manual."""
1812 webbrowser.open(url ="file://" +
1813 os.path.join(self._programDirectory, "doc", "manual",
1814 getLanguage(), "index.html"),
1815 new = 1)
1816
1817 def _showAbout(self, menuitem):
1818 """Show the about dialog."""
1819 dialog = self._getAboutDialog()
1820 dialog.show_all()
1821 dialog.run()
1822 dialog.hide()
1823
1824 def _getAboutDialog(self):
1825 """Get the about dialog.
1826
1827 If it does not exist yet, it will be created."""
1828 if self._aboutDialog is None:
1829 dialog = gtk.AboutDialog()
1830 dialog.set_transient_for(self._mainWindow)
1831 dialog.set_modal(True)
1832
1833 logoPath = os.path.join(self._programDirectory, "logo.png")
1834 logo = pixbuf_new_from_file(logoPath)
1835 dialog.set_logo(logo)
1836
1837 dialog.set_program_name(PROGRAM_NAME)
1838 dialog.set_version(const.VERSION)
1839 dialog.set_copyright("(c) 2012 by István Váradi")
1840 dialog.set_website("http://mlx.varadiistvan.hu")
1841 dialog.set_website_label(xstr("about_website"))
1842
1843 isHungarian = getLanguage()=="hu"
1844 authors = []
1845 for (familyName, firstName, role) in GUI._authors:
1846 author = "%s %s" % \
1847 (familyName if isHungarian else firstName,
1848 firstName if isHungarian else familyName)
1849 role = xstr("about_role_" + role)
1850 authors.append(author + " (" + role + ")")
1851 dialog.set_authors(authors)
1852
1853 dialog.set_license(xstr("about_license"))
1854
1855 if not pygobject:
1856 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1857
1858 self._aboutDialog = dialog
1859
1860 return self._aboutDialog
1861
1862 def _showAboutURL(self, dialog, link, user_data):
1863 """Show the about URL."""
1864 webbrowser.open(url = link, new = 1)
1865
1866 def _setTakeoffAntiIceOn(self, value):
1867 """Set the anti-ice on indicator."""
1868 self._wizard.takeoffAntiIceOn = value
1869
1870 def _setLandingAntiIceOn(self, value):
1871 """Set the anti-ice on indicator."""
1872 self._wizard.landingAntiIceOn = value
1873
1874 def _getCredentialsCallback(self):
1875 """Called when the web handler asks for the credentials."""
1876 # FIXME: this is almost the same as
1877 # SimBriefSetupPage._getCredentialsCallback
1878 with self._credentialsCondition:
1879 self._credentialsAvailable = False
1880
1881 gobject.idle_add(self._getCredentials)
1882
1883 while not self._credentialsAvailable:
1884 self._credentialsCondition.wait()
1885
1886 return (self._credentialsUserName, self._credentialsPassword)
1887
1888 def _getCredentials(self):
1889 """Get the credentials."""
1890 # FIXME: this is almost the same as
1891 # SimBriefSetupPage._getCredentials
1892 with self._credentialsCondition:
1893 config = self.config
1894
1895 dialog = CredentialsDialog(self, config.pilotID, config.password,
1896 xstr("login_title"),
1897 xstr("button_cancel"),
1898 xstr("button_ok"),
1899 xstr("label_pilotID"),
1900 xstr("login_pilotID_tooltip"),
1901 xstr("label_password"),
1902 xstr("login_password_tooltip"),
1903 xstr("login_info"),
1904 config.rememberPassword,
1905 xstr("remember_password"),
1906 xstr("login_remember_tooltip"))
1907 response = dialog.run()
1908
1909 if response==RESPONSETYPE_OK:
1910 self._credentialsUserName = dialog.userName
1911 self._credentialsPassword = dialog.password
1912 rememberPassword = dialog.rememberPassword
1913
1914 config.pilotID = self._credentialsUserName
1915
1916 config.password = \
1917 self._credentialsPassword if rememberPassword else ""
1918 config.rememberPassword = rememberPassword
1919
1920 config.save()
1921 else:
1922 self._credentialsUserName = None
1923 self._credentialsPassword = None
1924
1925 self._credentialsAvailable = True
1926 self._credentialsCondition.notify()
1927
1928 def _updateDone(self):
1929 """Called when the update is done.
1930
1931 It checks if we already know the PID, and if not, asks the user whether
1932 to register."""
1933 if not self.config.pilotID and not self.config.password:
1934 dialog = gtk.MessageDialog(parent = self._mainWindow,
1935 type = MESSAGETYPE_QUESTION,
1936 message_format = xstr("register_ask"))
1937
1938 dialog.set_title(WINDOW_TITLE_BASE)
1939 dialog.format_secondary_markup(xstr("register_ask_sec"))
1940
1941 dialog.add_button(xstr("button_cancel"), 0)
1942 dialog.add_button(xstr("button_register"), 1)
1943 dialog.set_default_response(1)
1944
1945 result = dialog.run()
1946 dialog.hide()
1947 if result == 1:
1948 self._wizard.jumpPage("register")
Note: See TracBrowser for help on using the repository browser.