source: src/mlx/gui/gui.py@ 87:b13b4d827920

Last change on this file since 87:b13b4d827920 was 86:285443d1c1e2, checked in by István Váradi <ivaradi@…>, 13 years ago

The IntegerEntry widget is used for the VRef as well on the landing page

File size: 16.1 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:
153 simulator = self._flight.simulator
154 if self._monitoring:
155 simulator.stopMonitoring()
156 self._monitoring = False
157 simulator.disconnect()
158
159 def connected(self, fsType, descriptor):
160 """Called when we have connected to the simulator."""
161 self._connected = True
162 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
163 gobject.idle_add(self._handleConnected, fsType, descriptor)
164
165 def _handleConnected(self, fsType, descriptor):
166 """Called when the connection to the simulator has succeeded."""
167 self._statusbar.updateConnection(self._connecting, self._connected)
168 self.endBusy()
169 if not self._reconnecting:
170 self._wizard.connected(fsType, descriptor)
171 self._reconnecting = False
172
173 def connectionFailed(self):
174 """Called when the connection failed."""
175 self._logger.untimedMessage("Connection to the simulator failed")
176 gobject.idle_add(self._connectionFailed)
177
178 def _connectionFailed(self):
179 """Called when the connection failed."""
180 self.endBusy()
181 self._statusbar.updateConnection(self._connecting, self._connected)
182
183 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
184 message_format =
185 "Cannot connect to the simulator.",
186 parent = self._mainWindow)
187 dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
188 "to try the connection again, "
189 "or <b>Cancel</b> to cancel the flight.")
190
191 dialog.add_button("_Cancel", 0)
192 dialog.add_button("_Try again", 1)
193 dialog.set_default_response(1)
194
195 result = dialog.run()
196 dialog.hide()
197 if result == 1:
198 self.beginBusy("Connecting to the simulator.")
199 self._simulator.reconnect()
200 else:
201 self._connecting = False
202 self._reconnecting = False
203 self._statusbar.updateConnection(self._connecting, self._connected)
204 self._wizard.connectionFailed()
205
206 def disconnected(self):
207 """Called when we have disconnected from the simulator."""
208 self._connected = False
209 self._logger.untimedMessage("Disconnected from the simulator")
210
211 gobject.idle_add(self._disconnected)
212
213 def _disconnected(self):
214 """Called when we have disconnected from the simulator unexpectedly."""
215 self._statusbar.updateConnection(self._connecting, self._connected)
216
217 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
218 message_format =
219 "The connection to the simulator failed unexpectedly.",
220 parent = self._mainWindow)
221 dialog.format_secondary_markup("If the simulator has crashed, restart it "
222 "and restore your flight as much as possible "
223 "to the state it was in before the crash.\n"
224 "Then press <b>Reconnect</b> to reconnect.\n\n"
225 "If you want to cancel the flight, press <b>Cancel</b>.")
226
227 dialog.add_button("_Cancel", 0)
228 dialog.add_button("_Reconnect", 1)
229 dialog.set_default_response(1)
230
231 result = dialog.run()
232 dialog.hide()
233 if result == 1:
234 self.beginBusy("Connecting to the simulator.")
235 self._reconnecting = True
236 self._simulator.reconnect()
237 else:
238 self._connecting = False
239 self._reconnecting = False
240 self._statusbar.updateConnection(self._connecting, self._connected)
241 self._wizard.disconnected()
242
243 def write(self, msg):
244 """Write the given message to the log."""
245 gobject.idle_add(self._writeLog, msg)
246
247 def check(self, flight, aircraft, logger, oldState, state):
248 """Update the data."""
249 gobject.idle_add(self._monitorWindow.setData, state)
250 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
251
252 def resetFlightStatus(self):
253 """Reset the status of the flight."""
254 self._statusbar.resetFlightStatus()
255 self._statusbar.updateTime()
256 self._statusIcon.resetFlightStatus()
257
258 def setStage(self, stage):
259 """Set the stage of the flight."""
260 gobject.idle_add(self._setStage, stage)
261
262 def _setStage(self, stage):
263 """Set the stage of the flight."""
264 self._statusbar.setStage(stage)
265 self._statusIcon.setStage(stage)
266 self._wizard.setStage(stage)
267
268 def setRating(self, rating):
269 """Set the rating of the flight."""
270 gobject.idle_add(self._setRating, rating)
271
272 def _setRating(self, rating):
273 """Set the rating of the flight."""
274 self._statusbar.setRating(rating)
275 self._statusIcon.setRating(rating)
276
277 def setNoGo(self, reason):
278 """Set the rating of the flight to No-Go with the given reason."""
279 gobject.idle_add(self._setNoGo, reason)
280
281 def _setNoGo(self, reason):
282 """Set the rating of the flight."""
283 self._statusbar.setNoGo(reason)
284 self._statusIcon.setNoGo(reason)
285
286 def _handleMainWindowState(self, window, event):
287 """Hande a change in the state of the window"""
288 iconified = gdk.WindowState.ICONIFIED if pygobject \
289 else gdk.WINDOW_STATE_ICONIFIED
290 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
291 self.hideMainWindow(savePosition = False)
292
293 def hideMainWindow(self, savePosition = True):
294 """Hide the main window and save its position."""
295 if savePosition:
296 (self._mainWindowX, self._mainWindowY) = \
297 self._mainWindow.get_window().get_root_origin()
298 else:
299 self._mainWindowX = self._mainWindowY = None
300 self._mainWindow.hide()
301 self._statusIcon.mainWindowHidden()
302 return True
303
304 def showMainWindow(self):
305 """Show the main window at its former position."""
306 if self._mainWindowX is not None and self._mainWindowY is not None:
307 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
308
309 self._mainWindow.show()
310 self._mainWindow.deiconify()
311
312 self._statusIcon.mainWindowShown()
313
314 def toggleMainWindow(self):
315 """Toggle the main window."""
316 if self._mainWindow.get_visible():
317 self.hideMainWindow()
318 else:
319 self.showMainWindow()
320
321 def hideMonitorWindow(self, savePosition = True):
322 """Hide the monitor window."""
323 if savePosition:
324 (self._monitorWindowX, self._monitorWindowY) = \
325 self._monitorWindow.get_window().get_root_origin()
326 else:
327 self._monitorWindowX = self._monitorWindowY = None
328 self._monitorWindow.hide()
329 self._statusIcon.monitorWindowHidden()
330 return True
331
332 def showMonitorWindow(self):
333 """Show the monitor window."""
334 if self._monitorWindowX is not None and self._monitorWindowY is not None:
335 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
336 self._monitorWindow.show_all()
337 self._statusIcon.monitorWindowShown()
338
339 def restart(self):
340 """Quit and restart the application."""
341 self.toRestart = True
342 self._quit(force = True)
343
344 def flushStdIO(self):
345 """Flush any text to the standard error that could not be logged."""
346 if self._stdioText:
347 sys.__stderr__.write(self._stdioText)
348
349 def writeStdIO(self, text):
350 """Write the given text into standard I/O log."""
351 with self._stdioLock:
352 self._stdioText += text
353
354 gobject.idle_add(self._writeStdIO)
355
356 def beginBusy(self, message):
357 """Begin a period of background processing."""
358 self._mainWindow.get_window().set_cursor(self._busyCursor)
359 self._statusbar.updateBusyState(message)
360
361 def endBusy(self):
362 """End a period of background processing."""
363 self._mainWindow.get_window().set_cursor(None)
364 self._statusbar.updateBusyState(None)
365
366 def _writeStdIO(self):
367 """Perform the real writing."""
368 with self._stdioLock:
369 text = self._stdioText
370 self._stdioText = ""
371 if not text: return
372
373 lines = text.splitlines()
374 if text[-1]=="\n":
375 text = ""
376 else:
377 text = lines[-1]
378 lines = lines[:-1]
379
380 for line in lines:
381 if self._stdioAfterNewLine:
382 line = "[STDIO] " + line
383 self._writeLog(line + "\n")
384 self._stdioAfterNewLine = True
385
386 if text:
387 if self._stdioAfterNewLine:
388 text = "[STDIO] " + text
389 self._writeLog(text)
390 self._stdioAfterNewLine = False
391
392 def connectSimulator(self, aircraftType):
393 """Connect to the simulator for the first time."""
394 self._logger.reset()
395
396 self._flight = flight.Flight(self._logger, self)
397 self._flight.aircraftType = aircraftType
398 self._flight.aircraft = acft.Aircraft.create(self._flight)
399 self._flight.aircraft._checkers.append(self)
400
401 if self._simulator is None:
402 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
403
404 self._flight.simulator = self._simulator
405
406 self.beginBusy("Connecting to the simulator...")
407 self._statusbar.updateConnection(self._connecting, self._connected)
408
409 self._connecting = True
410 self._simulator.connect(self._flight.aircraft)
411
412 def startMonitoring(self):
413 """Start monitoring."""
414 self._simulator.startMonitoring()
415 self._monitoring = True
416
417 def stopMonitoring(self):
418 """Stop monitoring."""
419 self._simulator.stoptMonitoring()
420 self._monitoring = False
421
422 def _buildLogFrame(self):
423 """Build the frame for the log."""
424 logFrame = gtk.Frame(label = "Log")
425
426 frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
427
428 frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
429 padding_left = 16, padding_right = 16)
430
431 logFrame.add(frameAlignment)
432
433 logScroller = gtk.ScrolledWindow()
434 self._logView = gtk.TextView()
435 self._logView.set_editable(False)
436 logScroller.add(self._logView)
437
438 logBox = gtk.VBox()
439 logBox.pack_start(logScroller, True, True, 0)
440 logBox.set_size_request(-1, 200)
441
442 frameAlignment.add(logBox)
443
444 return logFrame
445
446 def _writeLog(self, msg):
447 """Write the given message to the log."""
448 buffer = self._logView.get_buffer()
449 buffer.insert(buffer.get_end_iter(), msg)
450 self._logView.scroll_mark_onscreen(buffer.get_insert())
451
452 def _quit(self, what = None, force = False):
453 """Quit from the application."""
454 if force:
455 result=RESPONSETYPE_YES
456 else:
457 dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
458 buttons = BUTTONSTYPE_YES_NO,
459 message_format =
460 "Are you sure to quit the logger?")
461 result = dialog.run()
462 dialog.hide()
463
464 if result==RESPONSETYPE_YES:
465 self._statusIcon.destroy()
466 return gtk.main_quit()
467
468 def _notebookPageSwitch(self, notebook, page, page_num):
469 """Called when the current page of the notebook has changed."""
470 if page_num==0:
471 gobject.idle_add(self._wizard.grabDefault)
472 else:
473 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.