source: src/mlx/gui/gui.py@ 96:aa6a0b79c073

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

The contents of log lines can be modified after they are written, and we are using it for Vref

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