source: src/mlx/gui/gui.py@ 171:54a31411c301

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

The log windows now have no visible cursor

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