source: src/mlx/gui/gui.py@ 853:28eb49544c0f

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

PIREP modifications can be saved (re #307)

File size: 64.4 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 if self._pirepEditor.run()==RESPONSETYPE_OK:
1269 self.beginBusy(xstr("pirepEdit_save_busy"))
1270 self.webHandler.sendPIREP(self._pirepUpdatedCallback, pirep,
1271 update = True)
1272 else:
1273 self._pirepEditor.hide()
1274
1275 def _pirepUpdatedCallback(self, returned, result):
1276 """Callback for the PIREP updating result."""
1277 gobject.idle_add(self._handlePIREPUpdated, returned, result)
1278
1279 def _handlePIREPUpdated(self, returned, result):
1280 """Callback for the PIREP updating result."""
1281 self.endBusy()
1282 secondaryMarkup = None
1283 type = MESSAGETYPE_ERROR
1284 if returned:
1285 if result.success:
1286 type = None
1287 elif result.alreadyFlown:
1288 messageFormat = xstr("sendPIREP_already")
1289 secondaryMarkup = xstr("sendPIREP_already_sec")
1290 elif result.notAvailable:
1291 messageFormat = xstr("sendPIREP_notavail")
1292 else:
1293 messageFormat = xstr("sendPIREP_unknown")
1294 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1295 else:
1296 print "PIREP sending failed", result
1297 messageFormat = xstr("sendPIREP_failed")
1298 secondaryMarkup = xstr("sendPIREP_failed_sec")
1299
1300 if type is not None:
1301 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1302 type = type, message_format = messageFormat)
1303 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1304 dialog.set_title(WINDOW_TITLE_BASE)
1305 if secondaryMarkup is not None:
1306 dialog.format_secondary_markup(secondaryMarkup)
1307
1308 dialog.run()
1309 dialog.hide()
1310
1311 self._pirepEditor.hide()
1312
1313 def _loadPIREP(self, menuItem):
1314 """Load a PIREP for sending."""
1315 dialog = self._getLoadPirepDialog()
1316
1317 if self._lastLoadedPIREP:
1318 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1319 else:
1320 pirepDirectory = self.config.pirepDirectory
1321 if pirepDirectory is not None:
1322 dialog.set_current_folder(pirepDirectory)
1323
1324 result = dialog.run()
1325 dialog.hide()
1326
1327 if result==RESPONSETYPE_OK:
1328 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1329
1330 pirep = PIREP.load(self._lastLoadedPIREP)
1331 if pirep is None:
1332 dialog = gtk.MessageDialog(parent = self._mainWindow,
1333 type = MESSAGETYPE_ERROR,
1334 message_format = xstr("loadPIREP_failed"))
1335 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1336 dialog.set_title(WINDOW_TITLE_BASE)
1337 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1338 dialog.run()
1339 dialog.hide()
1340 else:
1341 dialog = self._getSendLoadedDialog(pirep)
1342 dialog.show_all()
1343 while True:
1344 result = dialog.run()
1345
1346 if result==RESPONSETYPE_OK:
1347 self.sendPIREP(pirep)
1348 elif result==1:
1349 self.viewPIREP(pirep)
1350 else:
1351 break
1352
1353 dialog.hide()
1354
1355 def _getLoadPirepDialog(self):
1356 """Get the PIREP loading file chooser dialog.
1357
1358 If it is not created yet, it will be created."""
1359 if self._loadPIREPDialog is None:
1360 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1361 xstr("loadPIREP_browser_title"),
1362 action = FILE_CHOOSER_ACTION_OPEN,
1363 buttons = (gtk.STOCK_CANCEL,
1364 RESPONSETYPE_CANCEL,
1365 gtk.STOCK_OK, RESPONSETYPE_OK),
1366 parent = self._mainWindow)
1367 dialog.set_modal(True)
1368
1369
1370 filter = gtk.FileFilter()
1371 filter.set_name(xstr("file_filter_pireps"))
1372 filter.add_pattern("*.pirep")
1373 dialog.add_filter(filter)
1374
1375 filter = gtk.FileFilter()
1376 filter.set_name(xstr("file_filter_all"))
1377 filter.add_pattern("*.*")
1378 dialog.add_filter(filter)
1379
1380 self._loadPIREPDialog = dialog
1381
1382 return self._loadPIREPDialog
1383
1384 def _getSendLoadedDialog(self, pirep):
1385 """Get a dialog displaying the main information of the flight from the
1386 PIREP and providing Cancel and Send buttons."""
1387 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1388 xstr("loadPIREP_send_title"),
1389 parent = self._mainWindow,
1390 flags = DIALOG_MODAL)
1391
1392 contentArea = dialog.get_content_area()
1393
1394 label = gtk.Label(xstr("loadPIREP_send_help"))
1395 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1396 xscale = 0.0, yscale = 0.0)
1397 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1398 padding_left = 48, padding_right = 48)
1399 alignment.add(label)
1400 contentArea.pack_start(alignment, False, False, 8)
1401
1402 table = gtk.Table(5, 2)
1403 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1404 xscale = 0.0, yscale = 0.0)
1405 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1406 padding_left = 48, padding_right = 48)
1407 table.set_row_spacings(4)
1408 table.set_col_spacings(16)
1409 tableAlignment.add(table)
1410 contentArea.pack_start(tableAlignment, True, True, 8)
1411
1412 bookedFlight = pirep.bookedFlight
1413
1414 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1415 label.set_use_markup(True)
1416 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1417 xscale = 0.0, yscale = 0.0)
1418 labelAlignment.add(label)
1419 table.attach(labelAlignment, 0, 1, 0, 1)
1420
1421 label = gtk.Label(bookedFlight.callsign)
1422 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1423 xscale = 0.0, yscale = 0.0)
1424 labelAlignment.add(label)
1425 table.attach(labelAlignment, 1, 2, 0, 1)
1426
1427 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1428 label.set_use_markup(True)
1429 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1430 xscale = 0.0, yscale = 0.0)
1431 labelAlignment.add(label)
1432 table.attach(labelAlignment, 0, 1, 1, 2)
1433
1434 label = gtk.Label(str(bookedFlight.departureTime.date()))
1435 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1436 xscale = 0.0, yscale = 0.0)
1437 labelAlignment.add(label)
1438 table.attach(labelAlignment, 1, 2, 1, 2)
1439
1440 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1441 label.set_use_markup(True)
1442 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1443 xscale = 0.0, yscale = 0.0)
1444 labelAlignment.add(label)
1445 table.attach(labelAlignment, 0, 1, 2, 3)
1446
1447 label = gtk.Label(bookedFlight.departureICAO)
1448 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1449 xscale = 0.0, yscale = 0.0)
1450 labelAlignment.add(label)
1451 table.attach(labelAlignment, 1, 2, 2, 3)
1452
1453 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1454 label.set_use_markup(True)
1455 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1456 xscale = 0.0, yscale = 0.0)
1457 labelAlignment.add(label)
1458 table.attach(labelAlignment, 0, 1, 3, 4)
1459
1460 label = gtk.Label(bookedFlight.arrivalICAO)
1461 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1462 xscale = 0.0, yscale = 0.0)
1463 labelAlignment.add(label)
1464 table.attach(labelAlignment, 1, 2, 3, 4)
1465
1466 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1467 label.set_use_markup(True)
1468 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1469 xscale = 0.0, yscale = 0.0)
1470 labelAlignment.add(label)
1471 table.attach(labelAlignment, 0, 1, 4, 5)
1472
1473 rating = pirep.rating
1474 label = gtk.Label()
1475 if rating<0:
1476 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1477 else:
1478 label.set_text("%.1f %%" % (rating,))
1479
1480 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1481 xscale = 0.0, yscale = 0.0)
1482 labelAlignment.add(label)
1483 table.attach(labelAlignment, 1, 2, 4, 5)
1484
1485 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1486 dialog.add_button(xstr("viewPIREP"), 1)
1487 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1488
1489 return dialog
1490
1491 def sendPIREP(self, pirep, callback = None):
1492 """Send the given PIREP."""
1493 self.beginBusy(xstr("sendPIREP_busy"))
1494 self._sendPIREPCallback = callback
1495 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1496
1497 def _pirepSentCallback(self, returned, result):
1498 """Callback for the PIREP sending result."""
1499 gobject.idle_add(self._handlePIREPSent, returned, result)
1500
1501 def _handlePIREPSent(self, returned, result):
1502 """Callback for the PIREP sending result."""
1503 self.endBusy()
1504 secondaryMarkup = None
1505 type = MESSAGETYPE_ERROR
1506 if returned:
1507 if result.success:
1508 type = MESSAGETYPE_INFO
1509 messageFormat = xstr("sendPIREP_success")
1510 secondaryMarkup = xstr("sendPIREP_success_sec")
1511 elif result.alreadyFlown:
1512 messageFormat = xstr("sendPIREP_already")
1513 secondaryMarkup = xstr("sendPIREP_already_sec")
1514 elif result.notAvailable:
1515 messageFormat = xstr("sendPIREP_notavail")
1516 else:
1517 messageFormat = xstr("sendPIREP_unknown")
1518 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1519 else:
1520 print "PIREP sending failed", result
1521 messageFormat = xstr("sendPIREP_failed")
1522 secondaryMarkup = xstr("sendPIREP_failed_sec")
1523
1524 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1525 type = type, message_format = messageFormat)
1526 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1527 dialog.set_title(WINDOW_TITLE_BASE)
1528 if secondaryMarkup is not None:
1529 dialog.format_secondary_markup(secondaryMarkup)
1530
1531 dialog.run()
1532 dialog.hide()
1533
1534 callback = self._sendPIREPCallback
1535 self._sendPIREPCallback = None
1536 if callback is not None:
1537 callback(returned, result)
1538
1539 def sendBugReport(self, summary, description, email, callback = None):
1540 """Send the bug report with the given data."""
1541 description += "\n\n" + ("=" * 40)
1542 description += "\n\nThe contents of the log:\n\n"
1543
1544 for (timestampString, text) in self._logger.lines:
1545 description += unicode(formatFlightLogLine(timestampString, text))
1546
1547 description += "\n\n" + ("=" * 40)
1548 description += "\n\nThe contents of the debug log:\n\n"
1549
1550 buffer = self._debugLogView.get_buffer()
1551 description += buffer.get_text(buffer.get_start_iter(),
1552 buffer.get_end_iter(), True)
1553
1554 self.beginBusy(xstr("sendBugReport_busy"))
1555 self._sendBugReportCallback = callback
1556 self.webHandler.sendBugReport(self._bugReportSentCallback,
1557 summary, description, email)
1558
1559 def _cefInitialized(self):
1560 """Called when CEF has been initialized."""
1561 self._acars.start()
1562 cef.initializeSimBrief()
1563
1564 def _bugReportSentCallback(self, returned, result):
1565 """Callback function for the bug report sending result."""
1566 gobject.idle_add(self._handleBugReportSent, returned, result)
1567
1568 def _handleBugReportSent(self, returned, result):
1569 """Callback for the bug report sending result."""
1570 self.endBusy()
1571 secondaryMarkup = None
1572 type = MESSAGETYPE_ERROR
1573 if returned:
1574 if result.success:
1575 type = MESSAGETYPE_INFO
1576 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1577 secondaryMarkup = xstr("sendBugReport_success_sec")
1578 else:
1579 messageFormat = xstr("sendBugReport_error")
1580 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1581 else:
1582 messageFormat = xstr("sendBugReport_error")
1583 secondaryMarkup = xstr("sendBugReport_error_sec")
1584
1585 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1586 type = type, message_format = messageFormat)
1587 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1588 dialog.set_title(WINDOW_TITLE_BASE)
1589 if secondaryMarkup is not None:
1590 dialog.format_secondary_markup(secondaryMarkup)
1591
1592 dialog.run()
1593 dialog.hide()
1594
1595 callback = self._sendBugReportCallback
1596 self._sendBugReportCallback = None
1597 if callback is not None:
1598 callback(returned, result)
1599
1600 def _listenHotkeys(self):
1601 """Setup the hotkeys based on the configuration."""
1602 if self._hotkeySetID is None and self._simulator is not None:
1603 self._pilotHotkeyIndex = None
1604 self._checklistHotkeyIndex = None
1605
1606 hotkeys = []
1607
1608 config = self.config
1609 if config.enableSounds and config.pilotControlsSounds:
1610 self._pilotHotkeyIndex = len(hotkeys)
1611 hotkeys.append(config.pilotHotkey)
1612
1613 if config.enableChecklists:
1614 self._checklistHotkeyIndex = len(hotkeys)
1615 hotkeys.append(config.checklistHotkey)
1616
1617 if hotkeys:
1618 self._hotkeySetID = \
1619 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1620
1621 def _clearHotkeys(self):
1622 """Clear the hotkeys."""
1623 if self._hotkeySetID is not None:
1624 self._hotkeySetID=None
1625 self._simulator.clearHotkeys()
1626
1627 def _handleHotkeys(self, id, hotkeys):
1628 """Handle the hotkeys."""
1629 if id==self._hotkeySetID:
1630 for index in hotkeys:
1631 if index==self._pilotHotkeyIndex:
1632 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1633 self._flight.pilotHotkeyPressed()
1634 elif index==self._checklistHotkeyIndex:
1635 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1636 self._flight.checklistHotkeyPressed()
1637 else:
1638 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1639
1640 def _showManual(self, menuitem):
1641 """Show the user's manual."""
1642 webbrowser.open(url ="file://" +
1643 os.path.join(self._programDirectory, "doc", "manual",
1644 getLanguage(), "index.html"),
1645 new = 1)
1646
1647 def _showAbout(self, menuitem):
1648 """Show the about dialog."""
1649 dialog = self._getAboutDialog()
1650 dialog.show_all()
1651 dialog.run()
1652 dialog.hide()
1653
1654 def _getAboutDialog(self):
1655 """Get the about dialog.
1656
1657 If it does not exist yet, it will be created."""
1658 if self._aboutDialog is None:
1659 dialog = gtk.AboutDialog()
1660 dialog.set_transient_for(self._mainWindow)
1661 dialog.set_modal(True)
1662
1663 logoPath = os.path.join(self._programDirectory, "logo.png")
1664 logo = pixbuf_new_from_file(logoPath)
1665 dialog.set_logo(logo)
1666
1667 dialog.set_program_name(PROGRAM_NAME)
1668 dialog.set_version(const.VERSION)
1669 dialog.set_copyright("(c) 2012 by István Váradi")
1670 dialog.set_website("http://mlx.varadiistvan.hu")
1671 dialog.set_website_label(xstr("about_website"))
1672
1673 isHungarian = getLanguage()=="hu"
1674 authors = []
1675 for (familyName, firstName, role) in GUI._authors:
1676 author = "%s %s" % \
1677 (familyName if isHungarian else firstName,
1678 firstName if isHungarian else familyName)
1679 role = xstr("about_role_" + role)
1680 authors.append(author + " (" + role + ")")
1681 dialog.set_authors(authors)
1682
1683 dialog.set_license(xstr("about_license"))
1684
1685 if not pygobject:
1686 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1687
1688 self._aboutDialog = dialog
1689
1690 return self._aboutDialog
1691
1692 def _showAboutURL(self, dialog, link, user_data):
1693 """Show the about URL."""
1694 webbrowser.open(url = link, new = 1)
1695
1696 def _setTakeoffAntiIceOn(self, value):
1697 """Set the anti-ice on indicator."""
1698 self._wizard.takeoffAntiIceOn = value
1699
1700 def _setLandingAntiIceOn(self, value):
1701 """Set the anti-ice on indicator."""
1702 self._wizard.landingAntiIceOn = value
1703
1704 def _getCredentialsCallback(self):
1705 """Called when the web handler asks for the credentials."""
1706 # FIXME: this is almost the same as
1707 # SimBriefSetupPage._getCredentialsCallback
1708 with self._credentialsCondition:
1709 self._credentialsAvailable = False
1710
1711 gobject.idle_add(self._getCredentials)
1712
1713 while not self._credentialsAvailable:
1714 self._credentialsCondition.wait()
1715
1716 return (self._credentialsUserName, self._credentialsPassword)
1717
1718 def _getCredentials(self):
1719 """Get the credentials."""
1720 # FIXME: this is almost the same as
1721 # SimBriefSetupPage._getCredentials
1722 with self._credentialsCondition:
1723 config = self.config
1724
1725 dialog = CredentialsDialog(self, config.pilotID, config.password,
1726 xstr("login_title"),
1727 xstr("button_cancel"),
1728 xstr("button_ok"),
1729 xstr("label_pilotID"),
1730 xstr("login_pilotID_tooltip"),
1731 xstr("label_password"),
1732 xstr("login_password_tooltip"),
1733 xstr("login_info"),
1734 config.rememberPassword,
1735 xstr("remember_password"),
1736 xstr("login_remember_tooltip"))
1737 response = dialog.run()
1738
1739 if response==RESPONSETYPE_OK:
1740 self._credentialsUserName = dialog.userName
1741 self._credentialsPassword = dialog.password
1742 rememberPassword = dialog.rememberPassword
1743
1744 config.pilotID = self._credentialsUserName
1745
1746 config.password = \
1747 self._credentialsPassword if rememberPassword else ""
1748 config.rememberPassword = rememberPassword
1749
1750 config.save()
1751 else:
1752 self._credentialsUserName = None
1753 self._credentialsPassword = None
1754
1755 self._credentialsAvailable = True
1756 self._credentialsCondition.notify()
1757
1758 def _updateDone(self):
1759 """Called when the update is done.
1760
1761 It checks if we already know the PID, and if not, asks the user whether
1762 to register."""
1763 if not self.config.pilotID and not self.config.password:
1764 dialog = gtk.MessageDialog(parent = self._mainWindow,
1765 type = MESSAGETYPE_QUESTION,
1766 message_format = xstr("register_ask"))
1767
1768 dialog.set_title(WINDOW_TITLE_BASE)
1769 dialog.format_secondary_markup(xstr("register_ask_sec"))
1770
1771 dialog.add_button(xstr("button_cancel"), 0)
1772 dialog.add_button(xstr("button_register"), 1)
1773 dialog.set_default_response(1)
1774
1775 result = dialog.run()
1776 dialog.hide()
1777 if result == 1:
1778 self._wizard.jumpPage("register")
Note: See TracBrowser for help on using the repository browser.