source: src/mlx/gui/gui.py@ 103:077fcdf8b411

Last change on this file since 103:077fcdf8b411 was 99:b55ca557d5e9, checked in by István Váradi <ivaradi@…>, 13 years ago

The delay codes are handled

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