source: src/mlx/gui/gui.py@ 620:bcbc2bc37909

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

The border of the fault explanation widget is highlighted if there are any unexplained faults (re #257)

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