source: src/mlx/gui/gui.py@ 88:3fdb6ad947ad

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

The logger disconnects from the simulator if the flight has ended and the Forward button on the landing page can be activated only then.

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