source: src/mlx/gui/gui.py@ 202:aee91ecda48a

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

Made some of the windows non-resizable

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