source: src/mlx/gui/gui.py@ 94:dc8fbc53a90c

Last change on this file since 94:dc8fbc53a90c was 93:fbcbfa72cdb2, checked in by István Váradi <ivaradi@…>, 13 years ago

Implemented the beginnings of the main menu and the separate debug log tab

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