source: src/mlx/gui/gui.py@ 854:3e7dca86c1ed

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

The accepted flights can be queried and viewed (re #307)

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