source: src/mlx/gui/gui.py@ 182:dd806c3cc18d

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

If the program is started in several instances, the ones after the first one just show the window of the running instance

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