source: src/mlx/gui/gui.py@ 605:f508c2cac441

Last change on this file since 605:f508c2cac441 was 605:f508c2cac441, checked in by István Váradi <ivaradi@…>, 10 years ago

The finish page also checks for the presence of all explanations (re #248)

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