source: src/mlx/gui/gui.py@ 182:dd806c3cc18d

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

If the program is started in several instances, the ones after the first one just show the window of the running instance

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