source: src/mlx/gui/gui.py@ 484:e6fc7bff478b

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

Implemented the GUI logic of the bug report sending (re #190)

File size: 55.3 KB
RevLine 
[227]1# -*- coding: utf-8 -*-
[28]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
[264]14from mlx.gui.callouts import ApproachCalloutsEditor
[220]15from mlx.gui.pirep import PIREPViewer
[483]16from mlx.gui.bugreport import BugReportDialog
[28]17
18import mlx.const as const
19import mlx.fs as fs
20import mlx.flight as flight
21import mlx.logger as logger
22import mlx.acft as acft
[41]23import mlx.web as web
[182]24import mlx.singleton as singleton
[261]25import mlx.airports as airports
[227]26from mlx.i18n import xstr, getLanguage
[151]27from mlx.pirep import PIREP
[28]28
29import time
[38]30import threading
31import sys
[203]32import datetime
[227]33import webbrowser
[28]34
[77]35#------------------------------------------------------------------------------
36
[300]37## @package mlx.gui.gui
38#
39# The main GUI class.
40#
41# The \ref GUI class is the main class of the GUI. It is a connection listener,
42# and aggregates all the windows, the menu, etc. It maintains the connection to
43# the simulator as well as the flight object.
44
45#------------------------------------------------------------------------------
46
[28]47class GUI(fs.ConnectionListener):
48 """The main GUI class."""
[276]49 _authors = [ (u"Váradi", u"István", "prog_test"),
50 (u"Galyassy", u"Tamás", "negotiation"),
51 (u"Kurják", u"Ákos", "test"),
52 (u"Nagy", u"Dániel", "test"),
[393]53 (u"Radó", u"Iván", "test"),
54 (u"Petrovszki", u"Gábor", "test"),
55 (u"Serfőző", u"Tamás", "test"),
56 (u"Szebenyi", u"Bálint", "test"),
57 (u"Zsebényi-Loksa", u"Gergely", "test") ]
[227]58
[36]59 def __init__(self, programDirectory, config):
[28]60 """Construct the GUI."""
61 gobject.threads_init()
62
[36]63 self._programDirectory = programDirectory
[42]64 self.config = config
[28]65 self._connecting = False
[59]66 self._reconnecting = False
[28]67 self._connected = False
[96]68 self._logger = logger.Logger(self)
[28]69 self._flight = None
70 self._simulator = None
[59]71 self._monitoring = False
[130]72
[119]73 self._fleet = None
[130]74
[119]75 self._fleetCallback = None
[38]76
[130]77 self._updatePlaneCallback = None
78 self._updatePlaneTailNumber = None
79 self._updatePlaneStatus = None
80 self._updatePlaneGateNumber = None
81
[38]82 self._stdioLock = threading.Lock()
83 self._stdioText = ""
[203]84 self._stdioStartingLine = True
[28]85
[151]86 self._sendPIREPCallback = None
[484]87 self._sendBugReportCallback = None
[151]88
[41]89 self.webHandler = web.Handler()
90 self.webHandler.start()
91
[38]92 self.toRestart = False
93
[29]94 def build(self, iconDirectory):
[28]95 """Build the GUI."""
[345]96
[123]97 self._mainWindow = window = gtk.Window()
[105]98 window.set_title(WINDOW_TITLE_BASE)
[36]99 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
[202]100 window.set_resizable(False)
[249]101 window.connect("delete-event", self.deleteMainWindow)
[36]102 window.connect("window-state-event", self._handleMainWindowState)
[93]103 accelGroup = gtk.AccelGroup()
104 window.add_accel_group(accelGroup)
[28]105
106 mainVBox = gtk.VBox()
[36]107 window.add(mainVBox)
[28]108
[123]109 self._preferences = Preferences(self)
[172]110 self._checklistEditor = ChecklistEditor(self)
[264]111 self._approachCalloutsEditor = ApproachCalloutsEditor(self)
[484]112 self._bugReportDialog = BugReportDialog(self)
[123]113
[93]114 menuBar = self._buildMenuBar(accelGroup)
115 mainVBox.pack_start(menuBar, False, False, 0)
116
[92]117 self._notebook = gtk.Notebook()
[93]118 mainVBox.pack_start(self._notebook, True, True, 4)
[345]119
[46]120 self._wizard = Wizard(self)
[107]121 label = gtk.Label(xstr("tab_flight"))
[46]122 label.set_use_underline(True)
[108]123 label.set_tooltip_text(xstr("tab_flight_tooltip"))
[92]124 self._notebook.append_page(self._wizard, label)
[42]125
[90]126 self._flightInfo = FlightInfo(self)
[107]127 label = gtk.Label(xstr("tab_flight_info"))
[90]128 label.set_use_underline(True)
[108]129 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
[92]130 self._notebook.append_page(self._flightInfo, label)
[93]131 self._flightInfo.disable()
[90]132
[117]133 self._weightHelp = WeightHelp(self)
134 label = gtk.Label(xstr("tab_weight_help"))
135 label.set_use_underline(True)
136 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
137 self._notebook.append_page(self._weightHelp, label)
[345]138
[93]139 (logWidget, self._logView) = self._buildLogWidget()
[345]140 addFaultTag(self._logView.get_buffer())
[107]141 label = gtk.Label(xstr("tab_log"))
[46]142 label.set_use_underline(True)
[108]143 label.set_tooltip_text(xstr("tab_log_tooltip"))
[93]144 self._notebook.append_page(logWidget, label)
145
[118]146 self._fleetGateStatus = FleetGateStatus(self)
147 label = gtk.Label(xstr("tab_gates"))
148 label.set_use_underline(True)
149 label.set_tooltip_text(xstr("tab_gates_tooltip"))
150 self._notebook.append_page(self._fleetGateStatus, label)
[345]151
[93]152 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
153 self._debugLogWidget.show_all()
[32]154
155 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
156
[281]157 self._statusbar = Statusbar(iconDirectory)
[32]158 mainVBox.pack_start(self._statusbar, False, False, 0)
159
[92]160 self._notebook.connect("switch-page", self._notebookPageSwitch)
[46]161
[77]162 self._monitorWindow = MonitorWindow(self, iconDirectory)
[93]163 self._monitorWindow.add_accel_group(accelGroup)
[77]164 self._monitorWindowX = None
165 self._monitorWindowY = None
[93]166 self._selfToggling = False
[77]167
[220]168 self._pirepViewer = PIREPViewer(self)
169
[46]170 window.show_all()
171 self._wizard.grabDefault()
[117]172 self._weightHelp.reset()
173 self._weightHelp.disable()
[28]174
[29]175 self._statusIcon = StatusIcon(iconDirectory, self)
[28]176
[49]177 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
178 else gdk.WATCH)
179
[151]180 self._loadPIREPDialog = None
181 self._lastLoadedPIREP = None
182
[168]183 self._hotkeySetID = None
[178]184 self._pilotHotkeyIndex = None
185 self._checklistHotkeyIndex = None
[168]186
[227]187 self._aboutDialog = None
188
[59]189 @property
[105]190 def mainWindow(self):
191 """Get the main window of the GUI."""
192 return self._mainWindow
[345]193
[105]194 @property
[97]195 def logger(self):
196 """Get the logger used by us."""
197 return self._logger
[345]198
[97]199 @property
[59]200 def simulator(self):
201 """Get the simulator used by us."""
202 return self._simulator
[345]203
[71]204 @property
205 def flight(self):
206 """Get the flight being performed."""
207 return self._flight
[84]208
209 @property
[184]210 def entranceExam(self):
211 """Get whether an entrance exam is about to be taken."""
212 return self._wizard.entranceExam
[215]213
214 @property
215 def loggedIn(self):
216 """Indicate if the user has logged in properly."""
217 return self._wizard.loggedIn
[345]218
[184]219 @property
[139]220 def loginResult(self):
221 """Get the result of the login."""
222 return self._wizard.loginResult
223
224 @property
[97]225 def bookedFlight(self):
226 """Get the booked flight selected, if any."""
227 return self._wizard.bookedFlight
228
229 @property
[303]230 def numCrew(self):
231 """Get the number of crew members."""
232 return self._wizard.numCrew
233
234 @property
235 def numPassengers(self):
236 """Get the number of passengers."""
237 return self._wizard.numPassengers
238
239 @property
240 def bagWeight(self):
241 """Get the bag weight."""
242 return self._wizard.bagWeight
243
244 @property
[97]245 def cargoWeight(self):
246 """Get the cargo weight."""
247 return self._wizard.cargoWeight
248
249 @property
[303]250 def mailWeight(self):
251 """Get the mail weight."""
252 return self._wizard.mailWeight
253
254 @property
[84]255 def zfw(self):
256 """Get Zero-Fuel Weight calculated for the current flight."""
257 return self._wizard.zfw
[345]258
[84]259 @property
[97]260 def filedCruiseAltitude(self):
261 """Get cruise altitude filed for the current flight."""
262 return self._wizard.filedCruiseAltitude
[345]263
[97]264 @property
[84]265 def cruiseAltitude(self):
[97]266 """Get cruise altitude set for the current flight."""
[84]267 return self._wizard.cruiseAltitude
[97]268
269 @property
[383]270 def loggableCruiseAltitude(self):
271 """Get the cruise altitude that can be logged."""
272 return self._wizard.loggableCruiseAltitude
273
274 @property
[97]275 def route(self):
276 """Get the flight route."""
277 return self._wizard.route
278
279 @property
280 def departureMETAR(self):
281 """Get the METAR of the deprature airport."""
282 return self._wizard.departureMETAR
[345]283
[84]284 @property
[97]285 def arrivalMETAR(self):
286 """Get the METAR of the deprature airport."""
287 return self._wizard.arrivalMETAR
288
289 @property
290 def departureRunway(self):
291 """Get the name of the departure runway."""
292 return self._wizard.departureRunway
[345]293
[97]294 @property
295 def sid(self):
296 """Get the SID."""
297 return self._wizard.sid
298
299 @property
[84]300 def v1(self):
301 """Get the V1 speed calculated for the flight."""
302 return self._wizard.v1
[345]303
[84]304 @property
305 def vr(self):
306 """Get the Vr speed calculated for the flight."""
307 return self._wizard.vr
[345]308
[84]309 @property
310 def v2(self):
311 """Get the V2 speed calculated for the flight."""
312 return self._wizard.v2
[345]313
[86]314 @property
[384]315 def derate(self):
316 """Get the derate value calculated for the flight."""
317 return self._wizard.derate
318
319 @property
[391]320 def takeoffAntiIceOn(self):
321 """Get whether the anti-ice system was on during take-off."""
322 return self._wizard.takeoffAntiIceOn
323
324 @takeoffAntiIceOn.setter
325 def takeoffAntiIceOn(self, value):
326 """Set the anti-ice on indicator."""
327 gobject.idle_add(self._setTakeoffAntiIceOn, value)
328
329 @property
[349]330 def rtoIndicated(self):
331 """Get whether the pilot has indicated than an RTO has occured."""
332 return self._wizard.rtoIndicated
333
334 @property
[97]335 def arrivalRunway(self):
336 """Get the arrival runway."""
337 return self._wizard.arrivalRunway
338
339 @property
340 def star(self):
341 """Get the STAR."""
342 return self._wizard.star
343
344 @property
345 def transition(self):
346 """Get the transition."""
347 return self._wizard.transition
348
349 @property
350 def approachType(self):
351 """Get the approach type."""
352 return self._wizard.approachType
353
354 @property
[86]355 def vref(self):
356 """Get the Vref speed calculated for the flight."""
357 return self._wizard.vref
[345]358
[97]359 @property
[391]360 def landingAntiIceOn(self):
361 """Get whether the anti-ice system was on during landing."""
362 return self._wizard.landingAntiIceOn
363
364 @landingAntiIceOn.setter
365 def landingAntiIceOn(self, value):
366 """Set the anti-ice on indicator."""
367 gobject.idle_add(self._setLandingAntiIceOn, value)
368
369 @property
[97]370 def flightType(self):
371 """Get the flight type."""
372 return self._wizard.flightType
373
374 @property
375 def online(self):
376 """Get whether the flight was online or not."""
377 return self._wizard.online
378
379 @property
380 def comments(self):
381 """Get the comments."""
382 return self._flightInfo.comments
383
384 @property
[349]385 def hasComments(self):
386 """Indicate whether there is a comment."""
387 return self._flightInfo.hasComments
388
389 @property
[97]390 def flightDefects(self):
391 """Get the flight defects."""
392 return self._flightInfo.flightDefects
393
[99]394 @property
395 def delayCodes(self):
396 """Get the delay codes."""
397 return self._flightInfo.delayCodes
398
[28]399 def run(self):
400 """Run the GUI."""
[42]401 if self.config.autoUpdate:
[38]402 self._updater = Updater(self,
403 self._programDirectory,
[42]404 self.config.updateURL,
[36]405 self._mainWindow)
406 self._updater.start()
[345]407
[182]408 singleton.raiseCallback = self.raiseCallback
[28]409 gtk.main()
[182]410 singleton.raiseCallback = None
[36]411
[91]412 self._disconnect()
[28]413
414 def connected(self, fsType, descriptor):
415 """Called when we have connected to the simulator."""
416 self._connected = True
[241]417 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
418 (const.VERSION, descriptor))
[133]419 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
420 "Welcome to MAVA Logger X " + const.VERSION)
[59]421 gobject.idle_add(self._handleConnected, fsType, descriptor)
422
423 def _handleConnected(self, fsType, descriptor):
424 """Called when the connection to the simulator has succeeded."""
425 self._statusbar.updateConnection(self._connecting, self._connected)
426 self.endBusy()
427 if not self._reconnecting:
428 self._wizard.connected(fsType, descriptor)
429 self._reconnecting = False
[168]430 self._listenHotkeys()
[59]431
432 def connectionFailed(self):
433 """Called when the connection failed."""
434 self._logger.untimedMessage("Connection to the simulator failed")
435 gobject.idle_add(self._connectionFailed)
436
437 def _connectionFailed(self):
438 """Called when the connection failed."""
439 self.endBusy()
440 self._statusbar.updateConnection(self._connecting, self._connected)
441
[105]442 dialog = gtk.MessageDialog(parent = self._mainWindow,
443 type = MESSAGETYPE_ERROR,
[108]444 message_format = xstr("conn_failed"))
[345]445
[105]446 dialog.set_title(WINDOW_TITLE_BASE)
[108]447 dialog.format_secondary_markup(xstr("conn_failed_sec"))
[345]448
[108]449 dialog.add_button(xstr("button_cancel"), 0)
450 dialog.add_button(xstr("button_tryagain"), 1)
[59]451 dialog.set_default_response(1)
[345]452
[59]453 result = dialog.run()
454 dialog.hide()
455 if result == 1:
[107]456 self.beginBusy(xstr("connect_busy"))
[59]457 self._simulator.reconnect()
458 else:
[91]459 self.reset()
[345]460
[28]461 def disconnected(self):
462 """Called when we have disconnected from the simulator."""
463 self._connected = False
464 self._logger.untimedMessage("Disconnected from the simulator")
[59]465
466 gobject.idle_add(self._disconnected)
467
468 def _disconnected(self):
[345]469 """Called when we have disconnected from the simulator unexpectedly."""
[59]470 self._statusbar.updateConnection(self._connecting, self._connected)
471
472 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
[108]473 message_format = xstr("conn_broken"),
[59]474 parent = self._mainWindow)
[105]475 dialog.set_title(WINDOW_TITLE_BASE)
[108]476 dialog.format_secondary_markup(xstr("conn_broken_sec"))
[59]477
[108]478 dialog.add_button(xstr("button_cancel"), 0)
479 dialog.add_button(xstr("button_reconnect"), 1)
[59]480 dialog.set_default_response(1)
481
482 result = dialog.run()
483 dialog.hide()
484 if result == 1:
[108]485 self.beginBusy(xstr("connect_busy"))
[59]486 self._reconnecting = True
487 self._simulator.reconnect()
488 else:
[91]489 self.reset()
490
[436]491 def enableFlightInfo(self, aircraftType):
[93]492 """Enable the flight info tab."""
[436]493 self._flightInfo.enable(aircraftType)
[93]494
[208]495 def cancelFlight(self):
496 """Cancel the current file, if the user confirms it."""
497 dialog = gtk.MessageDialog(parent = self._mainWindow,
498 type = MESSAGETYPE_QUESTION,
499 message_format = xstr("cancelFlight_question"))
500
501 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
502 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
503
504 dialog.set_title(WINDOW_TITLE_BASE)
505 result = dialog.run()
506 dialog.hide()
[345]507
[208]508 if result==RESPONSETYPE_YES:
509 self.reset()
510
[91]511 def reset(self):
512 """Reset the GUI."""
513 self._disconnect()
[92]514
[91]515 self._flightInfo.reset()
[93]516 self._flightInfo.disable()
[91]517 self.resetFlightStatus()
[28]518
[117]519 self._weightHelp.reset()
520 self._weightHelp.disable()
[92]521 self._notebook.set_current_page(0)
522
523 self._logView.get_buffer().set_text("")
524
[215]525 if self.loggedIn:
526 self._wizard.reloadFlights(self._handleReloadResult)
527 else:
528 self._wizard.reset(None)
[208]529
530 def _handleReloadResult(self, returned, result):
531 """Handle the result of the reloading of the flights."""
532 self._wizard.reset(result if returned and result.loggedIn else None)
533
[152]534 def _disconnect(self, closingMessage = None, duration = 3):
[91]535 """Disconnect from the simulator if connected."""
[92]536 self.stopMonitoring()
[168]537 self._clearHotkeys()
[92]538
[91]539 if self._connected:
[152]540 if closingMessage is None:
541 self._flight.simulator.disconnect()
542 else:
543 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
544 closingMessage, duration,
545 disconnect = True)
[91]546 self._connected = False
547
548 self._connecting = False
549 self._reconnecting = False
550 self._statusbar.updateConnection(False, False)
[128]551 self._weightHelp.disable()
[134]552
553 return True
[345]554
555 def insertFlightLogLine(self, index, timestampString, text, isFault):
556 """Insert the flight log line with the given data."""
557 gobject.idle_add(self._insertFlightLogLine, index,
558 formatFlightLogLine(timestampString, text),
559 isFault)
560
561 def _insertFlightLogLine(self, index, line, isFault):
562 """Perform the real insertion.
[96]563
[345]564 To be called from the event loop."""
565 buffer = self._logView.get_buffer()
566 lineIter = buffer.get_iter_at_line(index)
567 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
568 self._logView.scroll_mark_onscreen(buffer.get_insert())
[96]569
[345]570 def removeFlightLogLine(self, index):
571 """Remove the flight log line with the given index."""
572 gobject.idle_add(self._removeFlightLogLine, index)
573
574 def _removeFlightLogLine(self, index):
575 """Perform the real removal."""
[96]576 buffer = self._logView.get_buffer()
577 startIter = buffer.get_iter_at_line(index)
[345]578 endIter = buffer.get_iter_at_line(index+1)
[96]579 buffer.delete(startIter, endIter)
580 self._logView.scroll_mark_onscreen(buffer.get_insert())
581
[28]582 def check(self, flight, aircraft, logger, oldState, state):
583 """Update the data."""
[77]584 gobject.idle_add(self._monitorWindow.setData, state)
[80]585 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
[28]586
[31]587 def resetFlightStatus(self):
588 """Reset the status of the flight."""
[32]589 self._statusbar.resetFlightStatus()
[80]590 self._statusbar.updateTime()
[31]591 self._statusIcon.resetFlightStatus()
592
593 def setStage(self, stage):
594 """Set the stage of the flight."""
595 gobject.idle_add(self._setStage, stage)
596
597 def _setStage(self, stage):
598 """Set the stage of the flight."""
[32]599 self._statusbar.setStage(stage)
[31]600 self._statusIcon.setStage(stage)
[84]601 self._wizard.setStage(stage)
[88]602 if stage==const.STAGE_END:
[261]603 welcomeMessage = \
604 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
[152]605 self._disconnect(closingMessage =
[261]606 "Flight plan closed. " + welcomeMessage,
[152]607 duration = 5)
[31]608
609 def setRating(self, rating):
610 """Set the rating of the flight."""
611 gobject.idle_add(self._setRating, rating)
612
613 def _setRating(self, rating):
614 """Set the rating of the flight."""
[32]615 self._statusbar.setRating(rating)
[31]616 self._statusIcon.setRating(rating)
617
618 def setNoGo(self, reason):
619 """Set the rating of the flight to No-Go with the given reason."""
620 gobject.idle_add(self._setNoGo, reason)
621
622 def _setNoGo(self, reason):
623 """Set the rating of the flight."""
[32]624 self._statusbar.setNoGo(reason)
[31]625 self._statusIcon.setNoGo(reason)
626
[29]627 def _handleMainWindowState(self, window, event):
628 """Hande a change in the state of the window"""
629 iconified = gdk.WindowState.ICONIFIED if pygobject \
630 else gdk.WINDOW_STATE_ICONIFIED
[246]631
632 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
633 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
634 self._statusIcon.mainWindowHidden()
635 else:
636 self._statusIcon.mainWindowShown()
637
638 if self.config.hideMinimizedWindow and not pygobject and \
639 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
640 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
[35]641 self.hideMainWindow(savePosition = False)
[246]642 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
643 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
644 self._mainWindow.present()
[345]645
[182]646 def raiseCallback(self):
647 """Callback for the singleton handling code."""
648 gobject.idle_add(self.raiseMainWindow)
649
650 def raiseMainWindow(self):
[246]651 """Show the main window if invisible, and raise it."""
[182]652 if not self._mainWindow.get_visible():
653 self.showMainWindow()
654 self._mainWindow.present()
655
[249]656 def deleteMainWindow(self, window, event):
657 """Handle the delete event for the main window."""
658 if self.config.quitOnClose:
659 self._quit()
660 else:
661 self.hideMainWindow()
662 return True
663
[35]664 def hideMainWindow(self, savePosition = True):
[29]665 """Hide the main window and save its position."""
[35]666 if savePosition:
667 (self._mainWindowX, self._mainWindowY) = \
668 self._mainWindow.get_window().get_root_origin()
669 else:
670 self._mainWindowX = self._mainWindowY = None
[29]671 self._mainWindow.hide()
672 return True
673
674 def showMainWindow(self):
675 """Show the main window at its former position."""
[35]676 if self._mainWindowX is not None and self._mainWindowY is not None:
677 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
678
[246]679 if pygobject:
680 self._mainWindow.show()
681 else:
682 self._mainWindow.present()
[29]683 self._mainWindow.deiconify()
[345]684
[29]685 def toggleMainWindow(self):
686 """Toggle the main window."""
687 if self._mainWindow.get_visible():
688 self.hideMainWindow()
689 else:
690 self.showMainWindow()
[36]691
[77]692 def hideMonitorWindow(self, savePosition = True):
693 """Hide the monitor window."""
694 if savePosition:
695 (self._monitorWindowX, self._monitorWindowY) = \
696 self._monitorWindow.get_window().get_root_origin()
697 else:
698 self._monitorWindowX = self._monitorWindowY = None
699 self._monitorWindow.hide()
700 self._statusIcon.monitorWindowHidden()
[93]701 if self._showMonitorMenuItem.get_active():
702 self._selfToggling = True
703 self._showMonitorMenuItem.set_active(False)
[77]704 return True
705
706 def showMonitorWindow(self):
707 """Show the monitor window."""
708 if self._monitorWindowX is not None and self._monitorWindowY is not None:
709 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
710 self._monitorWindow.show_all()
711 self._statusIcon.monitorWindowShown()
[93]712 if not self._showMonitorMenuItem.get_active():
713 self._selfToggling = True
714 self._showMonitorMenuItem.set_active(True)
715
716 def _toggleMonitorWindow(self, menuItem):
717 if self._selfToggling:
718 self._selfToggling = False
719 elif self._monitorWindow.get_visible():
720 self.hideMonitorWindow()
721 else:
722 self.showMonitorWindow()
[77]723
[38]724 def restart(self):
725 """Quit and restart the application."""
726 self.toRestart = True
[81]727 self._quit(force = True)
[38]728
729 def flushStdIO(self):
730 """Flush any text to the standard error that could not be logged."""
731 if self._stdioText:
732 sys.__stderr__.write(self._stdioText)
[345]733
[36]734 def writeStdIO(self, text):
735 """Write the given text into standard I/O log."""
[38]736 with self._stdioLock:
737 self._stdioText += text
738
739 gobject.idle_add(self._writeStdIO)
[36]740
[49]741 def beginBusy(self, message):
742 """Begin a period of background processing."""
[93]743 self._wizard.set_sensitive(False)
[117]744 self._weightHelp.set_sensitive(False)
[49]745 self._mainWindow.get_window().set_cursor(self._busyCursor)
746 self._statusbar.updateBusyState(message)
747
748 def endBusy(self):
749 """End a period of background processing."""
750 self._mainWindow.get_window().set_cursor(None)
[117]751 self._weightHelp.set_sensitive(True)
[93]752 self._wizard.set_sensitive(True)
[49]753 self._statusbar.updateBusyState(None)
754
[117]755 def initializeWeightHelp(self):
756 """Initialize the weight help tab."""
757 self._weightHelp.reset()
758 self._weightHelp.enable()
759
[134]760 def getFleetAsync(self, callback = None, force = None):
761 """Get the fleet asynchronously."""
762 gobject.idle_add(self.getFleet, callback, force)
763
[119]764 def getFleet(self, callback = None, force = False):
765 """Get the fleet.
766
767 If force is False, and we already have a fleet retrieved,
768 that one will be used."""
769 if self._fleet is None or force:
770 self._fleetCallback = callback
771 self.beginBusy(xstr("fleet_busy"))
772 self.webHandler.getFleet(self._fleetResultCallback)
773 else:
774 callback(self._fleet)
775
[349]776 def updateRTO(self, inLoop = False):
777 """Indicate that the RTO state should be updated."""
778 if inLoop:
779 self._wizard.updateRTO()
780 else:
781 gobject.idle_add(self.updateRTO, True)
782
783 def rtoToggled(self, indicated):
784 """Called when the user has toggled the RTO checkbox."""
785 self._flight.rtoToggled(indicated)
786
[119]787 def _fleetResultCallback(self, returned, result):
788 """Called when the fleet has been queried."""
789 gobject.idle_add(self._handleFleetResult, returned, result)
790
791 def _handleFleetResult(self, returned, result):
792 """Handle the fleet result."""
793 self.endBusy()
794 if returned:
795 self._fleet = result.fleet
796 else:
797 self._fleet = None
798
[120]799 dialog = gtk.MessageDialog(parent = self.mainWindow,
[119]800 type = MESSAGETYPE_ERROR,
801 message_format = xstr("fleet_failed"))
[130]802 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
803 dialog.set_title(WINDOW_TITLE_BASE)
804 dialog.run()
805 dialog.hide()
806
807 callback = self._fleetCallback
808 self._fleetCallback = None
809 if callback is not None:
810 callback(self._fleet)
811 self._fleetGateStatus.handleFleet(self._fleet)
812
813 def updatePlane(self, tailNumber, status,
814 gateNumber = None, callback = None):
815 """Update the status of the given plane."""
816 self.beginBusy(xstr("fleet_update_busy"))
817
818 self._updatePlaneCallback = callback
819
820 self._updatePlaneTailNumber = tailNumber
821 self._updatePlaneStatus = status
822 self._updatePlaneGateNumber = gateNumber
[345]823
[130]824 self.webHandler.updatePlane(self._updatePlaneResultCallback,
825 tailNumber, status, gateNumber)
826
827 def _updatePlaneResultCallback(self, returned, result):
828 """Called when the status of a plane has been updated."""
829 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
830
831 def _handleUpdatePlaneResult(self, returned, result):
832 """Handle the plane update result."""
833 self.endBusy()
834 if returned:
835 success = result.success
836 if success:
837 if self._fleet is not None:
838 self._fleet.updatePlane(self._updatePlaneTailNumber,
839 self._updatePlaneStatus,
840 self._updatePlaneGateNumber)
841 self._fleetGateStatus.handleFleet(self._fleet)
842 else:
843 dialog = gtk.MessageDialog(parent = self.mainWindow,
844 type = MESSAGETYPE_ERROR,
845 message_format = xstr("fleet_update_failed"))
[124]846 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
[119]847 dialog.set_title(WINDOW_TITLE_BASE)
848 dialog.run()
849 dialog.hide()
850
[130]851 success = None
852
853 callback = self._updatePlaneCallback
854 self._updatePlaneCallback = None
855 if callback is not None:
856 callback(success)
[119]857
[38]858 def _writeStdIO(self):
[36]859 """Perform the real writing."""
[38]860 with self._stdioLock:
861 text = self._stdioText
862 self._stdioText = ""
863 if not text: return
[345]864
[36]865 lines = text.splitlines()
866 if text[-1]=="\n":
867 text = ""
868 else:
869 text = lines[-1]
870 lines = lines[:-1]
[203]871
872 now = datetime.datetime.now()
873 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
[345]874
[36]875 for line in lines:
[96]876 #print >> sys.__stdout__, line
[203]877 if self._stdioStartingLine:
878 self._writeLog(timeStr, self._debugLogView)
[93]879 self._writeLog(line + "\n", self._debugLogView)
[203]880 self._stdioStartingLine = True
[36]881
882 if text:
[96]883 #print >> sys.__stdout__, text,
[203]884 if self._stdioStartingLine:
885 self._writeLog(timeStr, self._debugLogView)
[93]886 self._writeLog(text, self._debugLogView)
[203]887 self._stdioStartingLine = False
[51]888
[59]889 def connectSimulator(self, aircraftType):
890 """Connect to the simulator for the first time."""
891 self._logger.reset()
892
893 self._flight = flight.Flight(self._logger, self)
[131]894 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
[59]895 self._flight.aircraftType = aircraftType
896 self._flight.aircraft = acft.Aircraft.create(self._flight)
897 self._flight.aircraft._checkers.append(self)
[345]898
[59]899 if self._simulator is None:
900 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
[133]901 fs.setupMessageSending(self.config, self._simulator)
[148]902 self._setupTimeSync()
[345]903
[59]904 self._flight.simulator = self._simulator
905
[107]906 self.beginBusy(xstr("connect_busy"))
[59]907 self._statusbar.updateConnection(self._connecting, self._connected)
908
909 self._connecting = True
[345]910 self._simulator.connect(self._flight.aircraft)
[59]911
[70]912 def startMonitoring(self):
913 """Start monitoring."""
[88]914 if not self._monitoring:
915 self.simulator.startMonitoring()
916 self._monitoring = True
[70]917
918 def stopMonitoring(self):
919 """Stop monitoring."""
[88]920 if self._monitoring:
921 self.simulator.stopMonitoring()
922 self._monitoring = False
[70]923
[304]924 def cruiseLevelChanged(self):
925 """Called when the cruise level is changed in the flight wizard."""
926 if self._flight is not None:
[383]927 return self._flight.cruiseLevelChanged()
928 else:
929 return False
[304]930
[93]931 def _buildMenuBar(self, accelGroup):
932 """Build the main menu bar."""
933 menuBar = gtk.MenuBar()
[345]934
[110]935 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
[93]936 fileMenu = gtk.Menu()
937 fileMenuItem.set_submenu(fileMenu)
938 menuBar.append(fileMenuItem)
939
[151]940 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
941 loadPIREPMenuItem.set_use_stock(True)
942 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
943 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
944 ord(xstr("menu_file_loadPIREP_key")),
945 CONTROL_MASK, ACCEL_VISIBLE)
946 loadPIREPMenuItem.connect("activate", self._loadPIREP)
947 fileMenu.append(loadPIREPMenuItem)
948
949 fileMenu.append(gtk.SeparatorMenuItem())
950
[93]951 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
952 quitMenuItem.set_use_stock(True)
[110]953 quitMenuItem.set_label(xstr("menu_file_quit"))
[93]954 quitMenuItem.add_accelerator("activate", accelGroup,
[110]955 ord(xstr("menu_file_quit_key")),
956 CONTROL_MASK, ACCEL_VISIBLE)
[93]957 quitMenuItem.connect("activate", self._quit)
958 fileMenu.append(quitMenuItem)
959
[123]960 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
961 toolsMenu = gtk.Menu()
962 toolsMenuItem.set_submenu(toolsMenu)
963 menuBar.append(toolsMenuItem)
964
[204]965 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
[172]966 checklistMenuItem.set_use_stock(True)
967 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
968 checklistMenuItem.add_accelerator("activate", accelGroup,
969 ord(xstr("menu_tools_chklst_key")),
970 CONTROL_MASK, ACCEL_VISIBLE)
971 checklistMenuItem.connect("activate", self._editChecklist)
972 toolsMenu.append(checklistMenuItem)
973
[264]974 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
975 approachCalloutsMenuItem.set_use_stock(True)
976 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
977 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
978 ord(xstr("menu_tools_callouts_key")),
979 CONTROL_MASK, ACCEL_VISIBLE)
980 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
981 toolsMenu.append(approachCalloutsMenuItem)
982
[204]983 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
[123]984 prefsMenuItem.set_use_stock(True)
985 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
986 prefsMenuItem.add_accelerator("activate", accelGroup,
987 ord(xstr("menu_tools_prefs_key")),
988 CONTROL_MASK, ACCEL_VISIBLE)
989 prefsMenuItem.connect("activate", self._editPreferences)
990 toolsMenu.append(prefsMenuItem)
[93]991
[482]992 toolsMenu.append(gtk.SeparatorMenuItem())
993
994 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
995 bugReportMenuItem.set_use_stock(True)
996 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
997 bugReportMenuItem.add_accelerator("activate", accelGroup,
998 ord(xstr("menu_tools_bugreport_key")),
999 CONTROL_MASK, ACCEL_VISIBLE)
1000 bugReportMenuItem.connect("activate", self._reportBug)
1001 toolsMenu.append(bugReportMenuItem)
1002
[110]1003 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
[93]1004 viewMenu = gtk.Menu()
1005 viewMenuItem.set_submenu(viewMenu)
1006 menuBar.append(viewMenuItem)
[28]1007
[93]1008 self._showMonitorMenuItem = gtk.CheckMenuItem()
[110]1009 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
[93]1010 self._showMonitorMenuItem.set_use_underline(True)
1011 self._showMonitorMenuItem.set_active(False)
1012 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
[110]1013 ord(xstr("menu_view_monitor_key")),
1014 CONTROL_MASK, ACCEL_VISIBLE)
[93]1015 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1016 viewMenu.append(self._showMonitorMenuItem)
1017
1018 showDebugMenuItem = gtk.CheckMenuItem()
[110]1019 showDebugMenuItem.set_label(xstr("menu_view_debug"))
[93]1020 showDebugMenuItem.set_use_underline(True)
1021 showDebugMenuItem.set_active(False)
1022 showDebugMenuItem.add_accelerator("activate", accelGroup,
[110]1023 ord(xstr("menu_view_debug_key")),
1024 CONTROL_MASK, ACCEL_VISIBLE)
[93]1025 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1026 viewMenu.append(showDebugMenuItem)
[28]1027
[227]1028 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1029 helpMenu = gtk.Menu()
1030 helpMenuItem.set_submenu(helpMenu)
1031 menuBar.append(helpMenuItem)
1032
1033 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1034 manualMenuItem.set_use_stock(True)
1035 manualMenuItem.set_label(xstr("menu_help_manual"))
1036 manualMenuItem.add_accelerator("activate", accelGroup,
1037 ord(xstr("menu_help_manual_key")),
1038 CONTROL_MASK, ACCEL_VISIBLE)
1039 manualMenuItem.connect("activate", self._showManual)
1040 helpMenu.append(manualMenuItem)
1041
1042 helpMenu.append(gtk.SeparatorMenuItem())
[345]1043
[227]1044 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1045 aboutMenuItem.set_use_stock(True)
1046 aboutMenuItem.set_label(xstr("menu_help_about"))
1047 aboutMenuItem.add_accelerator("activate", accelGroup,
1048 ord(xstr("menu_help_about_key")),
1049 CONTROL_MASK, ACCEL_VISIBLE)
1050 aboutMenuItem.connect("activate", self._showAbout)
1051 helpMenu.append(aboutMenuItem)
1052
[93]1053 return menuBar
[28]1054
[93]1055 def _toggleDebugLog(self, menuItem):
1056 """Toggle the debug log."""
1057 if menuItem.get_active():
[107]1058 label = gtk.Label(xstr("tab_debug_log"))
[93]1059 label.set_use_underline(True)
[345]1060 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
[93]1061 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1062 self._notebook.set_current_page(self._debugLogPage)
1063 else:
1064 self._notebook.remove_page(self._debugLogPage)
1065
1066 def _buildLogWidget(self):
1067 """Build the widget for the log."""
1068 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1069
1070 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1071 padding_left = 16, padding_right = 16)
[28]1072
1073 logScroller = gtk.ScrolledWindow()
[93]1074 # FIXME: these should be constants in common
1075 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1076 else gtk.POLICY_AUTOMATIC,
1077 gtk.PolicyType.AUTOMATIC if pygobject
1078 else gtk.POLICY_AUTOMATIC)
1079 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1080 else gtk.SHADOW_IN)
1081 logView = gtk.TextView()
1082 logView.set_editable(False)
[171]1083 logView.set_cursor_visible(False)
[93]1084 logScroller.add(logView)
[28]1085
1086 logBox = gtk.VBox()
1087 logBox.pack_start(logScroller, True, True, 0)
1088 logBox.set_size_request(-1, 200)
1089
[93]1090 alignment.add(logBox)
[28]1091
[93]1092 return (alignment, logView)
[28]1093
[226]1094 def _writeLog(self, msg, logView, isFault = False):
[28]1095 """Write the given message to the log."""
[93]1096 buffer = logView.get_buffer()
[226]1097 appendTextBuffer(buffer, msg, isFault = isFault)
[93]1098 logView.scroll_mark_onscreen(buffer.get_insert())
[28]1099
[81]1100 def _quit(self, what = None, force = False):
[38]1101 """Quit from the application."""
[81]1102 if force:
1103 result=RESPONSETYPE_YES
1104 else:
[105]1105 dialog = gtk.MessageDialog(parent = self._mainWindow,
1106 type = MESSAGETYPE_QUESTION,
[110]1107 message_format = xstr("quit_question"))
1108
[124]1109 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1110 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1111
[105]1112 dialog.set_title(WINDOW_TITLE_BASE)
[81]1113 result = dialog.run()
1114 dialog.hide()
[345]1115
[76]1116 if result==RESPONSETYPE_YES:
1117 self._statusIcon.destroy()
1118 return gtk.main_quit()
[38]1119
[46]1120 def _notebookPageSwitch(self, notebook, page, page_num):
1121 """Called when the current page of the notebook has changed."""
1122 if page_num==0:
[48]1123 gobject.idle_add(self._wizard.grabDefault)
[46]1124 else:
1125 self._mainWindow.set_default(None)
[123]1126
[172]1127 def _editChecklist(self, menuItem):
1128 """Callback for editing the checklists."""
1129 self._checklistEditor.run()
[345]1130
[264]1131 def _editApproachCallouts(self, menuItem):
1132 """Callback for editing the approach callouts."""
1133 self._approachCalloutsEditor.run()
[345]1134
[123]1135 def _editPreferences(self, menuItem):
1136 """Callback for editing the preferences."""
[168]1137 self._clearHotkeys()
[123]1138 self._preferences.run(self.config)
[148]1139 self._setupTimeSync()
[168]1140 self._listenHotkeys()
[148]1141
[482]1142 def _reportBug(self, menuItem):
1143 """Callback for reporting a bug."""
[484]1144 self._bugReportDialog.run()
[482]1145
[148]1146 def _setupTimeSync(self):
1147 """Enable or disable the simulator time synchronization based on the
1148 configuration."""
1149 simulator = self._simulator
1150 if simulator is not None:
1151 if self.config.syncFSTime:
1152 simulator.enableTimeSync()
1153 else:
1154 simulator.disableTimeSync()
[151]1155
1156 def _loadPIREP(self, menuItem):
1157 """Load a PIREP for sending."""
1158 dialog = self._getLoadPirepDialog()
1159
1160 if self._lastLoadedPIREP:
1161 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1162 else:
1163 pirepDirectory = self.config.pirepDirectory
1164 if pirepDirectory is not None:
1165 dialog.set_current_folder(pirepDirectory)
[345]1166
[151]1167 result = dialog.run()
1168 dialog.hide()
1169
1170 if result==RESPONSETYPE_OK:
[164]1171 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
[151]1172
1173 pirep = PIREP.load(self._lastLoadedPIREP)
1174 if pirep is None:
1175 dialog = gtk.MessageDialog(parent = self._mainWindow,
1176 type = MESSAGETYPE_ERROR,
1177 message_format = xstr("loadPIREP_failed"))
1178 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1179 dialog.set_title(WINDOW_TITLE_BASE)
1180 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1181 dialog.run()
1182 dialog.hide()
1183 else:
1184 dialog = self._getSendLoadedDialog(pirep)
1185 dialog.show_all()
[239]1186 while True:
1187 result = dialog.run()
[151]1188
[239]1189 if result==RESPONSETYPE_OK:
1190 self.sendPIREP(pirep)
1191 elif result==1:
1192 self._pirepViewer.setPIREP(pirep)
1193 self._pirepViewer.show_all()
1194 self._pirepViewer.run()
1195 self._pirepViewer.hide()
1196 else:
1197 break
1198
1199 dialog.hide()
[151]1200
1201 def _getLoadPirepDialog(self):
1202 """Get the PIREP loading file chooser dialog.
1203
1204 If it is not created yet, it will be created."""
1205 if self._loadPIREPDialog is None:
1206 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1207 xstr("loadPIREP_browser_title"),
1208 action = FILE_CHOOSER_ACTION_OPEN,
1209 buttons = (gtk.STOCK_CANCEL,
1210 RESPONSETYPE_CANCEL,
1211 gtk.STOCK_OK, RESPONSETYPE_OK),
1212 parent = self._mainWindow)
1213 dialog.set_modal(True)
[345]1214
[151]1215
1216 filter = gtk.FileFilter()
[184]1217 filter.set_name(xstr("file_filter_pireps"))
[151]1218 filter.add_pattern("*.pirep")
1219 dialog.add_filter(filter)
[345]1220
[151]1221 filter = gtk.FileFilter()
[184]1222 filter.set_name(xstr("file_filter_all"))
[151]1223 filter.add_pattern("*.*")
1224 dialog.add_filter(filter)
1225
1226 self._loadPIREPDialog = dialog
1227
1228 return self._loadPIREPDialog
1229
1230 def _getSendLoadedDialog(self, pirep):
1231 """Get a dialog displaying the main information of the flight from the
1232 PIREP and providing Cancel and Send buttons."""
1233 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1234 xstr("loadPIREP_send_title"),
1235 parent = self._mainWindow,
1236 flags = DIALOG_MODAL)
1237
1238 contentArea = dialog.get_content_area()
1239
1240 label = gtk.Label(xstr("loadPIREP_send_help"))
1241 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1242 xscale = 0.0, yscale = 0.0)
1243 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1244 padding_left = 48, padding_right = 48)
1245 alignment.add(label)
1246 contentArea.pack_start(alignment, False, False, 8)
1247
1248 table = gtk.Table(5, 2)
1249 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1250 xscale = 0.0, yscale = 0.0)
1251 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1252 padding_left = 48, padding_right = 48)
1253 table.set_row_spacings(4)
1254 table.set_col_spacings(16)
1255 tableAlignment.add(table)
1256 contentArea.pack_start(tableAlignment, True, True, 8)
1257
1258 bookedFlight = pirep.bookedFlight
1259
1260 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1261 label.set_use_markup(True)
1262 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1263 xscale = 0.0, yscale = 0.0)
1264 labelAlignment.add(label)
1265 table.attach(labelAlignment, 0, 1, 0, 1)
[345]1266
[151]1267 label = gtk.Label(bookedFlight.callsign)
1268 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1269 xscale = 0.0, yscale = 0.0)
1270 labelAlignment.add(label)
1271 table.attach(labelAlignment, 1, 2, 0, 1)
1272
1273 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1274 label.set_use_markup(True)
1275 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1276 xscale = 0.0, yscale = 0.0)
1277 labelAlignment.add(label)
1278 table.attach(labelAlignment, 0, 1, 1, 2)
[345]1279
[151]1280 label = gtk.Label(str(bookedFlight.departureTime.date()))
1281 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1282 xscale = 0.0, yscale = 0.0)
1283 labelAlignment.add(label)
1284 table.attach(labelAlignment, 1, 2, 1, 2)
1285
1286 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1287 label.set_use_markup(True)
1288 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1289 xscale = 0.0, yscale = 0.0)
1290 labelAlignment.add(label)
1291 table.attach(labelAlignment, 0, 1, 2, 3)
[345]1292
[151]1293 label = gtk.Label(bookedFlight.departureICAO)
1294 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1295 xscale = 0.0, yscale = 0.0)
1296 labelAlignment.add(label)
1297 table.attach(labelAlignment, 1, 2, 2, 3)
1298
1299 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1300 label.set_use_markup(True)
1301 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1302 xscale = 0.0, yscale = 0.0)
1303 labelAlignment.add(label)
1304 table.attach(labelAlignment, 0, 1, 3, 4)
[345]1305
[151]1306 label = gtk.Label(bookedFlight.arrivalICAO)
1307 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1308 xscale = 0.0, yscale = 0.0)
1309 labelAlignment.add(label)
1310 table.attach(labelAlignment, 1, 2, 3, 4)
1311
1312 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1313 label.set_use_markup(True)
1314 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1315 xscale = 0.0, yscale = 0.0)
1316 labelAlignment.add(label)
1317 table.attach(labelAlignment, 0, 1, 4, 5)
1318
1319 rating = pirep.rating
1320 label = gtk.Label()
1321 if rating<0:
1322 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1323 else:
1324 label.set_text("%.1f %%" % (rating,))
[345]1325
[151]1326 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1327 xscale = 0.0, yscale = 0.0)
1328 labelAlignment.add(label)
1329 table.attach(labelAlignment, 1, 2, 4, 5)
1330
1331 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
[220]1332 dialog.add_button(xstr("viewPIREP"), 1)
[151]1333 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
[345]1334
[151]1335 return dialog
[345]1336
[151]1337 def sendPIREP(self, pirep, callback = None):
1338 """Send the given PIREP."""
1339 self.beginBusy(xstr("sendPIREP_busy"))
1340 self._sendPIREPCallback = callback
1341 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1342
1343 def _pirepSentCallback(self, returned, result):
1344 """Callback for the PIREP sending result."""
1345 gobject.idle_add(self._handlePIREPSent, returned, result)
1346
1347 def _handlePIREPSent(self, returned, result):
1348 """Callback for the PIREP sending result."""
1349 self.endBusy()
1350 secondaryMarkup = None
1351 type = MESSAGETYPE_ERROR
1352 if returned:
1353 if result.success:
1354 type = MESSAGETYPE_INFO
1355 messageFormat = xstr("sendPIREP_success")
1356 secondaryMarkup = xstr("sendPIREP_success_sec")
1357 elif result.alreadyFlown:
1358 messageFormat = xstr("sendPIREP_already")
1359 secondaryMarkup = xstr("sendPIREP_already_sec")
1360 elif result.notAvailable:
1361 messageFormat = xstr("sendPIREP_notavail")
1362 else:
1363 messageFormat = xstr("sendPIREP_unknown")
1364 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1365 else:
1366 print "PIREP sending failed", result
1367 messageFormat = xstr("sendPIREP_failed")
1368 secondaryMarkup = xstr("sendPIREP_failed_sec")
[345]1369
[151]1370 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1371 type = type, message_format = messageFormat)
1372 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1373 dialog.set_title(WINDOW_TITLE_BASE)
1374 if secondaryMarkup is not None:
1375 dialog.format_secondary_markup(secondaryMarkup)
1376
1377 dialog.run()
1378 dialog.hide()
1379
1380 callback = self._sendPIREPCallback
1381 self._sendPIREPCallback = None
1382 if callback is not None:
1383 callback(returned, result)
[168]1384
[484]1385 def sendBugReport(self, summary, description, email, callback = None):
1386 """Send the bug report with the given data."""
1387 self.beginBusy(xstr("sendBugReport_busy"))
1388 self._sendBugReportCallback = callback
1389 self.webHandler.sendBugReport(self._bugReportSentCallback,
1390 summary, description, email)
1391
1392 def _bugReportSentCallback(self, returned, result):
1393 """Callback function for the bug report sending result."""
1394 gobject.idle_add(self._handleBugReportSent, returned, result)
1395
1396 def _handleBugReportSent(self, returned, result):
1397 """Callback for the bug report sending result."""
1398 self.endBusy()
1399 secondaryMarkup = None
1400 type = MESSAGETYPE_ERROR
1401 if returned:
1402 if result.success:
1403 type = MESSAGETYPE_INFO
1404 messageFormat = xstr("sendBugReport_success")
1405 secondaryMarkup = xstr("sendBugReport_success_sec")
1406 else:
1407 messageFormat = xstr("sendBugReport_error")
1408 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1409 else:
1410 messageFormat = xstr("sendBugReport_error")
1411 secondaryMarkup = xstr("sendBugReport_error_sec")
1412
1413 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1414 type = type, message_format = messageFormat)
1415 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1416 dialog.set_title(WINDOW_TITLE_BASE)
1417 if secondaryMarkup is not None:
1418 dialog.format_secondary_markup(secondaryMarkup)
1419
1420 dialog.run()
1421 dialog.hide()
1422
1423 callback = self._sendBugReportCallback
1424 self._sendBugReportCallback = None
1425 if callback is not None:
1426 callback(returned, result)
1427
[168]1428 def _listenHotkeys(self):
1429 """Setup the hotkeys based on the configuration."""
1430 if self._hotkeySetID is None and self._simulator is not None:
[178]1431 self._pilotHotkeyIndex = None
1432 self._checklistHotkeyIndex = None
1433
1434 hotkeys = []
1435
1436 config = self.config
1437 if config.enableSounds and config.pilotControlsSounds:
1438 self._pilotHotkeyIndex = len(hotkeys)
1439 hotkeys.append(config.pilotHotkey)
1440
1441 if config.enableChecklists:
1442 self._checklistHotkeyIndex = len(hotkeys)
1443 hotkeys.append(config.checklistHotkey)
1444
1445 if hotkeys:
1446 self._hotkeySetID = \
1447 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
[168]1448
1449 def _clearHotkeys(self):
1450 """Clear the hotkeys."""
1451 if self._hotkeySetID is not None:
1452 self._hotkeySetID=None
1453 self._simulator.clearHotkeys()
1454
1455 def _handleHotkeys(self, id, hotkeys):
1456 """Handle the hotkeys."""
1457 if id==self._hotkeySetID:
[170]1458 for index in hotkeys:
[178]1459 if index==self._pilotHotkeyIndex:
1460 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
[170]1461 self._flight.pilotHotkeyPressed()
[178]1462 elif index==self._checklistHotkeyIndex:
1463 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1464 self._flight.checklistHotkeyPressed()
[170]1465 else:
[178]1466 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
[227]1467
1468 def _showManual(self, menuitem):
1469 """Show the user's manual."""
1470 webbrowser.open(url ="file://" +
1471 os.path.join(self._programDirectory, "doc", "manual",
1472 getLanguage(), "index.html"),
1473 new = 1)
1474
1475 def _showAbout(self, menuitem):
1476 """Show the about dialog."""
1477 dialog = self._getAboutDialog()
1478 dialog.show_all()
1479 dialog.run()
1480 dialog.hide()
1481
1482 def _getAboutDialog(self):
1483 """Get the about dialog.
1484
1485 If it does not exist yet, it will be created."""
1486 if self._aboutDialog is None:
[276]1487 dialog = gtk.AboutDialog()
[227]1488 dialog.set_transient_for(self._mainWindow)
1489 dialog.set_modal(True)
[345]1490
[227]1491 logoPath = os.path.join(self._programDirectory, "logo.png")
1492 logo = pixbuf_new_from_file(logoPath)
1493 dialog.set_logo(logo)
[345]1494
[227]1495 dialog.set_program_name(PROGRAM_NAME)
1496 dialog.set_version(const.VERSION)
1497 dialog.set_copyright("(c) 2012 by István Váradi")
[237]1498 dialog.set_website("http://mlx.varadiistvan.hu")
1499 dialog.set_website_label(xstr("about_website"))
[227]1500
1501 isHungarian = getLanguage()=="hu"
1502 authors = []
1503 for (familyName, firstName, role) in GUI._authors:
[276]1504 author = "%s %s" % \
1505 (familyName if isHungarian else firstName,
1506 firstName if isHungarian else familyName)
1507 role = xstr("about_role_" + role)
1508 authors.append(author + " (" + role + ")")
[227]1509 dialog.set_authors(authors)
1510
1511 dialog.set_license(xstr("about_license"))
1512
[237]1513 if not pygobject:
1514 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1515
[276]1516 self._aboutDialog = dialog
1517
[227]1518 return self._aboutDialog
[237]1519
1520 def _showAboutURL(self, dialog, link, user_data):
[345]1521 """Show the about URL."""
[237]1522 webbrowser.open(url = link, new = 1)
[391]1523
1524 def _setTakeoffAntiIceOn(self, value):
1525 """Set the anti-ice on indicator."""
1526 self._wizard.takeoffAntiIceOn = value
1527
1528 def _setLandingAntiIceOn(self, value):
1529 """Set the anti-ice on indicator."""
1530 self._wizard.landingAntiIceOn = value
Note: See TracBrowser for help on using the repository browser.