source: src/mlx/gui/gui.py@ 170:7cda0cc74e19

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

The background sounds play back properly

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