source: src/mlx/gui/gui.py@ 206:35d44ec6e2be

Last change on this file since 206:35d44ec6e2be was 204:6616046534cf, checked in by István Váradi <ivaradi@…>, 13 years ago

Minor fixes, additions to the documents

File size: 43.4 KB
Line 
1# The main file for the GUI
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
14
15import mlx.const as const
16import mlx.fs as fs
17import mlx.flight as flight
18import mlx.logger as logger
19import mlx.acft as acft
20import mlx.web as web
21import mlx.singleton as singleton
22from mlx.i18n import xstr
23from mlx.pirep import PIREP
24
25import time
26import threading
27import sys
28import datetime
29
30#------------------------------------------------------------------------------
31
32class GUI(fs.ConnectionListener):
33 """The main GUI class."""
34 @staticmethod
35 def _formatFlightLogLine(timeStr, line):
36 """Format the given line for flight logging."""
37 if timeStr is not None:
38 line = timeStr + ": " + line
39 return line + "\n"
40
41 def __init__(self, programDirectory, config):
42 """Construct the GUI."""
43 gobject.threads_init()
44
45 self._programDirectory = programDirectory
46 self.config = config
47 self._connecting = False
48 self._reconnecting = False
49 self._connected = False
50 self._logger = logger.Logger(self)
51 self._flight = None
52 self._simulator = None
53 self._monitoring = False
54
55 self._fleet = None
56
57 self._fleetCallback = None
58
59 self._updatePlaneCallback = None
60 self._updatePlaneTailNumber = None
61 self._updatePlaneStatus = None
62 self._updatePlaneGateNumber = None
63
64 self._stdioLock = threading.Lock()
65 self._stdioText = ""
66 self._stdioStartingLine = True
67
68 self._sendPIREPCallback = None
69
70 self.webHandler = web.Handler()
71 self.webHandler.start()
72
73 self.toRestart = False
74
75 def build(self, iconDirectory):
76 """Build the GUI."""
77
78 self._mainWindow = window = gtk.Window()
79 window.set_title(WINDOW_TITLE_BASE)
80 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
81 window.set_resizable(False)
82 window.connect("delete-event",
83 lambda a, b: self.hideMainWindow())
84 window.connect("window-state-event", self._handleMainWindowState)
85 accelGroup = gtk.AccelGroup()
86 window.add_accel_group(accelGroup)
87
88 mainVBox = gtk.VBox()
89 window.add(mainVBox)
90
91 self._preferences = Preferences(self)
92 self._checklistEditor = ChecklistEditor(self)
93
94 menuBar = self._buildMenuBar(accelGroup)
95 mainVBox.pack_start(menuBar, False, False, 0)
96
97 self._notebook = gtk.Notebook()
98 mainVBox.pack_start(self._notebook, True, True, 4)
99
100 self._wizard = Wizard(self)
101 label = gtk.Label(xstr("tab_flight"))
102 label.set_use_underline(True)
103 label.set_tooltip_text(xstr("tab_flight_tooltip"))
104 self._notebook.append_page(self._wizard, label)
105
106 self._flightInfo = FlightInfo(self)
107 label = gtk.Label(xstr("tab_flight_info"))
108 label.set_use_underline(True)
109 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
110 self._notebook.append_page(self._flightInfo, label)
111 self._flightInfo.disable()
112
113 self._weightHelp = WeightHelp(self)
114 label = gtk.Label(xstr("tab_weight_help"))
115 label.set_use_underline(True)
116 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
117 self._notebook.append_page(self._weightHelp, label)
118
119 (logWidget, self._logView) = self._buildLogWidget()
120 label = gtk.Label(xstr("tab_log"))
121 label.set_use_underline(True)
122 label.set_tooltip_text(xstr("tab_log_tooltip"))
123 self._notebook.append_page(logWidget, label)
124
125 self._fleetGateStatus = FleetGateStatus(self)
126 label = gtk.Label(xstr("tab_gates"))
127 label.set_use_underline(True)
128 label.set_tooltip_text(xstr("tab_gates_tooltip"))
129 self._notebook.append_page(self._fleetGateStatus, label)
130
131 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
132 self._debugLogWidget.show_all()
133
134 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
135
136 self._statusbar = Statusbar()
137 mainVBox.pack_start(self._statusbar, False, False, 0)
138
139 self._notebook.connect("switch-page", self._notebookPageSwitch)
140
141 self._monitorWindow = MonitorWindow(self, iconDirectory)
142 self._monitorWindow.add_accel_group(accelGroup)
143 self._monitorWindowX = None
144 self._monitorWindowY = None
145 self._selfToggling = False
146
147 window.show_all()
148 self._wizard.grabDefault()
149 self._weightHelp.reset()
150 self._weightHelp.disable()
151
152 self._statusIcon = StatusIcon(iconDirectory, self)
153
154 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
155 else gdk.WATCH)
156
157 self._loadPIREPDialog = None
158 self._lastLoadedPIREP = None
159
160 self._hotkeySetID = None
161 self._pilotHotkeyIndex = None
162 self._checklistHotkeyIndex = None
163
164 @property
165 def mainWindow(self):
166 """Get the main window of the GUI."""
167 return self._mainWindow
168
169 @property
170 def logger(self):
171 """Get the logger used by us."""
172 return self._logger
173
174 @property
175 def simulator(self):
176 """Get the simulator used by us."""
177 return self._simulator
178
179 @property
180 def flight(self):
181 """Get the flight being performed."""
182 return self._flight
183
184 @property
185 def entranceExam(self):
186 """Get whether an entrance exam is about to be taken."""
187 return self._wizard.entranceExam
188
189 @property
190 def loginResult(self):
191 """Get the result of the login."""
192 return self._wizard.loginResult
193
194 @property
195 def bookedFlight(self):
196 """Get the booked flight selected, if any."""
197 return self._wizard.bookedFlight
198
199 @property
200 def cargoWeight(self):
201 """Get the cargo weight."""
202 return self._wizard.cargoWeight
203
204 @property
205 def zfw(self):
206 """Get Zero-Fuel Weight calculated for the current flight."""
207 return self._wizard.zfw
208
209 @property
210 def filedCruiseAltitude(self):
211 """Get cruise altitude filed for the current flight."""
212 return self._wizard.filedCruiseAltitude
213
214 @property
215 def cruiseAltitude(self):
216 """Get cruise altitude set for the current flight."""
217 return self._wizard.cruiseAltitude
218
219 @property
220 def route(self):
221 """Get the flight route."""
222 return self._wizard.route
223
224 @property
225 def departureMETAR(self):
226 """Get the METAR of the deprature airport."""
227 return self._wizard.departureMETAR
228
229 @property
230 def arrivalMETAR(self):
231 """Get the METAR of the deprature airport."""
232 return self._wizard.arrivalMETAR
233
234 @property
235 def departureRunway(self):
236 """Get the name of the departure runway."""
237 return self._wizard.departureRunway
238
239 @property
240 def sid(self):
241 """Get the SID."""
242 return self._wizard.sid
243
244 @property
245 def v1(self):
246 """Get the V1 speed calculated for the flight."""
247 return self._wizard.v1
248
249 @property
250 def vr(self):
251 """Get the Vr speed calculated for the flight."""
252 return self._wizard.vr
253
254 @property
255 def v2(self):
256 """Get the V2 speed calculated for the flight."""
257 return self._wizard.v2
258
259 @property
260 def arrivalRunway(self):
261 """Get the arrival runway."""
262 return self._wizard.arrivalRunway
263
264 @property
265 def star(self):
266 """Get the STAR."""
267 return self._wizard.star
268
269 @property
270 def transition(self):
271 """Get the transition."""
272 return self._wizard.transition
273
274 @property
275 def approachType(self):
276 """Get the approach type."""
277 return self._wizard.approachType
278
279 @property
280 def vref(self):
281 """Get the Vref speed calculated for the flight."""
282 return self._wizard.vref
283
284 @property
285 def flightType(self):
286 """Get the flight type."""
287 return self._wizard.flightType
288
289 @property
290 def online(self):
291 """Get whether the flight was online or not."""
292 return self._wizard.online
293
294 @property
295 def comments(self):
296 """Get the comments."""
297 return self._flightInfo.comments
298
299 @property
300 def flightDefects(self):
301 """Get the flight defects."""
302 return self._flightInfo.flightDefects
303
304 @property
305 def delayCodes(self):
306 """Get the delay codes."""
307 return self._flightInfo.delayCodes
308
309 def run(self):
310 """Run the GUI."""
311 if self.config.autoUpdate:
312 self._updater = Updater(self,
313 self._programDirectory,
314 self.config.updateURL,
315 self._mainWindow)
316 self._updater.start()
317
318 singleton.raiseCallback = self.raiseCallback
319 gtk.main()
320 singleton.raiseCallback = None
321
322 self._disconnect()
323
324 def connected(self, fsType, descriptor):
325 """Called when we have connected to the simulator."""
326 self._connected = True
327 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
328 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
329 "Welcome to MAVA Logger X " + const.VERSION)
330 gobject.idle_add(self._handleConnected, fsType, descriptor)
331
332 def _handleConnected(self, fsType, descriptor):
333 """Called when the connection to the simulator has succeeded."""
334 self._statusbar.updateConnection(self._connecting, self._connected)
335 self.endBusy()
336 if not self._reconnecting:
337 self._wizard.connected(fsType, descriptor)
338 self._reconnecting = False
339 self._listenHotkeys()
340
341 def connectionFailed(self):
342 """Called when the connection failed."""
343 self._logger.untimedMessage("Connection to the simulator failed")
344 gobject.idle_add(self._connectionFailed)
345
346 def _connectionFailed(self):
347 """Called when the connection failed."""
348 self.endBusy()
349 self._statusbar.updateConnection(self._connecting, self._connected)
350
351 dialog = gtk.MessageDialog(parent = self._mainWindow,
352 type = MESSAGETYPE_ERROR,
353 message_format = xstr("conn_failed"))
354
355 dialog.set_title(WINDOW_TITLE_BASE)
356 dialog.format_secondary_markup(xstr("conn_failed_sec"))
357
358 dialog.add_button(xstr("button_cancel"), 0)
359 dialog.add_button(xstr("button_tryagain"), 1)
360 dialog.set_default_response(1)
361
362 result = dialog.run()
363 dialog.hide()
364 if result == 1:
365 self.beginBusy(xstr("connect_busy"))
366 self._simulator.reconnect()
367 else:
368 self.reset()
369
370 def disconnected(self):
371 """Called when we have disconnected from the simulator."""
372 self._connected = False
373 self._logger.untimedMessage("Disconnected from the simulator")
374
375 gobject.idle_add(self._disconnected)
376
377 def _disconnected(self):
378 """Called when we have disconnected from the simulator unexpectedly."""
379 self._statusbar.updateConnection(self._connecting, self._connected)
380
381 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
382 message_format = xstr("conn_broken"),
383 parent = self._mainWindow)
384 dialog.set_title(WINDOW_TITLE_BASE)
385 dialog.format_secondary_markup(xstr("conn_broken_sec"))
386
387 dialog.add_button(xstr("button_cancel"), 0)
388 dialog.add_button(xstr("button_reconnect"), 1)
389 dialog.set_default_response(1)
390
391 result = dialog.run()
392 dialog.hide()
393 if result == 1:
394 self.beginBusy(xstr("connect_busy"))
395 self._reconnecting = True
396 self._simulator.reconnect()
397 else:
398 self.reset()
399
400 def enableFlightInfo(self):
401 """Enable the flight info tab."""
402 self._flightInfo.enable()
403
404 def reset(self):
405 """Reset the GUI."""
406 self._disconnect()
407
408 self._flightInfo.reset()
409 self._flightInfo.disable()
410 self.resetFlightStatus()
411
412 self._weightHelp.reset()
413 self._weightHelp.disable()
414 self._wizard.reset()
415 self._notebook.set_current_page(0)
416
417 self._logView.get_buffer().set_text("")
418
419 def _disconnect(self, closingMessage = None, duration = 3):
420 """Disconnect from the simulator if connected."""
421 self.stopMonitoring()
422 self._clearHotkeys()
423
424 if self._connected:
425 if closingMessage is None:
426 self._flight.simulator.disconnect()
427 else:
428 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
429 closingMessage, duration,
430 disconnect = True)
431 self._connected = False
432
433 self._connecting = False
434 self._reconnecting = False
435 self._statusbar.updateConnection(False, False)
436 self._weightHelp.disable()
437
438 return True
439
440 def addFlightLogLine(self, timeStr, line):
441 """Write the given message line to the log."""
442 gobject.idle_add(self._writeLog,
443 GUI._formatFlightLogLine(timeStr, line),
444 self._logView)
445
446 def updateFlightLogLine(self, index, timeStr, line):
447 """Update the line with the given index."""
448 gobject.idle_add(self._updateFlightLogLine, index,
449 GUI._formatFlightLogLine(timeStr, line))
450
451 def _updateFlightLogLine(self, index, line):
452 """Replace the contents of the given line in the log."""
453 buffer = self._logView.get_buffer()
454 startIter = buffer.get_iter_at_line(index)
455 endIter = buffer.get_iter_at_line(index + 1)
456 buffer.delete(startIter, endIter)
457 buffer.insert(startIter, line)
458 self._logView.scroll_mark_onscreen(buffer.get_insert())
459
460 def check(self, flight, aircraft, logger, oldState, state):
461 """Update the data."""
462 gobject.idle_add(self._monitorWindow.setData, state)
463 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
464
465 def resetFlightStatus(self):
466 """Reset the status of the flight."""
467 self._statusbar.resetFlightStatus()
468 self._statusbar.updateTime()
469 self._statusIcon.resetFlightStatus()
470
471 def setStage(self, stage):
472 """Set the stage of the flight."""
473 gobject.idle_add(self._setStage, stage)
474
475 def _setStage(self, stage):
476 """Set the stage of the flight."""
477 self._statusbar.setStage(stage)
478 self._statusIcon.setStage(stage)
479 self._wizard.setStage(stage)
480 if stage==const.STAGE_END:
481 self._disconnect(closingMessage =
482 "Flight plan closed. Welcome to %s" % \
483 (self.bookedFlight.arrivalICAO,),
484 duration = 5)
485
486 def setRating(self, rating):
487 """Set the rating of the flight."""
488 gobject.idle_add(self._setRating, rating)
489
490 def _setRating(self, rating):
491 """Set the rating of the flight."""
492 self._statusbar.setRating(rating)
493 self._statusIcon.setRating(rating)
494
495 def setNoGo(self, reason):
496 """Set the rating of the flight to No-Go with the given reason."""
497 gobject.idle_add(self._setNoGo, reason)
498
499 def _setNoGo(self, reason):
500 """Set the rating of the flight."""
501 self._statusbar.setNoGo(reason)
502 self._statusIcon.setNoGo(reason)
503
504 def _handleMainWindowState(self, window, event):
505 """Hande a change in the state of the window"""
506 iconified = gdk.WindowState.ICONIFIED if pygobject \
507 else gdk.WINDOW_STATE_ICONIFIED
508 if self.config.hideMinimizedWindow and \
509 (event.changed_mask&iconified)!=0 and \
510 (event.new_window_state&iconified)!=0:
511 self.hideMainWindow(savePosition = False)
512
513 def raiseCallback(self):
514 """Callback for the singleton handling code."""
515 gobject.idle_add(self.raiseMainWindow)
516
517 def raiseMainWindow(self):
518 """SHow the main window if invisible, and raise it."""
519 if not self._mainWindow.get_visible():
520 self.showMainWindow()
521 self._mainWindow.present()
522
523 def hideMainWindow(self, savePosition = True):
524 """Hide the main window and save its position."""
525 if savePosition:
526 (self._mainWindowX, self._mainWindowY) = \
527 self._mainWindow.get_window().get_root_origin()
528 else:
529 self._mainWindowX = self._mainWindowY = None
530 self._mainWindow.hide()
531 self._statusIcon.mainWindowHidden()
532 return True
533
534 def showMainWindow(self):
535 """Show the main window at its former position."""
536 if self._mainWindowX is not None and self._mainWindowY is not None:
537 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
538
539 self._mainWindow.show()
540 self._mainWindow.deiconify()
541
542 self._statusIcon.mainWindowShown()
543
544 def toggleMainWindow(self):
545 """Toggle the main window."""
546 if self._mainWindow.get_visible():
547 self.hideMainWindow()
548 else:
549 self.showMainWindow()
550
551 def hideMonitorWindow(self, savePosition = True):
552 """Hide the monitor window."""
553 if savePosition:
554 (self._monitorWindowX, self._monitorWindowY) = \
555 self._monitorWindow.get_window().get_root_origin()
556 else:
557 self._monitorWindowX = self._monitorWindowY = None
558 self._monitorWindow.hide()
559 self._statusIcon.monitorWindowHidden()
560 if self._showMonitorMenuItem.get_active():
561 self._selfToggling = True
562 self._showMonitorMenuItem.set_active(False)
563 return True
564
565 def showMonitorWindow(self):
566 """Show the monitor window."""
567 if self._monitorWindowX is not None and self._monitorWindowY is not None:
568 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
569 self._monitorWindow.show_all()
570 self._statusIcon.monitorWindowShown()
571 if not self._showMonitorMenuItem.get_active():
572 self._selfToggling = True
573 self._showMonitorMenuItem.set_active(True)
574
575 def _toggleMonitorWindow(self, menuItem):
576 if self._selfToggling:
577 self._selfToggling = False
578 elif self._monitorWindow.get_visible():
579 self.hideMonitorWindow()
580 else:
581 self.showMonitorWindow()
582
583 def restart(self):
584 """Quit and restart the application."""
585 self.toRestart = True
586 self._quit(force = True)
587
588 def flushStdIO(self):
589 """Flush any text to the standard error that could not be logged."""
590 if self._stdioText:
591 sys.__stderr__.write(self._stdioText)
592
593 def writeStdIO(self, text):
594 """Write the given text into standard I/O log."""
595 with self._stdioLock:
596 self._stdioText += text
597
598 gobject.idle_add(self._writeStdIO)
599
600 def beginBusy(self, message):
601 """Begin a period of background processing."""
602 self._wizard.set_sensitive(False)
603 self._weightHelp.set_sensitive(False)
604 self._mainWindow.get_window().set_cursor(self._busyCursor)
605 self._statusbar.updateBusyState(message)
606
607 def endBusy(self):
608 """End a period of background processing."""
609 self._mainWindow.get_window().set_cursor(None)
610 self._weightHelp.set_sensitive(True)
611 self._wizard.set_sensitive(True)
612 self._statusbar.updateBusyState(None)
613
614 def initializeWeightHelp(self):
615 """Initialize the weight help tab."""
616 self._weightHelp.reset()
617 self._weightHelp.enable()
618
619 def getFleetAsync(self, callback = None, force = None):
620 """Get the fleet asynchronously."""
621 gobject.idle_add(self.getFleet, callback, force)
622
623 def getFleet(self, callback = None, force = False):
624 """Get the fleet.
625
626 If force is False, and we already have a fleet retrieved,
627 that one will be used."""
628 if self._fleet is None or force:
629 self._fleetCallback = callback
630 self.beginBusy(xstr("fleet_busy"))
631 self.webHandler.getFleet(self._fleetResultCallback)
632 else:
633 callback(self._fleet)
634
635 def _fleetResultCallback(self, returned, result):
636 """Called when the fleet has been queried."""
637 gobject.idle_add(self._handleFleetResult, returned, result)
638
639 def _handleFleetResult(self, returned, result):
640 """Handle the fleet result."""
641 self.endBusy()
642 if returned:
643 self._fleet = result.fleet
644 else:
645 self._fleet = None
646
647 dialog = gtk.MessageDialog(parent = self.mainWindow,
648 type = MESSAGETYPE_ERROR,
649 message_format = xstr("fleet_failed"))
650 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
651 dialog.set_title(WINDOW_TITLE_BASE)
652 dialog.run()
653 dialog.hide()
654
655 callback = self._fleetCallback
656 self._fleetCallback = None
657 if callback is not None:
658 callback(self._fleet)
659 self._fleetGateStatus.handleFleet(self._fleet)
660
661 def updatePlane(self, tailNumber, status,
662 gateNumber = None, callback = None):
663 """Update the status of the given plane."""
664 self.beginBusy(xstr("fleet_update_busy"))
665
666 self._updatePlaneCallback = callback
667
668 self._updatePlaneTailNumber = tailNumber
669 self._updatePlaneStatus = status
670 self._updatePlaneGateNumber = gateNumber
671
672 self.webHandler.updatePlane(self._updatePlaneResultCallback,
673 tailNumber, status, gateNumber)
674
675 def _updatePlaneResultCallback(self, returned, result):
676 """Called when the status of a plane has been updated."""
677 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
678
679 def _handleUpdatePlaneResult(self, returned, result):
680 """Handle the plane update result."""
681 self.endBusy()
682 if returned:
683 success = result.success
684 if success:
685 if self._fleet is not None:
686 self._fleet.updatePlane(self._updatePlaneTailNumber,
687 self._updatePlaneStatus,
688 self._updatePlaneGateNumber)
689 self._fleetGateStatus.handleFleet(self._fleet)
690 else:
691 dialog = gtk.MessageDialog(parent = self.mainWindow,
692 type = MESSAGETYPE_ERROR,
693 message_format = xstr("fleet_update_failed"))
694 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
695 dialog.set_title(WINDOW_TITLE_BASE)
696 dialog.run()
697 dialog.hide()
698
699 success = None
700
701 callback = self._updatePlaneCallback
702 self._updatePlaneCallback = None
703 if callback is not None:
704 callback(success)
705
706 def _writeStdIO(self):
707 """Perform the real writing."""
708 with self._stdioLock:
709 text = self._stdioText
710 self._stdioText = ""
711 if not text: return
712
713 lines = text.splitlines()
714 if text[-1]=="\n":
715 text = ""
716 else:
717 text = lines[-1]
718 lines = lines[:-1]
719
720 now = datetime.datetime.now()
721 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
722
723 for line in lines:
724 #print >> sys.__stdout__, line
725 if self._stdioStartingLine:
726 self._writeLog(timeStr, self._debugLogView)
727 self._writeLog(line + "\n", self._debugLogView)
728 self._stdioStartingLine = True
729
730 if text:
731 #print >> sys.__stdout__, text,
732 if self._stdioStartingLine:
733 self._writeLog(timeStr, self._debugLogView)
734 self._writeLog(text, self._debugLogView)
735 self._stdioStartingLine = False
736
737 def connectSimulator(self, aircraftType):
738 """Connect to the simulator for the first time."""
739 self._logger.reset()
740
741 self._flight = flight.Flight(self._logger, self)
742 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
743 self._flight.aircraftType = aircraftType
744 self._flight.aircraft = acft.Aircraft.create(self._flight)
745 self._flight.aircraft._checkers.append(self)
746
747 if self._simulator is None:
748 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
749 fs.setupMessageSending(self.config, self._simulator)
750 self._setupTimeSync()
751
752 self._flight.simulator = self._simulator
753
754 self.beginBusy(xstr("connect_busy"))
755 self._statusbar.updateConnection(self._connecting, self._connected)
756
757 self._connecting = True
758 self._simulator.connect(self._flight.aircraft)
759
760 def startMonitoring(self):
761 """Start monitoring."""
762 if not self._monitoring:
763 self.simulator.startMonitoring()
764 self._monitoring = True
765
766 def stopMonitoring(self):
767 """Stop monitoring."""
768 if self._monitoring:
769 self.simulator.stopMonitoring()
770 self._monitoring = False
771
772 def _buildMenuBar(self, accelGroup):
773 """Build the main menu bar."""
774 menuBar = gtk.MenuBar()
775
776 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
777 fileMenu = gtk.Menu()
778 fileMenuItem.set_submenu(fileMenu)
779 menuBar.append(fileMenuItem)
780
781 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
782 loadPIREPMenuItem.set_use_stock(True)
783 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
784 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
785 ord(xstr("menu_file_loadPIREP_key")),
786 CONTROL_MASK, ACCEL_VISIBLE)
787 loadPIREPMenuItem.connect("activate", self._loadPIREP)
788 fileMenu.append(loadPIREPMenuItem)
789
790 fileMenu.append(gtk.SeparatorMenuItem())
791
792 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
793 quitMenuItem.set_use_stock(True)
794 quitMenuItem.set_label(xstr("menu_file_quit"))
795 quitMenuItem.add_accelerator("activate", accelGroup,
796 ord(xstr("menu_file_quit_key")),
797 CONTROL_MASK, ACCEL_VISIBLE)
798 quitMenuItem.connect("activate", self._quit)
799 fileMenu.append(quitMenuItem)
800
801 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
802 toolsMenu = gtk.Menu()
803 toolsMenuItem.set_submenu(toolsMenu)
804 menuBar.append(toolsMenuItem)
805
806 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
807 checklistMenuItem.set_use_stock(True)
808 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
809 checklistMenuItem.add_accelerator("activate", accelGroup,
810 ord(xstr("menu_tools_chklst_key")),
811 CONTROL_MASK, ACCEL_VISIBLE)
812 checklistMenuItem.connect("activate", self._editChecklist)
813 toolsMenu.append(checklistMenuItem)
814
815 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
816 prefsMenuItem.set_use_stock(True)
817 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
818 prefsMenuItem.add_accelerator("activate", accelGroup,
819 ord(xstr("menu_tools_prefs_key")),
820 CONTROL_MASK, ACCEL_VISIBLE)
821 prefsMenuItem.connect("activate", self._editPreferences)
822 toolsMenu.append(prefsMenuItem)
823
824 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
825 viewMenu = gtk.Menu()
826 viewMenuItem.set_submenu(viewMenu)
827 menuBar.append(viewMenuItem)
828
829 self._showMonitorMenuItem = gtk.CheckMenuItem()
830 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
831 self._showMonitorMenuItem.set_use_underline(True)
832 self._showMonitorMenuItem.set_active(False)
833 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
834 ord(xstr("menu_view_monitor_key")),
835 CONTROL_MASK, ACCEL_VISIBLE)
836 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
837 viewMenu.append(self._showMonitorMenuItem)
838
839 showDebugMenuItem = gtk.CheckMenuItem()
840 showDebugMenuItem.set_label(xstr("menu_view_debug"))
841 showDebugMenuItem.set_use_underline(True)
842 showDebugMenuItem.set_active(False)
843 showDebugMenuItem.add_accelerator("activate", accelGroup,
844 ord(xstr("menu_view_debug_key")),
845 CONTROL_MASK, ACCEL_VISIBLE)
846 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
847 viewMenu.append(showDebugMenuItem)
848
849 return menuBar
850
851 def _toggleDebugLog(self, menuItem):
852 """Toggle the debug log."""
853 if menuItem.get_active():
854 label = gtk.Label(xstr("tab_debug_log"))
855 label.set_use_underline(True)
856 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
857 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
858 self._notebook.set_current_page(self._debugLogPage)
859 else:
860 self._notebook.remove_page(self._debugLogPage)
861
862 def _buildLogWidget(self):
863 """Build the widget for the log."""
864 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
865
866 alignment.set_padding(padding_top = 8, padding_bottom = 8,
867 padding_left = 16, padding_right = 16)
868
869 logScroller = gtk.ScrolledWindow()
870 # FIXME: these should be constants in common
871 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
872 else gtk.POLICY_AUTOMATIC,
873 gtk.PolicyType.AUTOMATIC if pygobject
874 else gtk.POLICY_AUTOMATIC)
875 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
876 else gtk.SHADOW_IN)
877 logView = gtk.TextView()
878 logView.set_editable(False)
879 logView.set_cursor_visible(False)
880 logScroller.add(logView)
881
882 logBox = gtk.VBox()
883 logBox.pack_start(logScroller, True, True, 0)
884 logBox.set_size_request(-1, 200)
885
886 alignment.add(logBox)
887
888 return (alignment, logView)
889
890 def _writeLog(self, msg, logView):
891 """Write the given message to the log."""
892 buffer = logView.get_buffer()
893 buffer.insert(buffer.get_end_iter(), msg)
894 logView.scroll_mark_onscreen(buffer.get_insert())
895
896 def _quit(self, what = None, force = False):
897 """Quit from the application."""
898 if force:
899 result=RESPONSETYPE_YES
900 else:
901 dialog = gtk.MessageDialog(parent = self._mainWindow,
902 type = MESSAGETYPE_QUESTION,
903 message_format = xstr("quit_question"))
904
905 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
906 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
907
908 dialog.set_title(WINDOW_TITLE_BASE)
909 result = dialog.run()
910 dialog.hide()
911
912 if result==RESPONSETYPE_YES:
913 self._statusIcon.destroy()
914 return gtk.main_quit()
915
916 def _notebookPageSwitch(self, notebook, page, page_num):
917 """Called when the current page of the notebook has changed."""
918 if page_num==0:
919 gobject.idle_add(self._wizard.grabDefault)
920 else:
921 self._mainWindow.set_default(None)
922
923 def _editChecklist(self, menuItem):
924 """Callback for editing the checklists."""
925 self._checklistEditor.run()
926
927 def _editPreferences(self, menuItem):
928 """Callback for editing the preferences."""
929 self._clearHotkeys()
930 self._preferences.run(self.config)
931 self._setupTimeSync()
932 self._listenHotkeys()
933
934 def _setupTimeSync(self):
935 """Enable or disable the simulator time synchronization based on the
936 configuration."""
937 simulator = self._simulator
938 if simulator is not None:
939 if self.config.syncFSTime:
940 simulator.enableTimeSync()
941 else:
942 simulator.disableTimeSync()
943
944 def _loadPIREP(self, menuItem):
945 """Load a PIREP for sending."""
946 dialog = self._getLoadPirepDialog()
947
948 if self._lastLoadedPIREP:
949 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
950 else:
951 pirepDirectory = self.config.pirepDirectory
952 if pirepDirectory is not None:
953 dialog.set_current_folder(pirepDirectory)
954
955 result = dialog.run()
956 dialog.hide()
957
958 if result==RESPONSETYPE_OK:
959 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
960
961 pirep = PIREP.load(self._lastLoadedPIREP)
962 if pirep is None:
963 dialog = gtk.MessageDialog(parent = self._mainWindow,
964 type = MESSAGETYPE_ERROR,
965 message_format = xstr("loadPIREP_failed"))
966 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
967 dialog.set_title(WINDOW_TITLE_BASE)
968 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
969 dialog.run()
970 dialog.hide()
971 else:
972 dialog = self._getSendLoadedDialog(pirep)
973 dialog.show_all()
974 result = dialog.run()
975 dialog.hide()
976
977 if result==RESPONSETYPE_OK:
978 self.sendPIREP(pirep)
979
980 def _getLoadPirepDialog(self):
981 """Get the PIREP loading file chooser dialog.
982
983 If it is not created yet, it will be created."""
984 if self._loadPIREPDialog is None:
985 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
986 xstr("loadPIREP_browser_title"),
987 action = FILE_CHOOSER_ACTION_OPEN,
988 buttons = (gtk.STOCK_CANCEL,
989 RESPONSETYPE_CANCEL,
990 gtk.STOCK_OK, RESPONSETYPE_OK),
991 parent = self._mainWindow)
992 dialog.set_modal(True)
993
994
995 filter = gtk.FileFilter()
996 filter.set_name(xstr("file_filter_pireps"))
997 filter.add_pattern("*.pirep")
998 dialog.add_filter(filter)
999
1000 filter = gtk.FileFilter()
1001 filter.set_name(xstr("file_filter_all"))
1002 filter.add_pattern("*.*")
1003 dialog.add_filter(filter)
1004
1005 self._loadPIREPDialog = dialog
1006
1007 return self._loadPIREPDialog
1008
1009 def _getSendLoadedDialog(self, pirep):
1010 """Get a dialog displaying the main information of the flight from the
1011 PIREP and providing Cancel and Send buttons."""
1012 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1013 xstr("loadPIREP_send_title"),
1014 parent = self._mainWindow,
1015 flags = DIALOG_MODAL)
1016
1017 contentArea = dialog.get_content_area()
1018
1019 label = gtk.Label(xstr("loadPIREP_send_help"))
1020 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1021 xscale = 0.0, yscale = 0.0)
1022 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1023 padding_left = 48, padding_right = 48)
1024 alignment.add(label)
1025 contentArea.pack_start(alignment, False, False, 8)
1026
1027 table = gtk.Table(5, 2)
1028 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1029 xscale = 0.0, yscale = 0.0)
1030 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1031 padding_left = 48, padding_right = 48)
1032 table.set_row_spacings(4)
1033 table.set_col_spacings(16)
1034 tableAlignment.add(table)
1035 contentArea.pack_start(tableAlignment, True, True, 8)
1036
1037 bookedFlight = pirep.bookedFlight
1038
1039 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1040 label.set_use_markup(True)
1041 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1042 xscale = 0.0, yscale = 0.0)
1043 labelAlignment.add(label)
1044 table.attach(labelAlignment, 0, 1, 0, 1)
1045
1046 label = gtk.Label(bookedFlight.callsign)
1047 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1048 xscale = 0.0, yscale = 0.0)
1049 labelAlignment.add(label)
1050 table.attach(labelAlignment, 1, 2, 0, 1)
1051
1052 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1053 label.set_use_markup(True)
1054 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1055 xscale = 0.0, yscale = 0.0)
1056 labelAlignment.add(label)
1057 table.attach(labelAlignment, 0, 1, 1, 2)
1058
1059 label = gtk.Label(str(bookedFlight.departureTime.date()))
1060 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1061 xscale = 0.0, yscale = 0.0)
1062 labelAlignment.add(label)
1063 table.attach(labelAlignment, 1, 2, 1, 2)
1064
1065 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1066 label.set_use_markup(True)
1067 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1068 xscale = 0.0, yscale = 0.0)
1069 labelAlignment.add(label)
1070 table.attach(labelAlignment, 0, 1, 2, 3)
1071
1072 label = gtk.Label(bookedFlight.departureICAO)
1073 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1074 xscale = 0.0, yscale = 0.0)
1075 labelAlignment.add(label)
1076 table.attach(labelAlignment, 1, 2, 2, 3)
1077
1078 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1079 label.set_use_markup(True)
1080 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1081 xscale = 0.0, yscale = 0.0)
1082 labelAlignment.add(label)
1083 table.attach(labelAlignment, 0, 1, 3, 4)
1084
1085 label = gtk.Label(bookedFlight.arrivalICAO)
1086 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1087 xscale = 0.0, yscale = 0.0)
1088 labelAlignment.add(label)
1089 table.attach(labelAlignment, 1, 2, 3, 4)
1090
1091 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1092 label.set_use_markup(True)
1093 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1094 xscale = 0.0, yscale = 0.0)
1095 labelAlignment.add(label)
1096 table.attach(labelAlignment, 0, 1, 4, 5)
1097
1098 rating = pirep.rating
1099 label = gtk.Label()
1100 if rating<0:
1101 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1102 else:
1103 label.set_text("%.1f %%" % (rating,))
1104
1105 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1106 xscale = 0.0, yscale = 0.0)
1107 labelAlignment.add(label)
1108 table.attach(labelAlignment, 1, 2, 4, 5)
1109
1110 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1111 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1112
1113 return dialog
1114
1115 def sendPIREP(self, pirep, callback = None):
1116 """Send the given PIREP."""
1117 self.beginBusy(xstr("sendPIREP_busy"))
1118 self._sendPIREPCallback = callback
1119 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1120
1121 def _pirepSentCallback(self, returned, result):
1122 """Callback for the PIREP sending result."""
1123 gobject.idle_add(self._handlePIREPSent, returned, result)
1124
1125 def _handlePIREPSent(self, returned, result):
1126 """Callback for the PIREP sending result."""
1127 self.endBusy()
1128 secondaryMarkup = None
1129 type = MESSAGETYPE_ERROR
1130 if returned:
1131 if result.success:
1132 type = MESSAGETYPE_INFO
1133 messageFormat = xstr("sendPIREP_success")
1134 secondaryMarkup = xstr("sendPIREP_success_sec")
1135 elif result.alreadyFlown:
1136 messageFormat = xstr("sendPIREP_already")
1137 secondaryMarkup = xstr("sendPIREP_already_sec")
1138 elif result.notAvailable:
1139 messageFormat = xstr("sendPIREP_notavail")
1140 else:
1141 messageFormat = xstr("sendPIREP_unknown")
1142 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1143 else:
1144 print "PIREP sending failed", result
1145 messageFormat = xstr("sendPIREP_failed")
1146 secondaryMarkup = xstr("sendPIREP_failed_sec")
1147
1148 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1149 type = type, message_format = messageFormat)
1150 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1151 dialog.set_title(WINDOW_TITLE_BASE)
1152 if secondaryMarkup is not None:
1153 dialog.format_secondary_markup(secondaryMarkup)
1154
1155 dialog.run()
1156 dialog.hide()
1157
1158 callback = self._sendPIREPCallback
1159 self._sendPIREPCallback = None
1160 if callback is not None:
1161 callback(returned, result)
1162
1163 def _listenHotkeys(self):
1164 """Setup the hotkeys based on the configuration."""
1165 if self._hotkeySetID is None and self._simulator is not None:
1166 self._pilotHotkeyIndex = None
1167 self._checklistHotkeyIndex = None
1168
1169 hotkeys = []
1170
1171 config = self.config
1172 if config.enableSounds and config.pilotControlsSounds:
1173 self._pilotHotkeyIndex = len(hotkeys)
1174 hotkeys.append(config.pilotHotkey)
1175
1176 if config.enableChecklists:
1177 self._checklistHotkeyIndex = len(hotkeys)
1178 hotkeys.append(config.checklistHotkey)
1179
1180 if hotkeys:
1181 self._hotkeySetID = \
1182 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1183
1184 def _clearHotkeys(self):
1185 """Clear the hotkeys."""
1186 if self._hotkeySetID is not None:
1187 self._hotkeySetID=None
1188 self._simulator.clearHotkeys()
1189
1190 def _handleHotkeys(self, id, hotkeys):
1191 """Handle the hotkeys."""
1192 if id==self._hotkeySetID:
1193 for index in hotkeys:
1194 if index==self._pilotHotkeyIndex:
1195 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1196 self._flight.pilotHotkeyPressed()
1197 elif index==self._checklistHotkeyIndex:
1198 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1199 self._flight.checklistHotkeyPressed()
1200 else:
1201 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
Note: See TracBrowser for help on using the repository browser.