source: src/mlx/gui/gui.py@ 172:565a6ea1f630

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

The beginnings of the checklist editor widget

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