source: src/mlx/gui/gui.py@ 858:1f655516b7ae

Last change on this file since 858:1f655516b7ae was 858:1f655516b7ae, checked in by István Váradi <ivaradi@…>, 7 years ago

The timetable can be queried, displayed and filtered (re #304)

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