source: src/mlx/gui/gui.py@ 158:9b9f4da1e8f8

Last change on this file since 158:9b9f4da1e8f8 was 152:f0701a18628a, checked in by István Váradi <ivaradi@…>, 13 years ago

Made the sending of the closing message more reliable

File size: 39.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
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, closingMessage = None, duration = 3):
402 """Disconnect from the simulator if connected."""
403 self.stopMonitoring()
404
405 if self._connected:
406 if closingMessage is None:
407 self._flight.simulator.disconnect()
408 else:
409 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
410 closingMessage, duration,
411 disconnect = True)
412 self._connected = False
413
414 self._connecting = False
415 self._reconnecting = False
416 self._statusbar.updateConnection(False, False)
417 self._weightHelp.disable()
418
419 return True
420
421 def addFlightLogLine(self, timeStr, line):
422 """Write the given message line to the log."""
423 gobject.idle_add(self._writeLog,
424 GUI._formatFlightLogLine(timeStr, line),
425 self._logView)
426
427 def updateFlightLogLine(self, index, timeStr, line):
428 """Update the line with the given index."""
429 gobject.idle_add(self._updateFlightLogLine, index,
430 GUI._formatFlightLogLine(timeStr, line))
431
432 def _updateFlightLogLine(self, index, line):
433 """Replace the contents of the given line in the log."""
434 buffer = self._logView.get_buffer()
435 startIter = buffer.get_iter_at_line(index)
436 endIter = buffer.get_iter_at_line(index + 1)
437 buffer.delete(startIter, endIter)
438 buffer.insert(startIter, line)
439 self._logView.scroll_mark_onscreen(buffer.get_insert())
440
441 def check(self, flight, aircraft, logger, oldState, state):
442 """Update the data."""
443 gobject.idle_add(self._monitorWindow.setData, state)
444 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
445
446 def resetFlightStatus(self):
447 """Reset the status of the flight."""
448 self._statusbar.resetFlightStatus()
449 self._statusbar.updateTime()
450 self._statusIcon.resetFlightStatus()
451
452 def setStage(self, stage):
453 """Set the stage of the flight."""
454 gobject.idle_add(self._setStage, stage)
455
456 def _setStage(self, stage):
457 """Set the stage of the flight."""
458 self._statusbar.setStage(stage)
459 self._statusIcon.setStage(stage)
460 self._wizard.setStage(stage)
461 if stage==const.STAGE_END:
462 self._disconnect(closingMessage =
463 "Flight plan closed. Welcome to %s" % \
464 (self.bookedFlight.arrivalICAO,),
465 duration = 5)
466
467 def setRating(self, rating):
468 """Set the rating of the flight."""
469 gobject.idle_add(self._setRating, rating)
470
471 def _setRating(self, rating):
472 """Set the rating of the flight."""
473 self._statusbar.setRating(rating)
474 self._statusIcon.setRating(rating)
475
476 def setNoGo(self, reason):
477 """Set the rating of the flight to No-Go with the given reason."""
478 gobject.idle_add(self._setNoGo, reason)
479
480 def _setNoGo(self, reason):
481 """Set the rating of the flight."""
482 self._statusbar.setNoGo(reason)
483 self._statusIcon.setNoGo(reason)
484
485 def _handleMainWindowState(self, window, event):
486 """Hande a change in the state of the window"""
487 iconified = gdk.WindowState.ICONIFIED if pygobject \
488 else gdk.WINDOW_STATE_ICONIFIED
489 if self.config.hideMinimizedWindow and \
490 (event.changed_mask&iconified)!=0 and \
491 (event.new_window_state&iconified)!=0:
492 self.hideMainWindow(savePosition = False)
493
494 def hideMainWindow(self, savePosition = True):
495 """Hide the main window and save its position."""
496 if savePosition:
497 (self._mainWindowX, self._mainWindowY) = \
498 self._mainWindow.get_window().get_root_origin()
499 else:
500 self._mainWindowX = self._mainWindowY = None
501 self._mainWindow.hide()
502 self._statusIcon.mainWindowHidden()
503 return True
504
505 def showMainWindow(self):
506 """Show the main window at its former position."""
507 if self._mainWindowX is not None and self._mainWindowY is not None:
508 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
509
510 self._mainWindow.show()
511 self._mainWindow.deiconify()
512
513 self._statusIcon.mainWindowShown()
514
515 def toggleMainWindow(self):
516 """Toggle the main window."""
517 if self._mainWindow.get_visible():
518 self.hideMainWindow()
519 else:
520 self.showMainWindow()
521
522 def hideMonitorWindow(self, savePosition = True):
523 """Hide the monitor window."""
524 if savePosition:
525 (self._monitorWindowX, self._monitorWindowY) = \
526 self._monitorWindow.get_window().get_root_origin()
527 else:
528 self._monitorWindowX = self._monitorWindowY = None
529 self._monitorWindow.hide()
530 self._statusIcon.monitorWindowHidden()
531 if self._showMonitorMenuItem.get_active():
532 self._selfToggling = True
533 self._showMonitorMenuItem.set_active(False)
534 return True
535
536 def showMonitorWindow(self):
537 """Show the monitor window."""
538 if self._monitorWindowX is not None and self._monitorWindowY is not None:
539 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
540 self._monitorWindow.show_all()
541 self._statusIcon.monitorWindowShown()
542 if not self._showMonitorMenuItem.get_active():
543 self._selfToggling = True
544 self._showMonitorMenuItem.set_active(True)
545
546 def _toggleMonitorWindow(self, menuItem):
547 if self._selfToggling:
548 self._selfToggling = False
549 elif self._monitorWindow.get_visible():
550 self.hideMonitorWindow()
551 else:
552 self.showMonitorWindow()
553
554 def restart(self):
555 """Quit and restart the application."""
556 self.toRestart = True
557 self._quit(force = True)
558
559 def flushStdIO(self):
560 """Flush any text to the standard error that could not be logged."""
561 if self._stdioText:
562 sys.__stderr__.write(self._stdioText)
563
564 def writeStdIO(self, text):
565 """Write the given text into standard I/O log."""
566 with self._stdioLock:
567 self._stdioText += text
568
569 gobject.idle_add(self._writeStdIO)
570
571 def beginBusy(self, message):
572 """Begin a period of background processing."""
573 self._wizard.set_sensitive(False)
574 self._weightHelp.set_sensitive(False)
575 self._mainWindow.get_window().set_cursor(self._busyCursor)
576 self._statusbar.updateBusyState(message)
577
578 def endBusy(self):
579 """End a period of background processing."""
580 self._mainWindow.get_window().set_cursor(None)
581 self._weightHelp.set_sensitive(True)
582 self._wizard.set_sensitive(True)
583 self._statusbar.updateBusyState(None)
584
585 def initializeWeightHelp(self):
586 """Initialize the weight help tab."""
587 self._weightHelp.reset()
588 self._weightHelp.enable()
589
590 def getFleetAsync(self, callback = None, force = None):
591 """Get the fleet asynchronously."""
592 gobject.idle_add(self.getFleet, callback, force)
593
594 def getFleet(self, callback = None, force = False):
595 """Get the fleet.
596
597 If force is False, and we already have a fleet retrieved,
598 that one will be used."""
599 if self._fleet is None or force:
600 self._fleetCallback = callback
601 self.beginBusy(xstr("fleet_busy"))
602 self.webHandler.getFleet(self._fleetResultCallback)
603 else:
604 callback(self._fleet)
605
606 def _fleetResultCallback(self, returned, result):
607 """Called when the fleet has been queried."""
608 gobject.idle_add(self._handleFleetResult, returned, result)
609
610 def _handleFleetResult(self, returned, result):
611 """Handle the fleet result."""
612 self.endBusy()
613 if returned:
614 self._fleet = result.fleet
615 else:
616 self._fleet = None
617
618 dialog = gtk.MessageDialog(parent = self.mainWindow,
619 type = MESSAGETYPE_ERROR,
620 message_format = xstr("fleet_failed"))
621 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
622 dialog.set_title(WINDOW_TITLE_BASE)
623 dialog.run()
624 dialog.hide()
625
626 callback = self._fleetCallback
627 self._fleetCallback = None
628 if callback is not None:
629 callback(self._fleet)
630 self._fleetGateStatus.handleFleet(self._fleet)
631
632 def updatePlane(self, tailNumber, status,
633 gateNumber = None, callback = None):
634 """Update the status of the given plane."""
635 self.beginBusy(xstr("fleet_update_busy"))
636
637 self._updatePlaneCallback = callback
638
639 self._updatePlaneTailNumber = tailNumber
640 self._updatePlaneStatus = status
641 self._updatePlaneGateNumber = gateNumber
642
643 self.webHandler.updatePlane(self._updatePlaneResultCallback,
644 tailNumber, status, gateNumber)
645
646 def _updatePlaneResultCallback(self, returned, result):
647 """Called when the status of a plane has been updated."""
648 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
649
650 def _handleUpdatePlaneResult(self, returned, result):
651 """Handle the plane update result."""
652 self.endBusy()
653 if returned:
654 success = result.success
655 if success:
656 if self._fleet is not None:
657 self._fleet.updatePlane(self._updatePlaneTailNumber,
658 self._updatePlaneStatus,
659 self._updatePlaneGateNumber)
660 self._fleetGateStatus.handleFleet(self._fleet)
661 else:
662 dialog = gtk.MessageDialog(parent = self.mainWindow,
663 type = MESSAGETYPE_ERROR,
664 message_format = xstr("fleet_update_failed"))
665 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
666 dialog.set_title(WINDOW_TITLE_BASE)
667 dialog.run()
668 dialog.hide()
669
670 success = None
671
672 callback = self._updatePlaneCallback
673 self._updatePlaneCallback = None
674 if callback is not None:
675 callback(success)
676
677 def _writeStdIO(self):
678 """Perform the real writing."""
679 with self._stdioLock:
680 text = self._stdioText
681 self._stdioText = ""
682 if not text: return
683
684 lines = text.splitlines()
685 if text[-1]=="\n":
686 text = ""
687 else:
688 text = lines[-1]
689 lines = lines[:-1]
690
691 for line in lines:
692 #print >> sys.__stdout__, line
693 self._writeLog(line + "\n", self._debugLogView)
694
695 if text:
696 #print >> sys.__stdout__, text,
697 self._writeLog(text, self._debugLogView)
698
699 def connectSimulator(self, aircraftType):
700 """Connect to the simulator for the first time."""
701 self._logger.reset()
702
703 self._flight = flight.Flight(self._logger, self)
704 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
705 self._flight.aircraftType = aircraftType
706 self._flight.aircraft = acft.Aircraft.create(self._flight)
707 self._flight.aircraft._checkers.append(self)
708
709 if self._simulator is None:
710 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
711 fs.setupMessageSending(self.config, self._simulator)
712 self._setupTimeSync()
713
714 self._flight.simulator = self._simulator
715
716 self.beginBusy(xstr("connect_busy"))
717 self._statusbar.updateConnection(self._connecting, self._connected)
718
719 self._connecting = True
720 self._simulator.connect(self._flight.aircraft)
721
722 def startMonitoring(self):
723 """Start monitoring."""
724 if not self._monitoring:
725 self.simulator.startMonitoring()
726 self._monitoring = True
727
728 def stopMonitoring(self):
729 """Stop monitoring."""
730 if self._monitoring:
731 self.simulator.stopMonitoring()
732 self._monitoring = False
733
734 def _buildMenuBar(self, accelGroup):
735 """Build the main menu bar."""
736 menuBar = gtk.MenuBar()
737
738 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
739 fileMenu = gtk.Menu()
740 fileMenuItem.set_submenu(fileMenu)
741 menuBar.append(fileMenuItem)
742
743 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
744 loadPIREPMenuItem.set_use_stock(True)
745 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
746 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
747 ord(xstr("menu_file_loadPIREP_key")),
748 CONTROL_MASK, ACCEL_VISIBLE)
749 loadPIREPMenuItem.connect("activate", self._loadPIREP)
750 fileMenu.append(loadPIREPMenuItem)
751
752 fileMenu.append(gtk.SeparatorMenuItem())
753
754 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
755 quitMenuItem.set_use_stock(True)
756 quitMenuItem.set_label(xstr("menu_file_quit"))
757 quitMenuItem.add_accelerator("activate", accelGroup,
758 ord(xstr("menu_file_quit_key")),
759 CONTROL_MASK, ACCEL_VISIBLE)
760 quitMenuItem.connect("activate", self._quit)
761 fileMenu.append(quitMenuItem)
762
763 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
764 toolsMenu = gtk.Menu()
765 toolsMenuItem.set_submenu(toolsMenu)
766 menuBar.append(toolsMenuItem)
767
768 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
769 prefsMenuItem.set_use_stock(True)
770 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
771 prefsMenuItem.add_accelerator("activate", accelGroup,
772 ord(xstr("menu_tools_prefs_key")),
773 CONTROL_MASK, ACCEL_VISIBLE)
774 prefsMenuItem.connect("activate", self._editPreferences)
775 toolsMenu.append(prefsMenuItem)
776
777 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
778 viewMenu = gtk.Menu()
779 viewMenuItem.set_submenu(viewMenu)
780 menuBar.append(viewMenuItem)
781
782 self._showMonitorMenuItem = gtk.CheckMenuItem()
783 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
784 self._showMonitorMenuItem.set_use_underline(True)
785 self._showMonitorMenuItem.set_active(False)
786 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
787 ord(xstr("menu_view_monitor_key")),
788 CONTROL_MASK, ACCEL_VISIBLE)
789 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
790 viewMenu.append(self._showMonitorMenuItem)
791
792 showDebugMenuItem = gtk.CheckMenuItem()
793 showDebugMenuItem.set_label(xstr("menu_view_debug"))
794 showDebugMenuItem.set_use_underline(True)
795 showDebugMenuItem.set_active(False)
796 showDebugMenuItem.add_accelerator("activate", accelGroup,
797 ord(xstr("menu_view_debug_key")),
798 CONTROL_MASK, ACCEL_VISIBLE)
799 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
800 viewMenu.append(showDebugMenuItem)
801
802 return menuBar
803
804 def _toggleDebugLog(self, menuItem):
805 """Toggle the debug log."""
806 if menuItem.get_active():
807 label = gtk.Label(xstr("tab_debug_log"))
808 label.set_use_underline(True)
809 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
810 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
811 self._notebook.set_current_page(self._debugLogPage)
812 else:
813 self._notebook.remove_page(self._debugLogPage)
814
815 def _buildLogWidget(self):
816 """Build the widget for the log."""
817 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
818
819 alignment.set_padding(padding_top = 8, padding_bottom = 8,
820 padding_left = 16, padding_right = 16)
821
822 logScroller = gtk.ScrolledWindow()
823 # FIXME: these should be constants in common
824 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
825 else gtk.POLICY_AUTOMATIC,
826 gtk.PolicyType.AUTOMATIC if pygobject
827 else gtk.POLICY_AUTOMATIC)
828 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
829 else gtk.SHADOW_IN)
830 logView = gtk.TextView()
831 logView.set_editable(False)
832 logScroller.add(logView)
833
834 logBox = gtk.VBox()
835 logBox.pack_start(logScroller, True, True, 0)
836 logBox.set_size_request(-1, 200)
837
838 alignment.add(logBox)
839
840 return (alignment, logView)
841
842 def _writeLog(self, msg, logView):
843 """Write the given message to the log."""
844 buffer = logView.get_buffer()
845 buffer.insert(buffer.get_end_iter(), msg)
846 logView.scroll_mark_onscreen(buffer.get_insert())
847
848 def _quit(self, what = None, force = False):
849 """Quit from the application."""
850 if force:
851 result=RESPONSETYPE_YES
852 else:
853 dialog = gtk.MessageDialog(parent = self._mainWindow,
854 type = MESSAGETYPE_QUESTION,
855 message_format = xstr("quit_question"))
856
857 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
858 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
859
860 dialog.set_title(WINDOW_TITLE_BASE)
861 result = dialog.run()
862 dialog.hide()
863
864 if result==RESPONSETYPE_YES:
865 self._statusIcon.destroy()
866 return gtk.main_quit()
867
868 def _notebookPageSwitch(self, notebook, page, page_num):
869 """Called when the current page of the notebook has changed."""
870 if page_num==0:
871 gobject.idle_add(self._wizard.grabDefault)
872 else:
873 self._mainWindow.set_default(None)
874
875 def _editPreferences(self, menuItem):
876 """Callback for editing the preferences."""
877 self._preferences.run(self.config)
878 self._setupTimeSync()
879
880 def _setupTimeSync(self):
881 """Enable or disable the simulator time synchronization based on the
882 configuration."""
883 simulator = self._simulator
884 if simulator is not None:
885 if self.config.syncFSTime:
886 simulator.enableTimeSync()
887 else:
888 simulator.disableTimeSync()
889
890 def _loadPIREP(self, menuItem):
891 """Load a PIREP for sending."""
892 dialog = self._getLoadPirepDialog()
893
894 if self._lastLoadedPIREP:
895 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
896 else:
897 pirepDirectory = self.config.pirepDirectory
898 if pirepDirectory is not None:
899 dialog.set_current_folder(pirepDirectory)
900
901 result = dialog.run()
902 dialog.hide()
903
904 if result==RESPONSETYPE_OK:
905 self._lastLoadedPIREP = dialog.get_filename()
906
907 pirep = PIREP.load(self._lastLoadedPIREP)
908 if pirep is None:
909 dialog = gtk.MessageDialog(parent = self._mainWindow,
910 type = MESSAGETYPE_ERROR,
911 message_format = xstr("loadPIREP_failed"))
912 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
913 dialog.set_title(WINDOW_TITLE_BASE)
914 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
915 dialog.run()
916 dialog.hide()
917 else:
918 dialog = self._getSendLoadedDialog(pirep)
919 dialog.show_all()
920 result = dialog.run()
921 dialog.hide()
922
923 if result==RESPONSETYPE_OK:
924 self.sendPIREP(pirep)
925
926 def _getLoadPirepDialog(self):
927 """Get the PIREP loading file chooser dialog.
928
929 If it is not created yet, it will be created."""
930 if self._loadPIREPDialog is None:
931 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
932 xstr("loadPIREP_browser_title"),
933 action = FILE_CHOOSER_ACTION_OPEN,
934 buttons = (gtk.STOCK_CANCEL,
935 RESPONSETYPE_CANCEL,
936 gtk.STOCK_OK, RESPONSETYPE_OK),
937 parent = self._mainWindow)
938 dialog.set_modal(True)
939
940
941 filter = gtk.FileFilter()
942 filter.set_name(xstr("loadPIREP_filter_pireps"))
943 filter.add_pattern("*.pirep")
944 dialog.add_filter(filter)
945
946 filter = gtk.FileFilter()
947 filter.set_name(xstr("loadPIREP_filter_all"))
948 filter.add_pattern("*.*")
949 dialog.add_filter(filter)
950
951 self._loadPIREPDialog = dialog
952
953 return self._loadPIREPDialog
954
955 def _getSendLoadedDialog(self, pirep):
956 """Get a dialog displaying the main information of the flight from the
957 PIREP and providing Cancel and Send buttons."""
958 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
959 xstr("loadPIREP_send_title"),
960 parent = self._mainWindow,
961 flags = DIALOG_MODAL)
962
963 contentArea = dialog.get_content_area()
964
965 label = gtk.Label(xstr("loadPIREP_send_help"))
966 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
967 xscale = 0.0, yscale = 0.0)
968 alignment.set_padding(padding_top = 16, padding_bottom = 0,
969 padding_left = 48, padding_right = 48)
970 alignment.add(label)
971 contentArea.pack_start(alignment, False, False, 8)
972
973 table = gtk.Table(5, 2)
974 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
975 xscale = 0.0, yscale = 0.0)
976 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
977 padding_left = 48, padding_right = 48)
978 table.set_row_spacings(4)
979 table.set_col_spacings(16)
980 tableAlignment.add(table)
981 contentArea.pack_start(tableAlignment, True, True, 8)
982
983 bookedFlight = pirep.bookedFlight
984
985 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
986 label.set_use_markup(True)
987 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
988 xscale = 0.0, yscale = 0.0)
989 labelAlignment.add(label)
990 table.attach(labelAlignment, 0, 1, 0, 1)
991
992 label = gtk.Label(bookedFlight.callsign)
993 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
994 xscale = 0.0, yscale = 0.0)
995 labelAlignment.add(label)
996 table.attach(labelAlignment, 1, 2, 0, 1)
997
998 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
999 label.set_use_markup(True)
1000 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1001 xscale = 0.0, yscale = 0.0)
1002 labelAlignment.add(label)
1003 table.attach(labelAlignment, 0, 1, 1, 2)
1004
1005 label = gtk.Label(str(bookedFlight.departureTime.date()))
1006 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1007 xscale = 0.0, yscale = 0.0)
1008 labelAlignment.add(label)
1009 table.attach(labelAlignment, 1, 2, 1, 2)
1010
1011 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1012 label.set_use_markup(True)
1013 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1014 xscale = 0.0, yscale = 0.0)
1015 labelAlignment.add(label)
1016 table.attach(labelAlignment, 0, 1, 2, 3)
1017
1018 label = gtk.Label(bookedFlight.departureICAO)
1019 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1020 xscale = 0.0, yscale = 0.0)
1021 labelAlignment.add(label)
1022 table.attach(labelAlignment, 1, 2, 2, 3)
1023
1024 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1025 label.set_use_markup(True)
1026 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1027 xscale = 0.0, yscale = 0.0)
1028 labelAlignment.add(label)
1029 table.attach(labelAlignment, 0, 1, 3, 4)
1030
1031 label = gtk.Label(bookedFlight.arrivalICAO)
1032 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1033 xscale = 0.0, yscale = 0.0)
1034 labelAlignment.add(label)
1035 table.attach(labelAlignment, 1, 2, 3, 4)
1036
1037 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1038 label.set_use_markup(True)
1039 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1040 xscale = 0.0, yscale = 0.0)
1041 labelAlignment.add(label)
1042 table.attach(labelAlignment, 0, 1, 4, 5)
1043
1044 rating = pirep.rating
1045 label = gtk.Label()
1046 if rating<0:
1047 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1048 else:
1049 label.set_text("%.1f %%" % (rating,))
1050
1051 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1052 xscale = 0.0, yscale = 0.0)
1053 labelAlignment.add(label)
1054 table.attach(labelAlignment, 1, 2, 4, 5)
1055
1056 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1057 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1058
1059 return dialog
1060
1061 def sendPIREP(self, pirep, callback = None):
1062 """Send the given PIREP."""
1063 self.beginBusy(xstr("sendPIREP_busy"))
1064 self._sendPIREPCallback = callback
1065 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1066
1067 def _pirepSentCallback(self, returned, result):
1068 """Callback for the PIREP sending result."""
1069 gobject.idle_add(self._handlePIREPSent, returned, result)
1070
1071 def _handlePIREPSent(self, returned, result):
1072 """Callback for the PIREP sending result."""
1073 self.endBusy()
1074 secondaryMarkup = None
1075 type = MESSAGETYPE_ERROR
1076 if returned:
1077 if result.success:
1078 type = MESSAGETYPE_INFO
1079 messageFormat = xstr("sendPIREP_success")
1080 secondaryMarkup = xstr("sendPIREP_success_sec")
1081 elif result.alreadyFlown:
1082 messageFormat = xstr("sendPIREP_already")
1083 secondaryMarkup = xstr("sendPIREP_already_sec")
1084 elif result.notAvailable:
1085 messageFormat = xstr("sendPIREP_notavail")
1086 else:
1087 messageFormat = xstr("sendPIREP_unknown")
1088 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1089 else:
1090 print "PIREP sending failed", result
1091 messageFormat = xstr("sendPIREP_failed")
1092 secondaryMarkup = xstr("sendPIREP_failed_sec")
1093
1094 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1095 type = type, message_format = messageFormat)
1096 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1097 dialog.set_title(WINDOW_TITLE_BASE)
1098 if secondaryMarkup is not None:
1099 dialog.format_secondary_markup(secondaryMarkup)
1100
1101 dialog.run()
1102 dialog.hide()
1103
1104 callback = self._sendPIREPCallback
1105 self._sendPIREPCallback = None
1106 if callback is not None:
1107 callback(returned, result)
Note: See TracBrowser for help on using the repository browser.