source: src/mlx/gui/gui.py@ 305:ddc2dfec2080

Last change on this file since 305:ddc2dfec2080 was 304:9bfef8224383, checked in by István Váradi <ivaradi@…>, 12 years ago

Cruise level changes are logged

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