source: src/mlx/gui/gui.py@ 168:71af690e0c26

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

The hotkey handling works

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