source: src/mlx/gui/gui.py@ 151:a2584357ff6c

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

Added support for saving and loading PIREPs

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