source: src/mlx/gui/gui.py@ 604:0ec6a6f58f08

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

Added a new widget to list the faults and provide space for the user to enter an explanation (re #248).

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