source: src/mlx/gui/gui.py@ 90:0b3fb188dc92

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

Added the flight info tab

File size: 16.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
17
18import time
19import threading
20import sys
21
22#------------------------------------------------------------------------------
23
24class GUI(fs.ConnectionListener):
25 """The main GUI class."""
26 def __init__(self, programDirectory, config):
27 """Construct the GUI."""
28 gobject.threads_init()
29
30 self._programDirectory = programDirectory
31 self.config = config
32 self._connecting = False
33 self._reconnecting = False
34 self._connected = False
35 self._logger = logger.Logger(output = self)
36 self._flight = None
37 self._simulator = None
38 self._monitoring = False
39
40 self._stdioLock = threading.Lock()
41 self._stdioText = ""
42 self._stdioAfterNewLine = True
43
44 self.webHandler = web.Handler()
45 self.webHandler.start()
46
47 self.toRestart = False
48
49 def build(self, iconDirectory):
50 """Build the GUI."""
51
52 window = gtk.Window()
53 window.set_title("MAVA Logger X " + const.VERSION)
54 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
55 window.connect("delete-event",
56 lambda a, b: self.hideMainWindow())
57 window.connect("window-state-event", self._handleMainWindowState)
58
59 mainVBox = gtk.VBox()
60 window.add(mainVBox)
61
62 notebook = gtk.Notebook()
63 mainVBox.add(notebook)
64
65 self._wizard = Wizard(self)
66 label = gtk.Label("Fligh_t")
67 label.set_use_underline(True)
68 label.set_tooltip_text("Flight wizard")
69 notebook.append_page(self._wizard, label)
70
71 self._flightInfo = FlightInfo(self)
72 label = gtk.Label("Flight _info")
73 label.set_use_underline(True)
74 label.set_tooltip_text("Flight information")
75 notebook.append_page(self._flightInfo, label)
76
77 logVBox = gtk.VBox()
78 label = gtk.Label("_Log")
79 label.set_use_underline(True)
80 label.set_tooltip_text("Flight log")
81 notebook.append_page(logVBox, label)
82
83 logFrame = self._buildLogFrame()
84 logFrame.set_border_width(8)
85 logVBox.pack_start(logFrame, True, True, 0)
86
87 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
88
89 self._statusbar = Statusbar()
90 mainVBox.pack_start(self._statusbar, False, False, 0)
91
92 notebook.connect("switch-page", self._notebookPageSwitch)
93
94 self._monitorWindow = MonitorWindow(self, iconDirectory)
95 self._monitorWindowX = None
96 self._monitorWindowY = None
97
98 window.show_all()
99 self._wizard.grabDefault()
100
101 self._mainWindow = window
102
103 self._statusIcon = StatusIcon(iconDirectory, self)
104
105 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
106 else gdk.WATCH)
107
108 @property
109 def simulator(self):
110 """Get the simulator used by us."""
111 return self._simulator
112
113 @property
114 def flight(self):
115 """Get the flight being performed."""
116 return self._flight
117
118 @property
119 def zfw(self):
120 """Get Zero-Fuel Weight calculated for the current flight."""
121 return self._wizard.zfw
122
123 @property
124 def cruiseAltitude(self):
125 """Get cruise altitude calculated for the current flight."""
126 return self._wizard.cruiseAltitude
127
128 @property
129 def v1(self):
130 """Get the V1 speed calculated for the flight."""
131 return self._wizard.v1
132
133 @property
134 def vr(self):
135 """Get the Vr speed calculated for the flight."""
136 return self._wizard.vr
137
138 @property
139 def v2(self):
140 """Get the V2 speed calculated for the flight."""
141 return self._wizard.v2
142
143 @property
144 def vref(self):
145 """Get the Vref speed calculated for the flight."""
146 return self._wizard.vref
147
148 def run(self):
149 """Run the GUI."""
150 if self.config.autoUpdate:
151 self._updater = Updater(self,
152 self._programDirectory,
153 self.config.updateURL,
154 self._mainWindow)
155 self._updater.start()
156
157 gtk.main()
158
159 if self._flight is not None and self._connected:
160 self.stopMonitoring()
161 self._flight.simulator.disconnect()
162
163 def connected(self, fsType, descriptor):
164 """Called when we have connected to the simulator."""
165 self._connected = True
166 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
167 gobject.idle_add(self._handleConnected, fsType, descriptor)
168
169 def _handleConnected(self, fsType, descriptor):
170 """Called when the connection to the simulator has succeeded."""
171 self._statusbar.updateConnection(self._connecting, self._connected)
172 self.endBusy()
173 if not self._reconnecting:
174 self._wizard.connected(fsType, descriptor)
175 self._reconnecting = False
176
177 def connectionFailed(self):
178 """Called when the connection failed."""
179 self._logger.untimedMessage("Connection to the simulator failed")
180 gobject.idle_add(self._connectionFailed)
181
182 def _connectionFailed(self):
183 """Called when the connection failed."""
184 self.endBusy()
185 self._statusbar.updateConnection(self._connecting, self._connected)
186
187 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
188 message_format =
189 "Cannot connect to the simulator.",
190 parent = self._mainWindow)
191 dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
192 "to try the connection again, "
193 "or <b>Cancel</b> to cancel the flight.")
194
195 dialog.add_button("_Cancel", 0)
196 dialog.add_button("_Try again", 1)
197 dialog.set_default_response(1)
198
199 result = dialog.run()
200 dialog.hide()
201 if result == 1:
202 self.beginBusy("Connecting to the simulator.")
203 self._simulator.reconnect()
204 else:
205 self._connecting = False
206 self._reconnecting = False
207 self._statusbar.updateConnection(self._connecting, self._connected)
208 self._wizard.connectionFailed()
209 self._flightInfo.reset()
210
211 def disconnected(self):
212 """Called when we have disconnected from the simulator."""
213 self._connected = False
214 self._logger.untimedMessage("Disconnected from the simulator")
215
216 gobject.idle_add(self._disconnected)
217
218 def _disconnected(self):
219 """Called when we have disconnected from the simulator unexpectedly."""
220 self._statusbar.updateConnection(self._connecting, self._connected)
221
222 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
223 message_format =
224 "The connection to the simulator failed unexpectedly.",
225 parent = self._mainWindow)
226 dialog.format_secondary_markup("If the simulator has crashed, restart it "
227 "and restore your flight as much as possible "
228 "to the state it was in before the crash.\n"
229 "Then press <b>Reconnect</b> to reconnect.\n\n"
230 "If you want to cancel the flight, press <b>Cancel</b>.")
231
232 dialog.add_button("_Cancel", 0)
233 dialog.add_button("_Reconnect", 1)
234 dialog.set_default_response(1)
235
236 result = dialog.run()
237 dialog.hide()
238 if result == 1:
239 self.beginBusy("Connecting to the simulator.")
240 self._reconnecting = True
241 self._simulator.reconnect()
242 else:
243 self._connecting = False
244 self._reconnecting = False
245 self._statusbar.updateConnection(self._connecting, self._connected)
246 self._wizard.disconnected()
247 self._flightInfo.reset()
248
249 def write(self, msg):
250 """Write the given message to the log."""
251 gobject.idle_add(self._writeLog, msg)
252
253 def check(self, flight, aircraft, logger, oldState, state):
254 """Update the data."""
255 gobject.idle_add(self._monitorWindow.setData, state)
256 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
257
258 def resetFlightStatus(self):
259 """Reset the status of the flight."""
260 self._statusbar.resetFlightStatus()
261 self._statusbar.updateTime()
262 self._statusIcon.resetFlightStatus()
263
264 def setStage(self, stage):
265 """Set the stage of the flight."""
266 gobject.idle_add(self._setStage, stage)
267
268 def _setStage(self, stage):
269 """Set the stage of the flight."""
270 self._statusbar.setStage(stage)
271 self._statusIcon.setStage(stage)
272 self._wizard.setStage(stage)
273 if stage==const.STAGE_END:
274 self.stopMonitoring()
275 self.simulator.disconnect()
276 self._connecting = False
277 self._connected = False
278 self._statusbar.updateConnection(self._connecting, self._connected)
279
280 def setRating(self, rating):
281 """Set the rating of the flight."""
282 gobject.idle_add(self._setRating, rating)
283
284 def _setRating(self, rating):
285 """Set the rating of the flight."""
286 self._statusbar.setRating(rating)
287 self._statusIcon.setRating(rating)
288
289 def setNoGo(self, reason):
290 """Set the rating of the flight to No-Go with the given reason."""
291 gobject.idle_add(self._setNoGo, reason)
292
293 def _setNoGo(self, reason):
294 """Set the rating of the flight."""
295 self._statusbar.setNoGo(reason)
296 self._statusIcon.setNoGo(reason)
297
298 def _handleMainWindowState(self, window, event):
299 """Hande a change in the state of the window"""
300 iconified = gdk.WindowState.ICONIFIED if pygobject \
301 else gdk.WINDOW_STATE_ICONIFIED
302 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
303 self.hideMainWindow(savePosition = False)
304
305 def hideMainWindow(self, savePosition = True):
306 """Hide the main window and save its position."""
307 if savePosition:
308 (self._mainWindowX, self._mainWindowY) = \
309 self._mainWindow.get_window().get_root_origin()
310 else:
311 self._mainWindowX = self._mainWindowY = None
312 self._mainWindow.hide()
313 self._statusIcon.mainWindowHidden()
314 return True
315
316 def showMainWindow(self):
317 """Show the main window at its former position."""
318 if self._mainWindowX is not None and self._mainWindowY is not None:
319 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
320
321 self._mainWindow.show()
322 self._mainWindow.deiconify()
323
324 self._statusIcon.mainWindowShown()
325
326 def toggleMainWindow(self):
327 """Toggle the main window."""
328 if self._mainWindow.get_visible():
329 self.hideMainWindow()
330 else:
331 self.showMainWindow()
332
333 def hideMonitorWindow(self, savePosition = True):
334 """Hide the monitor window."""
335 if savePosition:
336 (self._monitorWindowX, self._monitorWindowY) = \
337 self._monitorWindow.get_window().get_root_origin()
338 else:
339 self._monitorWindowX = self._monitorWindowY = None
340 self._monitorWindow.hide()
341 self._statusIcon.monitorWindowHidden()
342 return True
343
344 def showMonitorWindow(self):
345 """Show the monitor window."""
346 if self._monitorWindowX is not None and self._monitorWindowY is not None:
347 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
348 self._monitorWindow.show_all()
349 self._statusIcon.monitorWindowShown()
350
351 def restart(self):
352 """Quit and restart the application."""
353 self.toRestart = True
354 self._quit(force = True)
355
356 def flushStdIO(self):
357 """Flush any text to the standard error that could not be logged."""
358 if self._stdioText:
359 sys.__stderr__.write(self._stdioText)
360
361 def writeStdIO(self, text):
362 """Write the given text into standard I/O log."""
363 with self._stdioLock:
364 self._stdioText += text
365
366 gobject.idle_add(self._writeStdIO)
367
368 def beginBusy(self, message):
369 """Begin a period of background processing."""
370 self._mainWindow.get_window().set_cursor(self._busyCursor)
371 self._statusbar.updateBusyState(message)
372
373 def endBusy(self):
374 """End a period of background processing."""
375 self._mainWindow.get_window().set_cursor(None)
376 self._statusbar.updateBusyState(None)
377
378 def _writeStdIO(self):
379 """Perform the real writing."""
380 with self._stdioLock:
381 text = self._stdioText
382 self._stdioText = ""
383 if not text: return
384
385 lines = text.splitlines()
386 if text[-1]=="\n":
387 text = ""
388 else:
389 text = lines[-1]
390 lines = lines[:-1]
391
392 for line in lines:
393 if self._stdioAfterNewLine:
394 line = "[STDIO] " + line
395 self._writeLog(line + "\n")
396 self._stdioAfterNewLine = True
397
398 if text:
399 if self._stdioAfterNewLine:
400 text = "[STDIO] " + text
401 self._writeLog(text)
402 self._stdioAfterNewLine = False
403
404 def connectSimulator(self, aircraftType):
405 """Connect to the simulator for the first time."""
406 self._logger.reset()
407
408 self._flight = flight.Flight(self._logger, self)
409 self._flight.aircraftType = aircraftType
410 self._flight.aircraft = acft.Aircraft.create(self._flight)
411 self._flight.aircraft._checkers.append(self)
412
413 if self._simulator is None:
414 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
415
416 self._flight.simulator = self._simulator
417
418 self.beginBusy("Connecting to the simulator...")
419 self._statusbar.updateConnection(self._connecting, self._connected)
420
421 self._connecting = True
422 self._simulator.connect(self._flight.aircraft)
423
424 def startMonitoring(self):
425 """Start monitoring."""
426 if not self._monitoring:
427 self.simulator.startMonitoring()
428 self._monitoring = True
429
430 def stopMonitoring(self):
431 """Stop monitoring."""
432 if self._monitoring:
433 self.simulator.stopMonitoring()
434 self._monitoring = False
435
436 def _buildLogFrame(self):
437 """Build the frame for the log."""
438 logFrame = gtk.Frame(label = "Log")
439
440 frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
441
442 frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
443 padding_left = 16, padding_right = 16)
444
445 logFrame.add(frameAlignment)
446
447 logScroller = gtk.ScrolledWindow()
448 self._logView = gtk.TextView()
449 self._logView.set_editable(False)
450 logScroller.add(self._logView)
451
452 logBox = gtk.VBox()
453 logBox.pack_start(logScroller, True, True, 0)
454 logBox.set_size_request(-1, 200)
455
456 frameAlignment.add(logBox)
457
458 return logFrame
459
460 def _writeLog(self, msg):
461 """Write the given message to the log."""
462 buffer = self._logView.get_buffer()
463 buffer.insert(buffer.get_end_iter(), msg)
464 self._logView.scroll_mark_onscreen(buffer.get_insert())
465
466 def _quit(self, what = None, force = False):
467 """Quit from the application."""
468 if force:
469 result=RESPONSETYPE_YES
470 else:
471 dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
472 buttons = BUTTONSTYPE_YES_NO,
473 message_format =
474 "Are you sure to quit the logger?")
475 result = dialog.run()
476 dialog.hide()
477
478 if result==RESPONSETYPE_YES:
479 self._statusIcon.destroy()
480 return gtk.main_quit()
481
482 def _notebookPageSwitch(self, notebook, page, page_num):
483 """Called when the current page of the notebook has changed."""
484 if page_num==0:
485 gobject.idle_add(self._wizard.grabDefault)
486 else:
487 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.