source: src/mlx/gui/gui.py@ 107:35310bf5309c

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

Added support for internationalization and translated most of the flight wizard into Hungarian

File size: 23.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
10
11import mlx.const as const
12import mlx.fs as fs
13import mlx.flight as flight
14import mlx.logger as logger
15import mlx.acft as acft
16import mlx.web as web
17from mlx.i18n import xstr
18
19import time
20import threading
21import sys
22
23#------------------------------------------------------------------------------
24
25class GUI(fs.ConnectionListener):
26 """The main GUI class."""
27 @staticmethod
28 def _formatFlightLogLine(timeStr, line):
29 """Format the given line for flight logging."""
30 if timeStr is not None:
31 line = timeStr + ": " + line
32 return line + "\n"
33
34 def __init__(self, programDirectory, config):
35 """Construct the GUI."""
36 gobject.threads_init()
37
38 self._programDirectory = programDirectory
39 self.config = config
40 self._connecting = False
41 self._reconnecting = False
42 self._connected = False
43 self._logger = logger.Logger(self)
44 self._flight = None
45 self._simulator = None
46 self._monitoring = False
47
48 self._stdioLock = threading.Lock()
49 self._stdioText = ""
50
51 self.webHandler = web.Handler()
52 self.webHandler.start()
53
54 self.toRestart = False
55
56 def build(self, iconDirectory):
57 """Build the GUI."""
58
59 window = gtk.Window()
60 window.set_title(WINDOW_TITLE_BASE)
61 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
62 window.connect("delete-event",
63 lambda a, b: self.hideMainWindow())
64 window.connect("window-state-event", self._handleMainWindowState)
65 accelGroup = gtk.AccelGroup()
66 window.add_accel_group(accelGroup)
67
68 mainVBox = gtk.VBox()
69 window.add(mainVBox)
70
71 menuBar = self._buildMenuBar(accelGroup)
72 mainVBox.pack_start(menuBar, False, False, 0)
73
74 self._notebook = gtk.Notebook()
75 mainVBox.pack_start(self._notebook, True, True, 4)
76
77 self._wizard = Wizard(self)
78 label = gtk.Label(xstr("tab_flight"))
79 label.set_use_underline(True)
80 label.set_tooltip_text("Flight wizard")
81 self._notebook.append_page(self._wizard, label)
82
83 self._flightInfo = FlightInfo(self)
84 label = gtk.Label(xstr("tab_flight_info"))
85 label.set_use_underline(True)
86 label.set_tooltip_text("Flight information")
87 self._notebook.append_page(self._flightInfo, label)
88 self._flightInfo.disable()
89
90 (logWidget, self._logView) = self._buildLogWidget()
91 label = gtk.Label(xstr("tab_log"))
92 label.set_use_underline(True)
93 label.set_tooltip_text("The log of your flight that will be sent to the MAVA website")
94 self._notebook.append_page(logWidget, label)
95
96 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
97 self._debugLogWidget.show_all()
98
99 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
100
101 self._statusbar = Statusbar()
102 mainVBox.pack_start(self._statusbar, False, False, 0)
103
104 self._notebook.connect("switch-page", self._notebookPageSwitch)
105
106 self._monitorWindow = MonitorWindow(self, iconDirectory)
107 self._monitorWindow.add_accel_group(accelGroup)
108 self._monitorWindowX = None
109 self._monitorWindowY = None
110 self._selfToggling = False
111
112 window.show_all()
113 self._wizard.grabDefault()
114
115 self._mainWindow = window
116
117 self._statusIcon = StatusIcon(iconDirectory, self)
118
119 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
120 else gdk.WATCH)
121
122 @property
123 def mainWindow(self):
124 """Get the main window of the GUI."""
125 return self._mainWindow
126
127 @property
128 def logger(self):
129 """Get the logger used by us."""
130 return self._logger
131
132 @property
133 def simulator(self):
134 """Get the simulator used by us."""
135 return self._simulator
136
137 @property
138 def flight(self):
139 """Get the flight being performed."""
140 return self._flight
141
142 @property
143 def bookedFlight(self):
144 """Get the booked flight selected, if any."""
145 return self._wizard.bookedFlight
146
147 @property
148 def cargoWeight(self):
149 """Get the cargo weight."""
150 return self._wizard.cargoWeight
151
152 @property
153 def zfw(self):
154 """Get Zero-Fuel Weight calculated for the current flight."""
155 return self._wizard.zfw
156
157 @property
158 def filedCruiseAltitude(self):
159 """Get cruise altitude filed for the current flight."""
160 return self._wizard.filedCruiseAltitude
161
162 @property
163 def cruiseAltitude(self):
164 """Get cruise altitude set for the current flight."""
165 return self._wizard.cruiseAltitude
166
167 @property
168 def route(self):
169 """Get the flight route."""
170 return self._wizard.route
171
172 @property
173 def departureMETAR(self):
174 """Get the METAR of the deprature airport."""
175 return self._wizard.departureMETAR
176
177 @property
178 def arrivalMETAR(self):
179 """Get the METAR of the deprature airport."""
180 return self._wizard.arrivalMETAR
181
182 @property
183 def departureRunway(self):
184 """Get the name of the departure runway."""
185 return self._wizard.departureRunway
186
187 @property
188 def sid(self):
189 """Get the SID."""
190 return self._wizard.sid
191
192 @property
193 def v1(self):
194 """Get the V1 speed calculated for the flight."""
195 return self._wizard.v1
196
197 @property
198 def vr(self):
199 """Get the Vr speed calculated for the flight."""
200 return self._wizard.vr
201
202 @property
203 def v2(self):
204 """Get the V2 speed calculated for the flight."""
205 return self._wizard.v2
206
207 @property
208 def arrivalRunway(self):
209 """Get the arrival runway."""
210 return self._wizard.arrivalRunway
211
212 @property
213 def star(self):
214 """Get the STAR."""
215 return self._wizard.star
216
217 @property
218 def transition(self):
219 """Get the transition."""
220 return self._wizard.transition
221
222 @property
223 def approachType(self):
224 """Get the approach type."""
225 return self._wizard.approachType
226
227 @property
228 def vref(self):
229 """Get the Vref speed calculated for the flight."""
230 return self._wizard.vref
231
232 @property
233 def flightType(self):
234 """Get the flight type."""
235 return self._wizard.flightType
236
237 @property
238 def online(self):
239 """Get whether the flight was online or not."""
240 return self._wizard.online
241
242 @property
243 def comments(self):
244 """Get the comments."""
245 return self._flightInfo.comments
246
247 @property
248 def flightDefects(self):
249 """Get the flight defects."""
250 return self._flightInfo.flightDefects
251
252 @property
253 def delayCodes(self):
254 """Get the delay codes."""
255 return self._flightInfo.delayCodes
256
257 def run(self):
258 """Run the GUI."""
259 if self.config.autoUpdate:
260 self._updater = Updater(self,
261 self._programDirectory,
262 self.config.updateURL,
263 self._mainWindow)
264 self._updater.start()
265
266 gtk.main()
267
268 self._disconnect()
269
270 def connected(self, fsType, descriptor):
271 """Called when we have connected to the simulator."""
272 self._connected = True
273 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
274 gobject.idle_add(self._handleConnected, fsType, descriptor)
275
276 def _handleConnected(self, fsType, descriptor):
277 """Called when the connection to the simulator has succeeded."""
278 self._statusbar.updateConnection(self._connecting, self._connected)
279 self.endBusy()
280 if not self._reconnecting:
281 self._wizard.connected(fsType, descriptor)
282 self._reconnecting = False
283
284 def connectionFailed(self):
285 """Called when the connection failed."""
286 self._logger.untimedMessage("Connection to the simulator failed")
287 gobject.idle_add(self._connectionFailed)
288
289 def _connectionFailed(self):
290 """Called when the connection failed."""
291 self.endBusy()
292 self._statusbar.updateConnection(self._connecting, self._connected)
293
294 dialog = gtk.MessageDialog(parent = self._mainWindow,
295 type = MESSAGETYPE_ERROR,
296 message_format =
297 "Cannot connect to the simulator.")
298 dialog.set_title(WINDOW_TITLE_BASE)
299 dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
300 "to try the connection again, "
301 "or <b>Cancel</b> to cancel the flight.")
302
303 dialog.add_button("_Cancel", 0)
304 dialog.add_button("_Try again", 1)
305 dialog.set_default_response(1)
306
307 result = dialog.run()
308 dialog.hide()
309 if result == 1:
310 self.beginBusy(xstr("connect_busy"))
311 self._simulator.reconnect()
312 else:
313 self.reset()
314
315 def disconnected(self):
316 """Called when we have disconnected from the simulator."""
317 self._connected = False
318 self._logger.untimedMessage("Disconnected from the simulator")
319
320 gobject.idle_add(self._disconnected)
321
322 def _disconnected(self):
323 """Called when we have disconnected from the simulator unexpectedly."""
324 self._statusbar.updateConnection(self._connecting, self._connected)
325
326 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
327 message_format =
328 "The connection to the simulator failed unexpectedly.",
329 parent = self._mainWindow)
330 dialog.set_title(WINDOW_TITLE_BASE)
331 dialog.format_secondary_markup("If the simulator has crashed, restart it "
332 "and restore your flight as much as possible "
333 "to the state it was in before the crash. "
334 "Then press <b>Reconnect</b> to reconnect.\n\n"
335 "If you want to cancel the flight, press <b>Cancel</b>.")
336
337 dialog.add_button("_Cancel", 0)
338 dialog.add_button("_Reconnect", 1)
339 dialog.set_default_response(1)
340
341 result = dialog.run()
342 dialog.hide()
343 if result == 1:
344 self.beginBusy("Connecting to the simulator.")
345 self._reconnecting = True
346 self._simulator.reconnect()
347 else:
348 self.reset()
349
350 def enableFlightInfo(self):
351 """Enable the flight info tab."""
352 self._flightInfo.enable()
353
354 def reset(self):
355 """Reset the GUI."""
356 self._disconnect()
357
358 self._flightInfo.reset()
359 self._flightInfo.disable()
360 self.resetFlightStatus()
361
362 self._wizard.reset()
363 self._notebook.set_current_page(0)
364
365 self._logView.get_buffer().set_text("")
366
367 def _disconnect(self):
368 """Disconnect from the simulator if connected."""
369 self.stopMonitoring()
370
371 if self._connected:
372 self._flight.simulator.disconnect()
373 self._connected = False
374
375 self._connecting = False
376 self._reconnecting = False
377 self._statusbar.updateConnection(False, False)
378
379 def addFlightLogLine(self, timeStr, line):
380 """Write the given message line to the log."""
381 gobject.idle_add(self._writeLog,
382 GUI._formatFlightLogLine(timeStr, line),
383 self._logView)
384
385 def updateFlightLogLine(self, index, timeStr, line):
386 """Update the line with the given index."""
387 gobject.idle_add(self._updateFlightLogLine, index,
388 GUI._formatFlightLogLine(timeStr, line))
389
390 def _updateFlightLogLine(self, index, line):
391 """Replace the contents of the given line in the log."""
392 buffer = self._logView.get_buffer()
393 startIter = buffer.get_iter_at_line(index)
394 endIter = buffer.get_iter_at_line(index + 1)
395 buffer.delete(startIter, endIter)
396 buffer.insert(startIter, line)
397 self._logView.scroll_mark_onscreen(buffer.get_insert())
398
399 def check(self, flight, aircraft, logger, oldState, state):
400 """Update the data."""
401 gobject.idle_add(self._monitorWindow.setData, state)
402 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
403
404 def resetFlightStatus(self):
405 """Reset the status of the flight."""
406 self._statusbar.resetFlightStatus()
407 self._statusbar.updateTime()
408 self._statusIcon.resetFlightStatus()
409
410 def setStage(self, stage):
411 """Set the stage of the flight."""
412 gobject.idle_add(self._setStage, stage)
413
414 def _setStage(self, stage):
415 """Set the stage of the flight."""
416 self._statusbar.setStage(stage)
417 self._statusIcon.setStage(stage)
418 self._wizard.setStage(stage)
419 if stage==const.STAGE_END:
420 self._disconnect()
421
422 def setRating(self, rating):
423 """Set the rating of the flight."""
424 gobject.idle_add(self._setRating, rating)
425
426 def _setRating(self, rating):
427 """Set the rating of the flight."""
428 self._statusbar.setRating(rating)
429 self._statusIcon.setRating(rating)
430
431 def setNoGo(self, reason):
432 """Set the rating of the flight to No-Go with the given reason."""
433 gobject.idle_add(self._setNoGo, reason)
434
435 def _setNoGo(self, reason):
436 """Set the rating of the flight."""
437 self._statusbar.setNoGo(reason)
438 self._statusIcon.setNoGo(reason)
439
440 def _handleMainWindowState(self, window, event):
441 """Hande a change in the state of the window"""
442 iconified = gdk.WindowState.ICONIFIED if pygobject \
443 else gdk.WINDOW_STATE_ICONIFIED
444 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
445 self.hideMainWindow(savePosition = False)
446
447 def hideMainWindow(self, savePosition = True):
448 """Hide the main window and save its position."""
449 if savePosition:
450 (self._mainWindowX, self._mainWindowY) = \
451 self._mainWindow.get_window().get_root_origin()
452 else:
453 self._mainWindowX = self._mainWindowY = None
454 self._mainWindow.hide()
455 self._statusIcon.mainWindowHidden()
456 return True
457
458 def showMainWindow(self):
459 """Show the main window at its former position."""
460 if self._mainWindowX is not None and self._mainWindowY is not None:
461 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
462
463 self._mainWindow.show()
464 self._mainWindow.deiconify()
465
466 self._statusIcon.mainWindowShown()
467
468 def toggleMainWindow(self):
469 """Toggle the main window."""
470 if self._mainWindow.get_visible():
471 self.hideMainWindow()
472 else:
473 self.showMainWindow()
474
475 def hideMonitorWindow(self, savePosition = True):
476 """Hide the monitor window."""
477 if savePosition:
478 (self._monitorWindowX, self._monitorWindowY) = \
479 self._monitorWindow.get_window().get_root_origin()
480 else:
481 self._monitorWindowX = self._monitorWindowY = None
482 self._monitorWindow.hide()
483 self._statusIcon.monitorWindowHidden()
484 if self._showMonitorMenuItem.get_active():
485 self._selfToggling = True
486 self._showMonitorMenuItem.set_active(False)
487 return True
488
489 def showMonitorWindow(self):
490 """Show the monitor window."""
491 if self._monitorWindowX is not None and self._monitorWindowY is not None:
492 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
493 self._monitorWindow.show_all()
494 self._statusIcon.monitorWindowShown()
495 if not self._showMonitorMenuItem.get_active():
496 self._selfToggling = True
497 self._showMonitorMenuItem.set_active(True)
498
499 def _toggleMonitorWindow(self, menuItem):
500 if self._selfToggling:
501 self._selfToggling = False
502 elif self._monitorWindow.get_visible():
503 self.hideMonitorWindow()
504 else:
505 self.showMonitorWindow()
506
507 def restart(self):
508 """Quit and restart the application."""
509 self.toRestart = True
510 self._quit(force = True)
511
512 def flushStdIO(self):
513 """Flush any text to the standard error that could not be logged."""
514 if self._stdioText:
515 sys.__stderr__.write(self._stdioText)
516
517 def writeStdIO(self, text):
518 """Write the given text into standard I/O log."""
519 with self._stdioLock:
520 self._stdioText += text
521
522 gobject.idle_add(self._writeStdIO)
523
524 def beginBusy(self, message):
525 """Begin a period of background processing."""
526 self._wizard.set_sensitive(False)
527 self._mainWindow.get_window().set_cursor(self._busyCursor)
528 self._statusbar.updateBusyState(message)
529
530 def endBusy(self):
531 """End a period of background processing."""
532 self._mainWindow.get_window().set_cursor(None)
533 self._wizard.set_sensitive(True)
534 self._statusbar.updateBusyState(None)
535
536 def _writeStdIO(self):
537 """Perform the real writing."""
538 with self._stdioLock:
539 text = self._stdioText
540 self._stdioText = ""
541 if not text: return
542
543 lines = text.splitlines()
544 if text[-1]=="\n":
545 text = ""
546 else:
547 text = lines[-1]
548 lines = lines[:-1]
549
550 for line in lines:
551 #print >> sys.__stdout__, line
552 self._writeLog(line + "\n", self._debugLogView)
553
554 if text:
555 #print >> sys.__stdout__, text,
556 self._writeLog(text, self._debugLogView)
557
558 def connectSimulator(self, aircraftType):
559 """Connect to the simulator for the first time."""
560 self._logger.reset()
561
562 self._flight = flight.Flight(self._logger, self)
563 self._flight.aircraftType = aircraftType
564 self._flight.aircraft = acft.Aircraft.create(self._flight)
565 self._flight.aircraft._checkers.append(self)
566
567 if self._simulator is None:
568 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
569
570 self._flight.simulator = self._simulator
571
572 self.beginBusy(xstr("connect_busy"))
573 self._statusbar.updateConnection(self._connecting, self._connected)
574
575 self._connecting = True
576 self._simulator.connect(self._flight.aircraft)
577
578 def startMonitoring(self):
579 """Start monitoring."""
580 if not self._monitoring:
581 self.simulator.startMonitoring()
582 self._monitoring = True
583
584 def stopMonitoring(self):
585 """Stop monitoring."""
586 if self._monitoring:
587 self.simulator.stopMonitoring()
588 self._monitoring = False
589
590 def _buildMenuBar(self, accelGroup):
591 """Build the main menu bar."""
592 menuBar = gtk.MenuBar()
593
594 fileMenuItem = gtk.MenuItem("File")
595 fileMenu = gtk.Menu()
596 fileMenuItem.set_submenu(fileMenu)
597 menuBar.append(fileMenuItem)
598
599 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
600 quitMenuItem.set_use_stock(True)
601 quitMenuItem.add_accelerator("activate", accelGroup,
602 ord("q"), CONTROL_MASK,
603 ACCEL_VISIBLE)
604 quitMenuItem.connect("activate", self._quit)
605 fileMenu.append(quitMenuItem)
606
607
608 viewMenuItem = gtk.MenuItem("View")
609 viewMenu = gtk.Menu()
610 viewMenuItem.set_submenu(viewMenu)
611 menuBar.append(viewMenuItem)
612
613 self._showMonitorMenuItem = gtk.CheckMenuItem()
614 self._showMonitorMenuItem.set_label("Show _monitor window")
615 self._showMonitorMenuItem.set_use_underline(True)
616 self._showMonitorMenuItem.set_active(False)
617 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
618 ord("m"), CONTROL_MASK,
619 ACCEL_VISIBLE)
620 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
621 viewMenu.append(self._showMonitorMenuItem)
622
623 showDebugMenuItem = gtk.CheckMenuItem()
624 showDebugMenuItem.set_label("Show _debug log")
625 showDebugMenuItem.set_use_underline(True)
626 showDebugMenuItem.set_active(False)
627 showDebugMenuItem.add_accelerator("activate", accelGroup,
628 ord("d"), CONTROL_MASK,
629 ACCEL_VISIBLE)
630 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
631 viewMenu.append(showDebugMenuItem)
632
633 return menuBar
634
635 def _toggleDebugLog(self, menuItem):
636 """Toggle the debug log."""
637 if menuItem.get_active():
638 label = gtk.Label(xstr("tab_debug_log"))
639 label.set_use_underline(True)
640 label.set_tooltip_text("Log with debugging information.")
641 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
642 self._notebook.set_current_page(self._debugLogPage)
643 else:
644 self._notebook.remove_page(self._debugLogPage)
645
646 def _buildLogWidget(self):
647 """Build the widget for the log."""
648 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
649
650 alignment.set_padding(padding_top = 8, padding_bottom = 8,
651 padding_left = 16, padding_right = 16)
652
653 logScroller = gtk.ScrolledWindow()
654 # FIXME: these should be constants in common
655 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
656 else gtk.POLICY_AUTOMATIC,
657 gtk.PolicyType.AUTOMATIC if pygobject
658 else gtk.POLICY_AUTOMATIC)
659 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
660 else gtk.SHADOW_IN)
661 logView = gtk.TextView()
662 logView.set_editable(False)
663 logScroller.add(logView)
664
665 logBox = gtk.VBox()
666 logBox.pack_start(logScroller, True, True, 0)
667 logBox.set_size_request(-1, 200)
668
669 alignment.add(logBox)
670
671 return (alignment, logView)
672
673 def _writeLog(self, msg, logView):
674 """Write the given message to the log."""
675 buffer = logView.get_buffer()
676 buffer.insert(buffer.get_end_iter(), msg)
677 logView.scroll_mark_onscreen(buffer.get_insert())
678
679 def _quit(self, what = None, force = False):
680 """Quit from the application."""
681 if force:
682 result=RESPONSETYPE_YES
683 else:
684 dialog = gtk.MessageDialog(parent = self._mainWindow,
685 type = MESSAGETYPE_QUESTION,
686 buttons = BUTTONSTYPE_YES_NO,
687 message_format =
688 "Are you sure to quit the logger?")
689 dialog.set_title(WINDOW_TITLE_BASE)
690 result = dialog.run()
691 dialog.hide()
692
693 if result==RESPONSETYPE_YES:
694 self._statusIcon.destroy()
695 return gtk.main_quit()
696
697 def _notebookPageSwitch(self, notebook, page, page_num):
698 """Called when the current page of the notebook has changed."""
699 if page_num==0:
700 gobject.idle_add(self._wizard.grabDefault)
701 else:
702 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.