source: src/mlx/gui/gui.py@ 609:4eac92648123

Last change on this file since 609:4eac92648123 was 609:4eac92648123, checked in by István Váradi <ivaradi@…>, 9 years ago

Disconnection from the simulator is logged (re #249)

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