source: src/mlx/gui/gui.py@ 93:fbcbfa72cdb2

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

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

File size: 20.2 KB
RevLine 
[28]1# The main file for the GUI
2
[29]3from statusicon import StatusIcon
[32]4from statusbar import Statusbar
[90]5from info import FlightInfo
[36]6from update import Updater
[29]7from mlx.gui.common import *
[42]8from mlx.gui.flight import Wizard
[77]9from mlx.gui.monitor import MonitorWindow
[28]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
[41]16import mlx.web as web
[28]17
18import time
[38]19import threading
20import sys
[28]21
[77]22#------------------------------------------------------------------------------
23
[28]24class GUI(fs.ConnectionListener):
25 """The main GUI class."""
[36]26 def __init__(self, programDirectory, config):
[28]27 """Construct the GUI."""
28 gobject.threads_init()
29
[36]30 self._programDirectory = programDirectory
[42]31 self.config = config
[28]32 self._connecting = False
[59]33 self._reconnecting = False
[28]34 self._connected = False
35 self._logger = logger.Logger(output = self)
36 self._flight = None
37 self._simulator = None
[59]38 self._monitoring = False
[38]39
40 self._stdioLock = threading.Lock()
41 self._stdioText = ""
[28]42
[41]43 self.webHandler = web.Handler()
44 self.webHandler.start()
45
[38]46 self.toRestart = False
47
[29]48 def build(self, iconDirectory):
[28]49 """Build the GUI."""
[29]50
[36]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)
[93]57 accelGroup = gtk.AccelGroup()
58 window.add_accel_group(accelGroup)
[28]59
60 mainVBox = gtk.VBox()
[36]61 window.add(mainVBox)
[28]62
[93]63 menuBar = self._buildMenuBar(accelGroup)
64 mainVBox.pack_start(menuBar, False, False, 0)
65
[92]66 self._notebook = gtk.Notebook()
[93]67 mainVBox.pack_start(self._notebook, True, True, 4)
[42]68
[46]69 self._wizard = Wizard(self)
[90]70 label = gtk.Label("Fligh_t")
[46]71 label.set_use_underline(True)
72 label.set_tooltip_text("Flight wizard")
[92]73 self._notebook.append_page(self._wizard, label)
[42]74
[90]75 self._flightInfo = FlightInfo(self)
76 label = gtk.Label("Flight _info")
77 label.set_use_underline(True)
78 label.set_tooltip_text("Flight information")
[92]79 self._notebook.append_page(self._flightInfo, label)
[93]80 self._flightInfo.disable()
[90]81
[93]82 (logWidget, self._logView) = self._buildLogWidget()
[46]83 label = gtk.Label("_Log")
84 label.set_use_underline(True)
[93]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()
[32]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
[92]96 self._notebook.connect("switch-page", self._notebookPageSwitch)
[46]97
[77]98 self._monitorWindow = MonitorWindow(self, iconDirectory)
[93]99 self._monitorWindow.add_accel_group(accelGroup)
[77]100 self._monitorWindowX = None
101 self._monitorWindowY = None
[93]102 self._selfToggling = False
[77]103
[46]104 window.show_all()
105 self._wizard.grabDefault()
[28]106
[36]107 self._mainWindow = window
[29]108
109 self._statusIcon = StatusIcon(iconDirectory, self)
[28]110
[49]111 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
112 else gdk.WATCH)
113
[59]114 @property
115 def simulator(self):
116 """Get the simulator used by us."""
117 return self._simulator
118
[71]119 @property
120 def flight(self):
121 """Get the flight being performed."""
122 return self._flight
[84]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
[71]148
[86]149 @property
150 def vref(self):
151 """Get the Vref speed calculated for the flight."""
152 return self._wizard.vref
153
[28]154 def run(self):
155 """Run the GUI."""
[42]156 if self.config.autoUpdate:
[38]157 self._updater = Updater(self,
158 self._programDirectory,
[42]159 self.config.updateURL,
[36]160 self._mainWindow)
161 self._updater.start()
162
[28]163 gtk.main()
[36]164
[91]165 self._disconnect()
[28]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,))
[59]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:
[91]209 self.reset()
[28]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")
[59]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:
[91]243 self.reset()
244
[93]245 def enableFlightInfo(self):
246 """Enable the flight info tab."""
247 self._flightInfo.enable()
248
[91]249 def reset(self):
250 """Reset the GUI."""
251 self._disconnect()
[92]252
[91]253 self._flightInfo.reset()
[93]254 self._flightInfo.disable()
[91]255 self.resetFlightStatus()
[28]256
[92]257 self._wizard.reset()
258 self._notebook.set_current_page(0)
259
260 self._logView.get_buffer().set_text("")
261
[91]262 def _disconnect(self):
263 """Disconnect from the simulator if connected."""
[92]264 self.stopMonitoring()
265
[91]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
[28]274 def write(self, msg):
275 """Write the given message to the log."""
[93]276 gobject.idle_add(self._writeLog, msg, self._logView)
[28]277
278 def check(self, flight, aircraft, logger, oldState, state):
279 """Update the data."""
[77]280 gobject.idle_add(self._monitorWindow.setData, state)
[80]281 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
[28]282
[31]283 def resetFlightStatus(self):
284 """Reset the status of the flight."""
[32]285 self._statusbar.resetFlightStatus()
[80]286 self._statusbar.updateTime()
[31]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."""
[32]295 self._statusbar.setStage(stage)
[31]296 self._statusIcon.setStage(stage)
[84]297 self._wizard.setStage(stage)
[88]298 if stage==const.STAGE_END:
[91]299 self._disconnect()
[31]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."""
[32]307 self._statusbar.setRating(rating)
[31]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."""
[32]316 self._statusbar.setNoGo(reason)
[31]317 self._statusIcon.setNoGo(reason)
318
[29]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:
[35]324 self.hideMainWindow(savePosition = False)
[29]325
[35]326 def hideMainWindow(self, savePosition = True):
[29]327 """Hide the main window and save its position."""
[35]328 if savePosition:
329 (self._mainWindowX, self._mainWindowY) = \
330 self._mainWindow.get_window().get_root_origin()
331 else:
332 self._mainWindowX = self._mainWindowY = None
[29]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."""
[35]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()
[29]343 self._mainWindow.deiconify()
[35]344
[29]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()
[36]353
[77]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()
[93]363 if self._showMonitorMenuItem.get_active():
364 self._selfToggling = True
365 self._showMonitorMenuItem.set_active(False)
[77]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()
[93]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()
[77]385
[38]386 def restart(self):
387 """Quit and restart the application."""
388 self.toRestart = True
[81]389 self._quit(force = True)
[38]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
[36]396 def writeStdIO(self, text):
397 """Write the given text into standard I/O log."""
[38]398 with self._stdioLock:
399 self._stdioText += text
400
401 gobject.idle_add(self._writeStdIO)
[36]402
[49]403 def beginBusy(self, message):
404 """Begin a period of background processing."""
[93]405 self._wizard.set_sensitive(False)
[49]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)
[93]412 self._wizard.set_sensitive(True)
[49]413 self._statusbar.updateBusyState(None)
414
[38]415 def _writeStdIO(self):
[36]416 """Perform the real writing."""
[38]417 with self._stdioLock:
418 text = self._stdioText
419 self._stdioText = ""
420 if not text: return
421
[36]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:
[93]430 print >> sys.__stdout__, line
431 self._writeLog(line + "\n", self._debugLogView)
[36]432
433 if text:
[93]434 print >> sys.__stdout__, line,
435 self._writeLog(text, self._debugLogView)
[51]436
[59]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
[70]457 def startMonitoring(self):
458 """Start monitoring."""
[88]459 if not self._monitoring:
460 self.simulator.startMonitoring()
461 self._monitoring = True
[70]462
463 def stopMonitoring(self):
464 """Stop monitoring."""
[88]465 if self._monitoring:
466 self.simulator.stopMonitoring()
467 self._monitoring = False
[70]468
[93]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)
[28]491
[93]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)
[28]511
[93]512 return menuBar
[28]513
[93]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)
[28]531
532 logScroller = gtk.ScrolledWindow()
[93]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)
[28]543
544 logBox = gtk.VBox()
545 logBox.pack_start(logScroller, True, True, 0)
546 logBox.set_size_request(-1, 200)
547
[93]548 alignment.add(logBox)
[28]549
[93]550 return (alignment, logView)
[28]551
[93]552 def _writeLog(self, msg, logView):
[28]553 """Write the given message to the log."""
[93]554 buffer = logView.get_buffer()
[28]555 buffer.insert(buffer.get_end_iter(), msg)
[93]556 logView.scroll_mark_onscreen(buffer.get_insert())
[28]557
[81]558 def _quit(self, what = None, force = False):
[38]559 """Quit from the application."""
[81]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()
[76]569
570 if result==RESPONSETYPE_YES:
571 self._statusIcon.destroy()
572 return gtk.main_quit()
[38]573
[46]574 def _notebookPageSwitch(self, notebook, page, page_num):
575 """Called when the current page of the notebook has changed."""
576 if page_num==0:
[48]577 gobject.idle_add(self._wizard.grabDefault)
[46]578 else:
579 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.