source: src/mlx/gui/gui.py@ 180:a126eca82a37

Last change on this file since 180:a126eca82a37 was 178:98121552b435, checked in by István Váradi <ivaradi@…>, 13 years ago

Allocating only as many hotkeys as really needed

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