source: src/mlx/gui/gui.py@ 345:a62373a28d90

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

Implemented the new, more flexible way of storing the log during flight (#143)

File size: 50.7 KB
Line 
1# -*- coding: utf-8 -*-
2
3from statusicon import StatusIcon
4from statusbar import Statusbar
5from info import FlightInfo
6from update import Updater
7from mlx.gui.common import *
8from mlx.gui.flight import Wizard
9from mlx.gui.monitor import MonitorWindow
10from mlx.gui.weighthelp import WeightHelp
11from mlx.gui.gates import FleetGateStatus
12from mlx.gui.prefs import Preferences
13from mlx.gui.checklist import ChecklistEditor
14from mlx.gui.callouts import ApproachCalloutsEditor
15from mlx.gui.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 insertFlightLogLine(self, index, timestampString, text, isFault):
511 """Insert the flight log line with the given data."""
512 gobject.idle_add(self._insertFlightLogLine, index,
513 formatFlightLogLine(timestampString, text),
514 isFault)
515
516 def _insertFlightLogLine(self, index, line, isFault):
517 """Perform the real insertion.
518
519 To be called from the event loop."""
520 buffer = self._logView.get_buffer()
521 lineIter = buffer.get_iter_at_line(index)
522 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
523 self._logView.scroll_mark_onscreen(buffer.get_insert())
524
525 def removeFlightLogLine(self, index):
526 """Remove the flight log line with the given index."""
527 gobject.idle_add(self._removeFlightLogLine, index)
528
529 def _removeFlightLogLine(self, index):
530 """Perform the real removal."""
531 buffer = self._logView.get_buffer()
532 startIter = buffer.get_iter_at_line(index)
533 endIter = buffer.get_iter_at_line(index+1)
534 buffer.delete(startIter, endIter)
535 self._logView.scroll_mark_onscreen(buffer.get_insert())
536
537 def check(self, flight, aircraft, logger, oldState, state):
538 """Update the data."""
539 gobject.idle_add(self._monitorWindow.setData, state)
540 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
541
542 def resetFlightStatus(self):
543 """Reset the status of the flight."""
544 self._statusbar.resetFlightStatus()
545 self._statusbar.updateTime()
546 self._statusIcon.resetFlightStatus()
547
548 def setStage(self, stage):
549 """Set the stage of the flight."""
550 gobject.idle_add(self._setStage, stage)
551
552 def _setStage(self, stage):
553 """Set the stage of the flight."""
554 self._statusbar.setStage(stage)
555 self._statusIcon.setStage(stage)
556 self._wizard.setStage(stage)
557 if stage==const.STAGE_END:
558 welcomeMessage = \
559 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
560 self._disconnect(closingMessage =
561 "Flight plan closed. " + welcomeMessage,
562 duration = 5)
563
564 def setRating(self, rating):
565 """Set the rating of the flight."""
566 gobject.idle_add(self._setRating, rating)
567
568 def _setRating(self, rating):
569 """Set the rating of the flight."""
570 self._statusbar.setRating(rating)
571 self._statusIcon.setRating(rating)
572
573 def setNoGo(self, reason):
574 """Set the rating of the flight to No-Go with the given reason."""
575 gobject.idle_add(self._setNoGo, reason)
576
577 def _setNoGo(self, reason):
578 """Set the rating of the flight."""
579 self._statusbar.setNoGo(reason)
580 self._statusIcon.setNoGo(reason)
581
582 def _handleMainWindowState(self, window, event):
583 """Hande a change in the state of the window"""
584 iconified = gdk.WindowState.ICONIFIED if pygobject \
585 else gdk.WINDOW_STATE_ICONIFIED
586
587 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
588 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
589 self._statusIcon.mainWindowHidden()
590 else:
591 self._statusIcon.mainWindowShown()
592
593 if self.config.hideMinimizedWindow and not pygobject and \
594 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
595 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
596 self.hideMainWindow(savePosition = False)
597 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
598 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
599 self._mainWindow.present()
600
601 def raiseCallback(self):
602 """Callback for the singleton handling code."""
603 gobject.idle_add(self.raiseMainWindow)
604
605 def raiseMainWindow(self):
606 """Show the main window if invisible, and raise it."""
607 if not self._mainWindow.get_visible():
608 self.showMainWindow()
609 self._mainWindow.present()
610
611 def deleteMainWindow(self, window, event):
612 """Handle the delete event for the main window."""
613 if self.config.quitOnClose:
614 self._quit()
615 else:
616 self.hideMainWindow()
617 return True
618
619 def hideMainWindow(self, savePosition = True):
620 """Hide the main window and save its position."""
621 if savePosition:
622 (self._mainWindowX, self._mainWindowY) = \
623 self._mainWindow.get_window().get_root_origin()
624 else:
625 self._mainWindowX = self._mainWindowY = None
626 self._mainWindow.hide()
627 return True
628
629 def showMainWindow(self):
630 """Show the main window at its former position."""
631 if self._mainWindowX is not None and self._mainWindowY is not None:
632 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
633
634 if pygobject:
635 self._mainWindow.show()
636 else:
637 self._mainWindow.present()
638 self._mainWindow.deiconify()
639
640 def toggleMainWindow(self):
641 """Toggle the main window."""
642 if self._mainWindow.get_visible():
643 self.hideMainWindow()
644 else:
645 self.showMainWindow()
646
647 def hideMonitorWindow(self, savePosition = True):
648 """Hide the monitor window."""
649 if savePosition:
650 (self._monitorWindowX, self._monitorWindowY) = \
651 self._monitorWindow.get_window().get_root_origin()
652 else:
653 self._monitorWindowX = self._monitorWindowY = None
654 self._monitorWindow.hide()
655 self._statusIcon.monitorWindowHidden()
656 if self._showMonitorMenuItem.get_active():
657 self._selfToggling = True
658 self._showMonitorMenuItem.set_active(False)
659 return True
660
661 def showMonitorWindow(self):
662 """Show the monitor window."""
663 if self._monitorWindowX is not None and self._monitorWindowY is not None:
664 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
665 self._monitorWindow.show_all()
666 self._statusIcon.monitorWindowShown()
667 if not self._showMonitorMenuItem.get_active():
668 self._selfToggling = True
669 self._showMonitorMenuItem.set_active(True)
670
671 def _toggleMonitorWindow(self, menuItem):
672 if self._selfToggling:
673 self._selfToggling = False
674 elif self._monitorWindow.get_visible():
675 self.hideMonitorWindow()
676 else:
677 self.showMonitorWindow()
678
679 def restart(self):
680 """Quit and restart the application."""
681 self.toRestart = True
682 self._quit(force = True)
683
684 def flushStdIO(self):
685 """Flush any text to the standard error that could not be logged."""
686 if self._stdioText:
687 sys.__stderr__.write(self._stdioText)
688
689 def writeStdIO(self, text):
690 """Write the given text into standard I/O log."""
691 with self._stdioLock:
692 self._stdioText += text
693
694 gobject.idle_add(self._writeStdIO)
695
696 def beginBusy(self, message):
697 """Begin a period of background processing."""
698 self._wizard.set_sensitive(False)
699 self._weightHelp.set_sensitive(False)
700 self._mainWindow.get_window().set_cursor(self._busyCursor)
701 self._statusbar.updateBusyState(message)
702
703 def endBusy(self):
704 """End a period of background processing."""
705 self._mainWindow.get_window().set_cursor(None)
706 self._weightHelp.set_sensitive(True)
707 self._wizard.set_sensitive(True)
708 self._statusbar.updateBusyState(None)
709
710 def initializeWeightHelp(self):
711 """Initialize the weight help tab."""
712 self._weightHelp.reset()
713 self._weightHelp.enable()
714
715 def getFleetAsync(self, callback = None, force = None):
716 """Get the fleet asynchronously."""
717 gobject.idle_add(self.getFleet, callback, force)
718
719 def getFleet(self, callback = None, force = False):
720 """Get the fleet.
721
722 If force is False, and we already have a fleet retrieved,
723 that one will be used."""
724 if self._fleet is None or force:
725 self._fleetCallback = callback
726 self.beginBusy(xstr("fleet_busy"))
727 self.webHandler.getFleet(self._fleetResultCallback)
728 else:
729 callback(self._fleet)
730
731 def _fleetResultCallback(self, returned, result):
732 """Called when the fleet has been queried."""
733 gobject.idle_add(self._handleFleetResult, returned, result)
734
735 def _handleFleetResult(self, returned, result):
736 """Handle the fleet result."""
737 self.endBusy()
738 if returned:
739 self._fleet = result.fleet
740 else:
741 self._fleet = None
742
743 dialog = gtk.MessageDialog(parent = self.mainWindow,
744 type = MESSAGETYPE_ERROR,
745 message_format = xstr("fleet_failed"))
746 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
747 dialog.set_title(WINDOW_TITLE_BASE)
748 dialog.run()
749 dialog.hide()
750
751 callback = self._fleetCallback
752 self._fleetCallback = None
753 if callback is not None:
754 callback(self._fleet)
755 self._fleetGateStatus.handleFleet(self._fleet)
756
757 def updatePlane(self, tailNumber, status,
758 gateNumber = None, callback = None):
759 """Update the status of the given plane."""
760 self.beginBusy(xstr("fleet_update_busy"))
761
762 self._updatePlaneCallback = callback
763
764 self._updatePlaneTailNumber = tailNumber
765 self._updatePlaneStatus = status
766 self._updatePlaneGateNumber = gateNumber
767
768 self.webHandler.updatePlane(self._updatePlaneResultCallback,
769 tailNumber, status, gateNumber)
770
771 def _updatePlaneResultCallback(self, returned, result):
772 """Called when the status of a plane has been updated."""
773 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
774
775 def _handleUpdatePlaneResult(self, returned, result):
776 """Handle the plane update result."""
777 self.endBusy()
778 if returned:
779 success = result.success
780 if success:
781 if self._fleet is not None:
782 self._fleet.updatePlane(self._updatePlaneTailNumber,
783 self._updatePlaneStatus,
784 self._updatePlaneGateNumber)
785 self._fleetGateStatus.handleFleet(self._fleet)
786 else:
787 dialog = gtk.MessageDialog(parent = self.mainWindow,
788 type = MESSAGETYPE_ERROR,
789 message_format = xstr("fleet_update_failed"))
790 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
791 dialog.set_title(WINDOW_TITLE_BASE)
792 dialog.run()
793 dialog.hide()
794
795 success = None
796
797 callback = self._updatePlaneCallback
798 self._updatePlaneCallback = None
799 if callback is not None:
800 callback(success)
801
802 def _writeStdIO(self):
803 """Perform the real writing."""
804 with self._stdioLock:
805 text = self._stdioText
806 self._stdioText = ""
807 if not text: return
808
809 lines = text.splitlines()
810 if text[-1]=="\n":
811 text = ""
812 else:
813 text = lines[-1]
814 lines = lines[:-1]
815
816 now = datetime.datetime.now()
817 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
818
819 for line in lines:
820 #print >> sys.__stdout__, line
821 if self._stdioStartingLine:
822 self._writeLog(timeStr, self._debugLogView)
823 self._writeLog(line + "\n", self._debugLogView)
824 self._stdioStartingLine = True
825
826 if text:
827 #print >> sys.__stdout__, text,
828 if self._stdioStartingLine:
829 self._writeLog(timeStr, self._debugLogView)
830 self._writeLog(text, self._debugLogView)
831 self._stdioStartingLine = False
832
833 def connectSimulator(self, aircraftType):
834 """Connect to the simulator for the first time."""
835 self._logger.reset()
836
837 self._flight = flight.Flight(self._logger, self)
838 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
839 self._flight.aircraftType = aircraftType
840 self._flight.aircraft = acft.Aircraft.create(self._flight)
841 self._flight.aircraft._checkers.append(self)
842
843 if self._simulator is None:
844 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
845 fs.setupMessageSending(self.config, self._simulator)
846 self._setupTimeSync()
847
848 self._flight.simulator = self._simulator
849
850 self.beginBusy(xstr("connect_busy"))
851 self._statusbar.updateConnection(self._connecting, self._connected)
852
853 self._connecting = True
854 self._simulator.connect(self._flight.aircraft)
855
856 def startMonitoring(self):
857 """Start monitoring."""
858 if not self._monitoring:
859 self.simulator.startMonitoring()
860 self._monitoring = True
861
862 def stopMonitoring(self):
863 """Stop monitoring."""
864 if self._monitoring:
865 self.simulator.stopMonitoring()
866 self._monitoring = False
867
868 def cruiseLevelChanged(self):
869 """Called when the cruise level is changed in the flight wizard."""
870 if self._flight is not None:
871 self._flight.cruiseLevelChanged()
872
873 def _buildMenuBar(self, accelGroup):
874 """Build the main menu bar."""
875 menuBar = gtk.MenuBar()
876
877 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
878 fileMenu = gtk.Menu()
879 fileMenuItem.set_submenu(fileMenu)
880 menuBar.append(fileMenuItem)
881
882 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
883 loadPIREPMenuItem.set_use_stock(True)
884 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
885 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
886 ord(xstr("menu_file_loadPIREP_key")),
887 CONTROL_MASK, ACCEL_VISIBLE)
888 loadPIREPMenuItem.connect("activate", self._loadPIREP)
889 fileMenu.append(loadPIREPMenuItem)
890
891 fileMenu.append(gtk.SeparatorMenuItem())
892
893 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
894 quitMenuItem.set_use_stock(True)
895 quitMenuItem.set_label(xstr("menu_file_quit"))
896 quitMenuItem.add_accelerator("activate", accelGroup,
897 ord(xstr("menu_file_quit_key")),
898 CONTROL_MASK, ACCEL_VISIBLE)
899 quitMenuItem.connect("activate", self._quit)
900 fileMenu.append(quitMenuItem)
901
902 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
903 toolsMenu = gtk.Menu()
904 toolsMenuItem.set_submenu(toolsMenu)
905 menuBar.append(toolsMenuItem)
906
907 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
908 checklistMenuItem.set_use_stock(True)
909 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
910 checklistMenuItem.add_accelerator("activate", accelGroup,
911 ord(xstr("menu_tools_chklst_key")),
912 CONTROL_MASK, ACCEL_VISIBLE)
913 checklistMenuItem.connect("activate", self._editChecklist)
914 toolsMenu.append(checklistMenuItem)
915
916 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
917 approachCalloutsMenuItem.set_use_stock(True)
918 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
919 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
920 ord(xstr("menu_tools_callouts_key")),
921 CONTROL_MASK, ACCEL_VISIBLE)
922 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
923 toolsMenu.append(approachCalloutsMenuItem)
924
925 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
926 prefsMenuItem.set_use_stock(True)
927 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
928 prefsMenuItem.add_accelerator("activate", accelGroup,
929 ord(xstr("menu_tools_prefs_key")),
930 CONTROL_MASK, ACCEL_VISIBLE)
931 prefsMenuItem.connect("activate", self._editPreferences)
932 toolsMenu.append(prefsMenuItem)
933
934 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
935 viewMenu = gtk.Menu()
936 viewMenuItem.set_submenu(viewMenu)
937 menuBar.append(viewMenuItem)
938
939 self._showMonitorMenuItem = gtk.CheckMenuItem()
940 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
941 self._showMonitorMenuItem.set_use_underline(True)
942 self._showMonitorMenuItem.set_active(False)
943 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
944 ord(xstr("menu_view_monitor_key")),
945 CONTROL_MASK, ACCEL_VISIBLE)
946 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
947 viewMenu.append(self._showMonitorMenuItem)
948
949 showDebugMenuItem = gtk.CheckMenuItem()
950 showDebugMenuItem.set_label(xstr("menu_view_debug"))
951 showDebugMenuItem.set_use_underline(True)
952 showDebugMenuItem.set_active(False)
953 showDebugMenuItem.add_accelerator("activate", accelGroup,
954 ord(xstr("menu_view_debug_key")),
955 CONTROL_MASK, ACCEL_VISIBLE)
956 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
957 viewMenu.append(showDebugMenuItem)
958
959 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
960 helpMenu = gtk.Menu()
961 helpMenuItem.set_submenu(helpMenu)
962 menuBar.append(helpMenuItem)
963
964 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
965 manualMenuItem.set_use_stock(True)
966 manualMenuItem.set_label(xstr("menu_help_manual"))
967 manualMenuItem.add_accelerator("activate", accelGroup,
968 ord(xstr("menu_help_manual_key")),
969 CONTROL_MASK, ACCEL_VISIBLE)
970 manualMenuItem.connect("activate", self._showManual)
971 helpMenu.append(manualMenuItem)
972
973 helpMenu.append(gtk.SeparatorMenuItem())
974
975 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
976 aboutMenuItem.set_use_stock(True)
977 aboutMenuItem.set_label(xstr("menu_help_about"))
978 aboutMenuItem.add_accelerator("activate", accelGroup,
979 ord(xstr("menu_help_about_key")),
980 CONTROL_MASK, ACCEL_VISIBLE)
981 aboutMenuItem.connect("activate", self._showAbout)
982 helpMenu.append(aboutMenuItem)
983
984 return menuBar
985
986 def _toggleDebugLog(self, menuItem):
987 """Toggle the debug log."""
988 if menuItem.get_active():
989 label = gtk.Label(xstr("tab_debug_log"))
990 label.set_use_underline(True)
991 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
992 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
993 self._notebook.set_current_page(self._debugLogPage)
994 else:
995 self._notebook.remove_page(self._debugLogPage)
996
997 def _buildLogWidget(self):
998 """Build the widget for the log."""
999 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1000
1001 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1002 padding_left = 16, padding_right = 16)
1003
1004 logScroller = gtk.ScrolledWindow()
1005 # FIXME: these should be constants in common
1006 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1007 else gtk.POLICY_AUTOMATIC,
1008 gtk.PolicyType.AUTOMATIC if pygobject
1009 else gtk.POLICY_AUTOMATIC)
1010 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1011 else gtk.SHADOW_IN)
1012 logView = gtk.TextView()
1013 logView.set_editable(False)
1014 logView.set_cursor_visible(False)
1015 logScroller.add(logView)
1016
1017 logBox = gtk.VBox()
1018 logBox.pack_start(logScroller, True, True, 0)
1019 logBox.set_size_request(-1, 200)
1020
1021 alignment.add(logBox)
1022
1023 return (alignment, logView)
1024
1025 def _writeLog(self, msg, logView, isFault = False):
1026 """Write the given message to the log."""
1027 buffer = logView.get_buffer()
1028 appendTextBuffer(buffer, msg, isFault = isFault)
1029 logView.scroll_mark_onscreen(buffer.get_insert())
1030
1031 def _quit(self, what = None, force = False):
1032 """Quit from the application."""
1033 if force:
1034 result=RESPONSETYPE_YES
1035 else:
1036 dialog = gtk.MessageDialog(parent = self._mainWindow,
1037 type = MESSAGETYPE_QUESTION,
1038 message_format = xstr("quit_question"))
1039
1040 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1041 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1042
1043 dialog.set_title(WINDOW_TITLE_BASE)
1044 result = dialog.run()
1045 dialog.hide()
1046
1047 if result==RESPONSETYPE_YES:
1048 self._statusIcon.destroy()
1049 return gtk.main_quit()
1050
1051 def _notebookPageSwitch(self, notebook, page, page_num):
1052 """Called when the current page of the notebook has changed."""
1053 if page_num==0:
1054 gobject.idle_add(self._wizard.grabDefault)
1055 else:
1056 self._mainWindow.set_default(None)
1057
1058 def _editChecklist(self, menuItem):
1059 """Callback for editing the checklists."""
1060 self._checklistEditor.run()
1061
1062 def _editApproachCallouts(self, menuItem):
1063 """Callback for editing the approach callouts."""
1064 self._approachCalloutsEditor.run()
1065
1066 def _editPreferences(self, menuItem):
1067 """Callback for editing the preferences."""
1068 self._clearHotkeys()
1069 self._preferences.run(self.config)
1070 self._setupTimeSync()
1071 self._listenHotkeys()
1072
1073 def _setupTimeSync(self):
1074 """Enable or disable the simulator time synchronization based on the
1075 configuration."""
1076 simulator = self._simulator
1077 if simulator is not None:
1078 if self.config.syncFSTime:
1079 simulator.enableTimeSync()
1080 else:
1081 simulator.disableTimeSync()
1082
1083 def _loadPIREP(self, menuItem):
1084 """Load a PIREP for sending."""
1085 dialog = self._getLoadPirepDialog()
1086
1087 if self._lastLoadedPIREP:
1088 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1089 else:
1090 pirepDirectory = self.config.pirepDirectory
1091 if pirepDirectory is not None:
1092 dialog.set_current_folder(pirepDirectory)
1093
1094 result = dialog.run()
1095 dialog.hide()
1096
1097 if result==RESPONSETYPE_OK:
1098 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1099
1100 pirep = PIREP.load(self._lastLoadedPIREP)
1101 if pirep is None:
1102 dialog = gtk.MessageDialog(parent = self._mainWindow,
1103 type = MESSAGETYPE_ERROR,
1104 message_format = xstr("loadPIREP_failed"))
1105 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1106 dialog.set_title(WINDOW_TITLE_BASE)
1107 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1108 dialog.run()
1109 dialog.hide()
1110 else:
1111 dialog = self._getSendLoadedDialog(pirep)
1112 dialog.show_all()
1113 while True:
1114 result = dialog.run()
1115
1116 if result==RESPONSETYPE_OK:
1117 self.sendPIREP(pirep)
1118 elif result==1:
1119 self._pirepViewer.setPIREP(pirep)
1120 self._pirepViewer.show_all()
1121 self._pirepViewer.run()
1122 self._pirepViewer.hide()
1123 else:
1124 break
1125
1126 dialog.hide()
1127
1128 def _getLoadPirepDialog(self):
1129 """Get the PIREP loading file chooser dialog.
1130
1131 If it is not created yet, it will be created."""
1132 if self._loadPIREPDialog is None:
1133 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1134 xstr("loadPIREP_browser_title"),
1135 action = FILE_CHOOSER_ACTION_OPEN,
1136 buttons = (gtk.STOCK_CANCEL,
1137 RESPONSETYPE_CANCEL,
1138 gtk.STOCK_OK, RESPONSETYPE_OK),
1139 parent = self._mainWindow)
1140 dialog.set_modal(True)
1141
1142
1143 filter = gtk.FileFilter()
1144 filter.set_name(xstr("file_filter_pireps"))
1145 filter.add_pattern("*.pirep")
1146 dialog.add_filter(filter)
1147
1148 filter = gtk.FileFilter()
1149 filter.set_name(xstr("file_filter_all"))
1150 filter.add_pattern("*.*")
1151 dialog.add_filter(filter)
1152
1153 self._loadPIREPDialog = dialog
1154
1155 return self._loadPIREPDialog
1156
1157 def _getSendLoadedDialog(self, pirep):
1158 """Get a dialog displaying the main information of the flight from the
1159 PIREP and providing Cancel and Send buttons."""
1160 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1161 xstr("loadPIREP_send_title"),
1162 parent = self._mainWindow,
1163 flags = DIALOG_MODAL)
1164
1165 contentArea = dialog.get_content_area()
1166
1167 label = gtk.Label(xstr("loadPIREP_send_help"))
1168 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1169 xscale = 0.0, yscale = 0.0)
1170 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1171 padding_left = 48, padding_right = 48)
1172 alignment.add(label)
1173 contentArea.pack_start(alignment, False, False, 8)
1174
1175 table = gtk.Table(5, 2)
1176 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1177 xscale = 0.0, yscale = 0.0)
1178 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1179 padding_left = 48, padding_right = 48)
1180 table.set_row_spacings(4)
1181 table.set_col_spacings(16)
1182 tableAlignment.add(table)
1183 contentArea.pack_start(tableAlignment, True, True, 8)
1184
1185 bookedFlight = pirep.bookedFlight
1186
1187 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1188 label.set_use_markup(True)
1189 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1190 xscale = 0.0, yscale = 0.0)
1191 labelAlignment.add(label)
1192 table.attach(labelAlignment, 0, 1, 0, 1)
1193
1194 label = gtk.Label(bookedFlight.callsign)
1195 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1196 xscale = 0.0, yscale = 0.0)
1197 labelAlignment.add(label)
1198 table.attach(labelAlignment, 1, 2, 0, 1)
1199
1200 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1201 label.set_use_markup(True)
1202 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1203 xscale = 0.0, yscale = 0.0)
1204 labelAlignment.add(label)
1205 table.attach(labelAlignment, 0, 1, 1, 2)
1206
1207 label = gtk.Label(str(bookedFlight.departureTime.date()))
1208 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1209 xscale = 0.0, yscale = 0.0)
1210 labelAlignment.add(label)
1211 table.attach(labelAlignment, 1, 2, 1, 2)
1212
1213 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1214 label.set_use_markup(True)
1215 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1216 xscale = 0.0, yscale = 0.0)
1217 labelAlignment.add(label)
1218 table.attach(labelAlignment, 0, 1, 2, 3)
1219
1220 label = gtk.Label(bookedFlight.departureICAO)
1221 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1222 xscale = 0.0, yscale = 0.0)
1223 labelAlignment.add(label)
1224 table.attach(labelAlignment, 1, 2, 2, 3)
1225
1226 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1227 label.set_use_markup(True)
1228 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1229 xscale = 0.0, yscale = 0.0)
1230 labelAlignment.add(label)
1231 table.attach(labelAlignment, 0, 1, 3, 4)
1232
1233 label = gtk.Label(bookedFlight.arrivalICAO)
1234 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1235 xscale = 0.0, yscale = 0.0)
1236 labelAlignment.add(label)
1237 table.attach(labelAlignment, 1, 2, 3, 4)
1238
1239 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1240 label.set_use_markup(True)
1241 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1242 xscale = 0.0, yscale = 0.0)
1243 labelAlignment.add(label)
1244 table.attach(labelAlignment, 0, 1, 4, 5)
1245
1246 rating = pirep.rating
1247 label = gtk.Label()
1248 if rating<0:
1249 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1250 else:
1251 label.set_text("%.1f %%" % (rating,))
1252
1253 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1254 xscale = 0.0, yscale = 0.0)
1255 labelAlignment.add(label)
1256 table.attach(labelAlignment, 1, 2, 4, 5)
1257
1258 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1259 dialog.add_button(xstr("viewPIREP"), 1)
1260 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1261
1262 return dialog
1263
1264 def sendPIREP(self, pirep, callback = None):
1265 """Send the given PIREP."""
1266 self.beginBusy(xstr("sendPIREP_busy"))
1267 self._sendPIREPCallback = callback
1268 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1269
1270 def _pirepSentCallback(self, returned, result):
1271 """Callback for the PIREP sending result."""
1272 gobject.idle_add(self._handlePIREPSent, returned, result)
1273
1274 def _handlePIREPSent(self, returned, result):
1275 """Callback for the PIREP sending result."""
1276 self.endBusy()
1277 secondaryMarkup = None
1278 type = MESSAGETYPE_ERROR
1279 if returned:
1280 if result.success:
1281 type = MESSAGETYPE_INFO
1282 messageFormat = xstr("sendPIREP_success")
1283 secondaryMarkup = xstr("sendPIREP_success_sec")
1284 elif result.alreadyFlown:
1285 messageFormat = xstr("sendPIREP_already")
1286 secondaryMarkup = xstr("sendPIREP_already_sec")
1287 elif result.notAvailable:
1288 messageFormat = xstr("sendPIREP_notavail")
1289 else:
1290 messageFormat = xstr("sendPIREP_unknown")
1291 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1292 else:
1293 print "PIREP sending failed", result
1294 messageFormat = xstr("sendPIREP_failed")
1295 secondaryMarkup = xstr("sendPIREP_failed_sec")
1296
1297 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1298 type = type, message_format = messageFormat)
1299 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1300 dialog.set_title(WINDOW_TITLE_BASE)
1301 if secondaryMarkup is not None:
1302 dialog.format_secondary_markup(secondaryMarkup)
1303
1304 dialog.run()
1305 dialog.hide()
1306
1307 callback = self._sendPIREPCallback
1308 self._sendPIREPCallback = None
1309 if callback is not None:
1310 callback(returned, result)
1311
1312 def _listenHotkeys(self):
1313 """Setup the hotkeys based on the configuration."""
1314 if self._hotkeySetID is None and self._simulator is not None:
1315 self._pilotHotkeyIndex = None
1316 self._checklistHotkeyIndex = None
1317
1318 hotkeys = []
1319
1320 config = self.config
1321 if config.enableSounds and config.pilotControlsSounds:
1322 self._pilotHotkeyIndex = len(hotkeys)
1323 hotkeys.append(config.pilotHotkey)
1324
1325 if config.enableChecklists:
1326 self._checklistHotkeyIndex = len(hotkeys)
1327 hotkeys.append(config.checklistHotkey)
1328
1329 if hotkeys:
1330 self._hotkeySetID = \
1331 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1332
1333 def _clearHotkeys(self):
1334 """Clear the hotkeys."""
1335 if self._hotkeySetID is not None:
1336 self._hotkeySetID=None
1337 self._simulator.clearHotkeys()
1338
1339 def _handleHotkeys(self, id, hotkeys):
1340 """Handle the hotkeys."""
1341 if id==self._hotkeySetID:
1342 for index in hotkeys:
1343 if index==self._pilotHotkeyIndex:
1344 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1345 self._flight.pilotHotkeyPressed()
1346 elif index==self._checklistHotkeyIndex:
1347 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1348 self._flight.checklistHotkeyPressed()
1349 else:
1350 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1351
1352 def _showManual(self, menuitem):
1353 """Show the user's manual."""
1354 webbrowser.open(url ="file://" +
1355 os.path.join(self._programDirectory, "doc", "manual",
1356 getLanguage(), "index.html"),
1357 new = 1)
1358
1359 def _showAbout(self, menuitem):
1360 """Show the about dialog."""
1361 dialog = self._getAboutDialog()
1362 dialog.show_all()
1363 dialog.run()
1364 dialog.hide()
1365
1366 def _getAboutDialog(self):
1367 """Get the about dialog.
1368
1369 If it does not exist yet, it will be created."""
1370 if self._aboutDialog is None:
1371 dialog = gtk.AboutDialog()
1372 dialog.set_transient_for(self._mainWindow)
1373 dialog.set_modal(True)
1374
1375 logoPath = os.path.join(self._programDirectory, "logo.png")
1376 logo = pixbuf_new_from_file(logoPath)
1377 dialog.set_logo(logo)
1378
1379 dialog.set_program_name(PROGRAM_NAME)
1380 dialog.set_version(const.VERSION)
1381 dialog.set_copyright("(c) 2012 by István Váradi")
1382 dialog.set_website("http://mlx.varadiistvan.hu")
1383 dialog.set_website_label(xstr("about_website"))
1384
1385 isHungarian = getLanguage()=="hu"
1386 authors = []
1387 for (familyName, firstName, role) in GUI._authors:
1388 author = "%s %s" % \
1389 (familyName if isHungarian else firstName,
1390 firstName if isHungarian else familyName)
1391 role = xstr("about_role_" + role)
1392 authors.append(author + " (" + role + ")")
1393 dialog.set_authors(authors)
1394
1395 dialog.set_license(xstr("about_license"))
1396
1397 if not pygobject:
1398 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1399
1400 self._aboutDialog = dialog
1401
1402 return self._aboutDialog
1403
1404 def _showAboutURL(self, dialog, link, user_data):
1405 """Show the about URL."""
1406 webbrowser.open(url = link, new = 1)
Note: See TracBrowser for help on using the repository browser.