source: src/mlx/gui/gui.py@ 227:50c3ae93007d

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

Added a Help menu with the manual and the about dialog

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