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@…>, 13 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
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 simulator(self):
123 """Get the simulator used by us."""
124 return self._simulator
125
126 @property
127 def flight(self):
128 """Get the flight being performed."""
129 return self._flight
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
155
156 @property
157 def vref(self):
158 """Get the Vref speed calculated for the flight."""
159 return self._wizard.vref
160
161 def run(self):
162 """Run the GUI."""
163 if self.config.autoUpdate:
164 self._updater = Updater(self,
165 self._programDirectory,
166 self.config.updateURL,
167 self._mainWindow)
168 self._updater.start()
169
170 gtk.main()
171
172 self._disconnect()
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,))
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:
216 self.reset()
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")
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:
250 self.reset()
251
252 def enableFlightInfo(self):
253 """Enable the flight info tab."""
254 self._flightInfo.enable()
255
256 def reset(self):
257 """Reset the GUI."""
258 self._disconnect()
259
260 self._flightInfo.reset()
261 self._flightInfo.disable()
262 self.resetFlightStatus()
263
264 self._wizard.reset()
265 self._notebook.set_current_page(0)
266
267 self._logView.get_buffer().set_text("")
268
269 def _disconnect(self):
270 """Disconnect from the simulator if connected."""
271 self.stopMonitoring()
272
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
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
301 def check(self, flight, aircraft, logger, oldState, state):
302 """Update the data."""
303 gobject.idle_add(self._monitorWindow.setData, state)
304 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
305
306 def resetFlightStatus(self):
307 """Reset the status of the flight."""
308 self._statusbar.resetFlightStatus()
309 self._statusbar.updateTime()
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."""
318 self._statusbar.setStage(stage)
319 self._statusIcon.setStage(stage)
320 self._wizard.setStage(stage)
321 if stage==const.STAGE_END:
322 self._disconnect()
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."""
330 self._statusbar.setRating(rating)
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."""
339 self._statusbar.setNoGo(reason)
340 self._statusIcon.setNoGo(reason)
341
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:
347 self.hideMainWindow(savePosition = False)
348
349 def hideMainWindow(self, savePosition = True):
350 """Hide the main window and save its position."""
351 if savePosition:
352 (self._mainWindowX, self._mainWindowY) = \
353 self._mainWindow.get_window().get_root_origin()
354 else:
355 self._mainWindowX = self._mainWindowY = None
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."""
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()
366 self._mainWindow.deiconify()
367
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()
376
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()
386 if self._showMonitorMenuItem.get_active():
387 self._selfToggling = True
388 self._showMonitorMenuItem.set_active(False)
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()
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()
408
409 def restart(self):
410 """Quit and restart the application."""
411 self.toRestart = True
412 self._quit(force = True)
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
419 def writeStdIO(self, text):
420 """Write the given text into standard I/O log."""
421 with self._stdioLock:
422 self._stdioText += text
423
424 gobject.idle_add(self._writeStdIO)
425
426 def beginBusy(self, message):
427 """Begin a period of background processing."""
428 self._wizard.set_sensitive(False)
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)
435 self._wizard.set_sensitive(True)
436 self._statusbar.updateBusyState(None)
437
438 def _writeStdIO(self):
439 """Perform the real writing."""
440 with self._stdioLock:
441 text = self._stdioText
442 self._stdioText = ""
443 if not text: return
444
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:
453 #print >> sys.__stdout__, line
454 self._writeLog(line + "\n", self._debugLogView)
455
456 if text:
457 #print >> sys.__stdout__, text,
458 self._writeLog(text, self._debugLogView)
459
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
480 def startMonitoring(self):
481 """Start monitoring."""
482 if not self._monitoring:
483 self.simulator.startMonitoring()
484 self._monitoring = True
485
486 def stopMonitoring(self):
487 """Stop monitoring."""
488 if self._monitoring:
489 self.simulator.stopMonitoring()
490 self._monitoring = False
491
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)
514
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)
534
535 return menuBar
536
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)
554
555 logScroller = gtk.ScrolledWindow()
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)
566
567 logBox = gtk.VBox()
568 logBox.pack_start(logScroller, True, True, 0)
569 logBox.set_size_request(-1, 200)
570
571 alignment.add(logBox)
572
573 return (alignment, logView)
574
575 def _writeLog(self, msg, logView):
576 """Write the given message to the log."""
577 buffer = logView.get_buffer()
578 buffer.insert(buffer.get_end_iter(), msg)
579 logView.scroll_mark_onscreen(buffer.get_insert())
580
581 def _quit(self, what = None, force = False):
582 """Quit from the application."""
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()
592
593 if result==RESPONSETYPE_YES:
594 self._statusIcon.destroy()
595 return gtk.main_quit()
596
597 def _notebookPageSwitch(self, notebook, page, page_num):
598 """Called when the current page of the notebook has changed."""
599 if page_num==0:
600 gobject.idle_add(self._wizard.grabDefault)
601 else:
602 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.