source: src/mlx/gui/gui.py@ 845:9f492b8e735f

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

All data can be edited in the PIREP editor widget (re #307)

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