source: src/mlx/gui/gui.py@ 196:13494d36771a

Last change on this file since 196:13494d36771a was 184:0a000ef19c3a, checked in by István Váradi <ivaradi@…>, 13 years ago

Added support for the entrance exam

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