source: src/mlx/gui/gui.py@ 202:aee91ecda48a

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

Made some of the windows non-resizable

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