source: src/mlx/gui/gui.py@ 109:83b56617aec1

Last change on this file since 109:83b56617aec1 was 108:3ebb3c906fd1, checked in by István Váradi <ivaradi@…>, 13 years ago

The connection failure dialogs are now internationalized

File size: 23.1 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(xstr("tab_flight_tooltip"))
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(xstr("tab_flight_info_tooltip"))
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(xstr("tab_log_tooltip"))
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 = xstr("conn_failed"))
297
298 dialog.set_title(WINDOW_TITLE_BASE)
299 dialog.format_secondary_markup(xstr("conn_failed_sec"))
300
301 dialog.add_button(xstr("button_cancel"), 0)
302 dialog.add_button(xstr("button_tryagain"), 1)
303 dialog.set_default_response(1)
304
305 result = dialog.run()
306 dialog.hide()
307 if result == 1:
308 self.beginBusy(xstr("connect_busy"))
309 self._simulator.reconnect()
310 else:
311 self.reset()
312
313 def disconnected(self):
314 """Called when we have disconnected from the simulator."""
315 self._connected = False
316 self._logger.untimedMessage("Disconnected from the simulator")
317
318 gobject.idle_add(self._disconnected)
319
320 def _disconnected(self):
321 """Called when we have disconnected from the simulator unexpectedly."""
322 self._statusbar.updateConnection(self._connecting, self._connected)
323
324 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
325 message_format = xstr("conn_broken"),
326 parent = self._mainWindow)
327 dialog.set_title(WINDOW_TITLE_BASE)
328 dialog.format_secondary_markup(xstr("conn_broken_sec"))
329
330 dialog.add_button(xstr("button_cancel"), 0)
331 dialog.add_button(xstr("button_reconnect"), 1)
332 dialog.set_default_response(1)
333
334 result = dialog.run()
335 dialog.hide()
336 if result == 1:
337 self.beginBusy(xstr("connect_busy"))
338 self._reconnecting = True
339 self._simulator.reconnect()
340 else:
341 self.reset()
342
343 def enableFlightInfo(self):
344 """Enable the flight info tab."""
345 self._flightInfo.enable()
346
347 def reset(self):
348 """Reset the GUI."""
349 self._disconnect()
350
351 self._flightInfo.reset()
352 self._flightInfo.disable()
353 self.resetFlightStatus()
354
355 self._wizard.reset()
356 self._notebook.set_current_page(0)
357
358 self._logView.get_buffer().set_text("")
359
360 def _disconnect(self):
361 """Disconnect from the simulator if connected."""
362 self.stopMonitoring()
363
364 if self._connected:
365 self._flight.simulator.disconnect()
366 self._connected = False
367
368 self._connecting = False
369 self._reconnecting = False
370 self._statusbar.updateConnection(False, False)
371
372 def addFlightLogLine(self, timeStr, line):
373 """Write the given message line to the log."""
374 gobject.idle_add(self._writeLog,
375 GUI._formatFlightLogLine(timeStr, line),
376 self._logView)
377
378 def updateFlightLogLine(self, index, timeStr, line):
379 """Update the line with the given index."""
380 gobject.idle_add(self._updateFlightLogLine, index,
381 GUI._formatFlightLogLine(timeStr, line))
382
383 def _updateFlightLogLine(self, index, line):
384 """Replace the contents of the given line in the log."""
385 buffer = self._logView.get_buffer()
386 startIter = buffer.get_iter_at_line(index)
387 endIter = buffer.get_iter_at_line(index + 1)
388 buffer.delete(startIter, endIter)
389 buffer.insert(startIter, line)
390 self._logView.scroll_mark_onscreen(buffer.get_insert())
391
392 def check(self, flight, aircraft, logger, oldState, state):
393 """Update the data."""
394 gobject.idle_add(self._monitorWindow.setData, state)
395 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
396
397 def resetFlightStatus(self):
398 """Reset the status of the flight."""
399 self._statusbar.resetFlightStatus()
400 self._statusbar.updateTime()
401 self._statusIcon.resetFlightStatus()
402
403 def setStage(self, stage):
404 """Set the stage of the flight."""
405 gobject.idle_add(self._setStage, stage)
406
407 def _setStage(self, stage):
408 """Set the stage of the flight."""
409 self._statusbar.setStage(stage)
410 self._statusIcon.setStage(stage)
411 self._wizard.setStage(stage)
412 if stage==const.STAGE_END:
413 self._disconnect()
414
415 def setRating(self, rating):
416 """Set the rating of the flight."""
417 gobject.idle_add(self._setRating, rating)
418
419 def _setRating(self, rating):
420 """Set the rating of the flight."""
421 self._statusbar.setRating(rating)
422 self._statusIcon.setRating(rating)
423
424 def setNoGo(self, reason):
425 """Set the rating of the flight to No-Go with the given reason."""
426 gobject.idle_add(self._setNoGo, reason)
427
428 def _setNoGo(self, reason):
429 """Set the rating of the flight."""
430 self._statusbar.setNoGo(reason)
431 self._statusIcon.setNoGo(reason)
432
433 def _handleMainWindowState(self, window, event):
434 """Hande a change in the state of the window"""
435 iconified = gdk.WindowState.ICONIFIED if pygobject \
436 else gdk.WINDOW_STATE_ICONIFIED
437 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
438 self.hideMainWindow(savePosition = False)
439
440 def hideMainWindow(self, savePosition = True):
441 """Hide the main window and save its position."""
442 if savePosition:
443 (self._mainWindowX, self._mainWindowY) = \
444 self._mainWindow.get_window().get_root_origin()
445 else:
446 self._mainWindowX = self._mainWindowY = None
447 self._mainWindow.hide()
448 self._statusIcon.mainWindowHidden()
449 return True
450
451 def showMainWindow(self):
452 """Show the main window at its former position."""
453 if self._mainWindowX is not None and self._mainWindowY is not None:
454 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
455
456 self._mainWindow.show()
457 self._mainWindow.deiconify()
458
459 self._statusIcon.mainWindowShown()
460
461 def toggleMainWindow(self):
462 """Toggle the main window."""
463 if self._mainWindow.get_visible():
464 self.hideMainWindow()
465 else:
466 self.showMainWindow()
467
468 def hideMonitorWindow(self, savePosition = True):
469 """Hide the monitor window."""
470 if savePosition:
471 (self._monitorWindowX, self._monitorWindowY) = \
472 self._monitorWindow.get_window().get_root_origin()
473 else:
474 self._monitorWindowX = self._monitorWindowY = None
475 self._monitorWindow.hide()
476 self._statusIcon.monitorWindowHidden()
477 if self._showMonitorMenuItem.get_active():
478 self._selfToggling = True
479 self._showMonitorMenuItem.set_active(False)
480 return True
481
482 def showMonitorWindow(self):
483 """Show the monitor window."""
484 if self._monitorWindowX is not None and self._monitorWindowY is not None:
485 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
486 self._monitorWindow.show_all()
487 self._statusIcon.monitorWindowShown()
488 if not self._showMonitorMenuItem.get_active():
489 self._selfToggling = True
490 self._showMonitorMenuItem.set_active(True)
491
492 def _toggleMonitorWindow(self, menuItem):
493 if self._selfToggling:
494 self._selfToggling = False
495 elif self._monitorWindow.get_visible():
496 self.hideMonitorWindow()
497 else:
498 self.showMonitorWindow()
499
500 def restart(self):
501 """Quit and restart the application."""
502 self.toRestart = True
503 self._quit(force = True)
504
505 def flushStdIO(self):
506 """Flush any text to the standard error that could not be logged."""
507 if self._stdioText:
508 sys.__stderr__.write(self._stdioText)
509
510 def writeStdIO(self, text):
511 """Write the given text into standard I/O log."""
512 with self._stdioLock:
513 self._stdioText += text
514
515 gobject.idle_add(self._writeStdIO)
516
517 def beginBusy(self, message):
518 """Begin a period of background processing."""
519 self._wizard.set_sensitive(False)
520 self._mainWindow.get_window().set_cursor(self._busyCursor)
521 self._statusbar.updateBusyState(message)
522
523 def endBusy(self):
524 """End a period of background processing."""
525 self._mainWindow.get_window().set_cursor(None)
526 self._wizard.set_sensitive(True)
527 self._statusbar.updateBusyState(None)
528
529 def _writeStdIO(self):
530 """Perform the real writing."""
531 with self._stdioLock:
532 text = self._stdioText
533 self._stdioText = ""
534 if not text: return
535
536 lines = text.splitlines()
537 if text[-1]=="\n":
538 text = ""
539 else:
540 text = lines[-1]
541 lines = lines[:-1]
542
543 for line in lines:
544 #print >> sys.__stdout__, line
545 self._writeLog(line + "\n", self._debugLogView)
546
547 if text:
548 #print >> sys.__stdout__, text,
549 self._writeLog(text, self._debugLogView)
550
551 def connectSimulator(self, aircraftType):
552 """Connect to the simulator for the first time."""
553 self._logger.reset()
554
555 self._flight = flight.Flight(self._logger, self)
556 self._flight.aircraftType = aircraftType
557 self._flight.aircraft = acft.Aircraft.create(self._flight)
558 self._flight.aircraft._checkers.append(self)
559
560 if self._simulator is None:
561 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
562
563 self._flight.simulator = self._simulator
564
565 self.beginBusy(xstr("connect_busy"))
566 self._statusbar.updateConnection(self._connecting, self._connected)
567
568 self._connecting = True
569 self._simulator.connect(self._flight.aircraft)
570
571 def startMonitoring(self):
572 """Start monitoring."""
573 if not self._monitoring:
574 self.simulator.startMonitoring()
575 self._monitoring = True
576
577 def stopMonitoring(self):
578 """Stop monitoring."""
579 if self._monitoring:
580 self.simulator.stopMonitoring()
581 self._monitoring = False
582
583 def _buildMenuBar(self, accelGroup):
584 """Build the main menu bar."""
585 menuBar = gtk.MenuBar()
586
587 fileMenuItem = gtk.MenuItem("File")
588 fileMenu = gtk.Menu()
589 fileMenuItem.set_submenu(fileMenu)
590 menuBar.append(fileMenuItem)
591
592 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
593 quitMenuItem.set_use_stock(True)
594 quitMenuItem.add_accelerator("activate", accelGroup,
595 ord("q"), CONTROL_MASK,
596 ACCEL_VISIBLE)
597 quitMenuItem.connect("activate", self._quit)
598 fileMenu.append(quitMenuItem)
599
600
601 viewMenuItem = gtk.MenuItem("View")
602 viewMenu = gtk.Menu()
603 viewMenuItem.set_submenu(viewMenu)
604 menuBar.append(viewMenuItem)
605
606 self._showMonitorMenuItem = gtk.CheckMenuItem()
607 self._showMonitorMenuItem.set_label("Show _monitor window")
608 self._showMonitorMenuItem.set_use_underline(True)
609 self._showMonitorMenuItem.set_active(False)
610 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
611 ord("m"), CONTROL_MASK,
612 ACCEL_VISIBLE)
613 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
614 viewMenu.append(self._showMonitorMenuItem)
615
616 showDebugMenuItem = gtk.CheckMenuItem()
617 showDebugMenuItem.set_label("Show _debug log")
618 showDebugMenuItem.set_use_underline(True)
619 showDebugMenuItem.set_active(False)
620 showDebugMenuItem.add_accelerator("activate", accelGroup,
621 ord("d"), CONTROL_MASK,
622 ACCEL_VISIBLE)
623 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
624 viewMenu.append(showDebugMenuItem)
625
626 return menuBar
627
628 def _toggleDebugLog(self, menuItem):
629 """Toggle the debug log."""
630 if menuItem.get_active():
631 label = gtk.Label(xstr("tab_debug_log"))
632 label.set_use_underline(True)
633 label.set_tooltip_text("Log with debugging information.")
634 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
635 self._notebook.set_current_page(self._debugLogPage)
636 else:
637 self._notebook.remove_page(self._debugLogPage)
638
639 def _buildLogWidget(self):
640 """Build the widget for the log."""
641 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
642
643 alignment.set_padding(padding_top = 8, padding_bottom = 8,
644 padding_left = 16, padding_right = 16)
645
646 logScroller = gtk.ScrolledWindow()
647 # FIXME: these should be constants in common
648 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
649 else gtk.POLICY_AUTOMATIC,
650 gtk.PolicyType.AUTOMATIC if pygobject
651 else gtk.POLICY_AUTOMATIC)
652 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
653 else gtk.SHADOW_IN)
654 logView = gtk.TextView()
655 logView.set_editable(False)
656 logScroller.add(logView)
657
658 logBox = gtk.VBox()
659 logBox.pack_start(logScroller, True, True, 0)
660 logBox.set_size_request(-1, 200)
661
662 alignment.add(logBox)
663
664 return (alignment, logView)
665
666 def _writeLog(self, msg, logView):
667 """Write the given message to the log."""
668 buffer = logView.get_buffer()
669 buffer.insert(buffer.get_end_iter(), msg)
670 logView.scroll_mark_onscreen(buffer.get_insert())
671
672 def _quit(self, what = None, force = False):
673 """Quit from the application."""
674 if force:
675 result=RESPONSETYPE_YES
676 else:
677 dialog = gtk.MessageDialog(parent = self._mainWindow,
678 type = MESSAGETYPE_QUESTION,
679 buttons = BUTTONSTYPE_YES_NO,
680 message_format =
681 "Are you sure to quit the logger?")
682 dialog.set_title(WINDOW_TITLE_BASE)
683 result = dialog.run()
684 dialog.hide()
685
686 if result==RESPONSETYPE_YES:
687 self._statusIcon.destroy()
688 return gtk.main_quit()
689
690 def _notebookPageSwitch(self, notebook, page, page_num):
691 """Called when the current page of the notebook has changed."""
692 if page_num==0:
693 gobject.idle_add(self._wizard.grabDefault)
694 else:
695 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.