source: src/mlx/gui/gui.py@ 178:98121552b435

Last change on this file since 178:98121552b435 was 178:98121552b435, checked in by István Váradi <ivaradi@…>, 12 years ago

Allocating only as many hotkeys as really needed

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