source: src/mlx/gui/gui.py@ 172:565a6ea1f630

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

The beginnings of the checklist editor widget

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