source: src/mlx/gui/gui.py@ 164:a2bd429c9f9e

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

Fixed the handling of filenames

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