source: src/mlx/gui/gui.py@ 97:f885322fb296

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

The PIREP can be created and sent.

File size: 23.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 @staticmethod
27 def _formatFlightLogLine(timeStr, line):
28 """Format the given line for flight logging."""
29 if timeStr is not None:
30 line = timeStr + ": " + line
31 return line + "\n"
32
33 def __init__(self, programDirectory, config):
34 """Construct the GUI."""
35 gobject.threads_init()
36
37 self._programDirectory = programDirectory
38 self.config = config
39 self._connecting = False
40 self._reconnecting = False
41 self._connected = False
42 self._logger = logger.Logger(self)
43 self._flight = None
44 self._simulator = None
45 self._monitoring = False
46
47 self._stdioLock = threading.Lock()
48 self._stdioText = ""
49
50 self.webHandler = web.Handler()
51 self.webHandler.start()
52
53 self.toRestart = False
54
55 def build(self, iconDirectory):
56 """Build the GUI."""
57
58 window = gtk.Window()
59 window.set_title("MAVA Logger X " + const.VERSION)
60 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
61 window.connect("delete-event",
62 lambda a, b: self.hideMainWindow())
63 window.connect("window-state-event", self._handleMainWindowState)
64 accelGroup = gtk.AccelGroup()
65 window.add_accel_group(accelGroup)
66
67 mainVBox = gtk.VBox()
68 window.add(mainVBox)
69
70 menuBar = self._buildMenuBar(accelGroup)
71 mainVBox.pack_start(menuBar, False, False, 0)
72
73 self._notebook = gtk.Notebook()
74 mainVBox.pack_start(self._notebook, True, True, 4)
75
76 self._wizard = Wizard(self)
77 label = gtk.Label("Fligh_t")
78 label.set_use_underline(True)
79 label.set_tooltip_text("Flight wizard")
80 self._notebook.append_page(self._wizard, label)
81
82 self._flightInfo = FlightInfo(self)
83 label = gtk.Label("Flight _info")
84 label.set_use_underline(True)
85 label.set_tooltip_text("Flight information")
86 self._notebook.append_page(self._flightInfo, label)
87 self._flightInfo.disable()
88
89 (logWidget, self._logView) = self._buildLogWidget()
90 label = gtk.Label("_Log")
91 label.set_use_underline(True)
92 label.set_tooltip_text("The log of your flight that will be sent to the MAVA website")
93 self._notebook.append_page(logWidget, label)
94
95 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
96 self._debugLogWidget.show_all()
97
98 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
99
100 self._statusbar = Statusbar()
101 mainVBox.pack_start(self._statusbar, False, False, 0)
102
103 self._notebook.connect("switch-page", self._notebookPageSwitch)
104
105 self._monitorWindow = MonitorWindow(self, iconDirectory)
106 self._monitorWindow.add_accel_group(accelGroup)
107 self._monitorWindowX = None
108 self._monitorWindowY = None
109 self._selfToggling = False
110
111 window.show_all()
112 self._wizard.grabDefault()
113
114 self._mainWindow = window
115
116 self._statusIcon = StatusIcon(iconDirectory, self)
117
118 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
119 else gdk.WATCH)
120
121 @property
122 def logger(self):
123 """Get the logger used by us."""
124 return self._logger
125
126 @property
127 def simulator(self):
128 """Get the simulator used by us."""
129 return self._simulator
130
131 @property
132 def flight(self):
133 """Get the flight being performed."""
134 return self._flight
135
136 @property
137 def bookedFlight(self):
138 """Get the booked flight selected, if any."""
139 return self._wizard.bookedFlight
140
141 @property
142 def cargoWeight(self):
143 """Get the cargo weight."""
144 return self._wizard.cargoWeight
145
146 @property
147 def zfw(self):
148 """Get Zero-Fuel Weight calculated for the current flight."""
149 return self._wizard.zfw
150
151 @property
152 def filedCruiseAltitude(self):
153 """Get cruise altitude filed for the current flight."""
154 return self._wizard.filedCruiseAltitude
155
156 @property
157 def cruiseAltitude(self):
158 """Get cruise altitude set for the current flight."""
159 return self._wizard.cruiseAltitude
160
161 @property
162 def route(self):
163 """Get the flight route."""
164 return self._wizard.route
165
166 @property
167 def departureMETAR(self):
168 """Get the METAR of the deprature airport."""
169 return self._wizard.departureMETAR
170
171 @property
172 def arrivalMETAR(self):
173 """Get the METAR of the deprature airport."""
174 return self._wizard.arrivalMETAR
175
176 @property
177 def departureRunway(self):
178 """Get the name of the departure runway."""
179 return self._wizard.departureRunway
180
181 @property
182 def sid(self):
183 """Get the SID."""
184 return self._wizard.sid
185
186 @property
187 def v1(self):
188 """Get the V1 speed calculated for the flight."""
189 return self._wizard.v1
190
191 @property
192 def vr(self):
193 """Get the Vr speed calculated for the flight."""
194 return self._wizard.vr
195
196 @property
197 def v2(self):
198 """Get the V2 speed calculated for the flight."""
199 return self._wizard.v2
200
201 @property
202 def arrivalRunway(self):
203 """Get the arrival runway."""
204 return self._wizard.arrivalRunway
205
206 @property
207 def star(self):
208 """Get the STAR."""
209 return self._wizard.star
210
211 @property
212 def transition(self):
213 """Get the transition."""
214 return self._wizard.transition
215
216 @property
217 def approachType(self):
218 """Get the approach type."""
219 return self._wizard.approachType
220
221 @property
222 def vref(self):
223 """Get the Vref speed calculated for the flight."""
224 return self._wizard.vref
225
226 @property
227 def flightType(self):
228 """Get the flight type."""
229 return self._wizard.flightType
230
231 @property
232 def online(self):
233 """Get whether the flight was online or not."""
234 return self._wizard.online
235
236 @property
237 def comments(self):
238 """Get the comments."""
239 return self._flightInfo.comments
240
241 @property
242 def flightDefects(self):
243 """Get the flight defects."""
244 return self._flightInfo.flightDefects
245
246 def run(self):
247 """Run the GUI."""
248 if self.config.autoUpdate:
249 self._updater = Updater(self,
250 self._programDirectory,
251 self.config.updateURL,
252 self._mainWindow)
253 self._updater.start()
254
255 gtk.main()
256
257 self._disconnect()
258
259 def connected(self, fsType, descriptor):
260 """Called when we have connected to the simulator."""
261 self._connected = True
262 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
263 gobject.idle_add(self._handleConnected, fsType, descriptor)
264
265 def _handleConnected(self, fsType, descriptor):
266 """Called when the connection to the simulator has succeeded."""
267 self._statusbar.updateConnection(self._connecting, self._connected)
268 self.endBusy()
269 if not self._reconnecting:
270 self._wizard.connected(fsType, descriptor)
271 self._reconnecting = False
272
273 def connectionFailed(self):
274 """Called when the connection failed."""
275 self._logger.untimedMessage("Connection to the simulator failed")
276 gobject.idle_add(self._connectionFailed)
277
278 def _connectionFailed(self):
279 """Called when the connection failed."""
280 self.endBusy()
281 self._statusbar.updateConnection(self._connecting, self._connected)
282
283 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
284 message_format =
285 "Cannot connect to the simulator.",
286 parent = self._mainWindow)
287 dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
288 "to try the connection again, "
289 "or <b>Cancel</b> to cancel the flight.")
290
291 dialog.add_button("_Cancel", 0)
292 dialog.add_button("_Try again", 1)
293 dialog.set_default_response(1)
294
295 result = dialog.run()
296 dialog.hide()
297 if result == 1:
298 self.beginBusy("Connecting to the simulator.")
299 self._simulator.reconnect()
300 else:
301 self.reset()
302
303 def disconnected(self):
304 """Called when we have disconnected from the simulator."""
305 self._connected = False
306 self._logger.untimedMessage("Disconnected from the simulator")
307
308 gobject.idle_add(self._disconnected)
309
310 def _disconnected(self):
311 """Called when we have disconnected from the simulator unexpectedly."""
312 self._statusbar.updateConnection(self._connecting, self._connected)
313
314 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
315 message_format =
316 "The connection to the simulator failed unexpectedly.",
317 parent = self._mainWindow)
318 dialog.format_secondary_markup("If the simulator has crashed, restart it "
319 "and restore your flight as much as possible "
320 "to the state it was in before the crash.\n"
321 "Then press <b>Reconnect</b> to reconnect.\n\n"
322 "If you want to cancel the flight, press <b>Cancel</b>.")
323
324 dialog.add_button("_Cancel", 0)
325 dialog.add_button("_Reconnect", 1)
326 dialog.set_default_response(1)
327
328 result = dialog.run()
329 dialog.hide()
330 if result == 1:
331 self.beginBusy("Connecting to the simulator.")
332 self._reconnecting = True
333 self._simulator.reconnect()
334 else:
335 self.reset()
336
337 def enableFlightInfo(self):
338 """Enable the flight info tab."""
339 self._flightInfo.enable()
340
341 def reset(self):
342 """Reset the GUI."""
343 self._disconnect()
344
345 self._flightInfo.reset()
346 self._flightInfo.disable()
347 self.resetFlightStatus()
348
349 self._wizard.reset()
350 self._notebook.set_current_page(0)
351
352 self._logView.get_buffer().set_text("")
353
354 def _disconnect(self):
355 """Disconnect from the simulator if connected."""
356 self.stopMonitoring()
357
358 if self._connected:
359 self._flight.simulator.disconnect()
360 self._connected = False
361
362 self._connecting = False
363 self._reconnecting = False
364 self._statusbar.updateConnection(False, False)
365
366 def addFlightLogLine(self, timeStr, line):
367 """Write the given message line to the log."""
368 gobject.idle_add(self._writeLog,
369 GUI._formatFlightLogLine(timeStr, line),
370 self._logView)
371
372 def updateFlightLogLine(self, index, timeStr, line):
373 """Update the line with the given index."""
374 gobject.idle_add(self._updateFlightLogLine, index,
375 GUI._formatFlightLogLine(timeStr, line))
376
377 def _updateFlightLogLine(self, index, line):
378 """Replace the contents of the given line in the log."""
379 buffer = self._logView.get_buffer()
380 startIter = buffer.get_iter_at_line(index)
381 endIter = buffer.get_iter_at_line(index + 1)
382 buffer.delete(startIter, endIter)
383 buffer.insert(startIter, line)
384 self._logView.scroll_mark_onscreen(buffer.get_insert())
385
386 def check(self, flight, aircraft, logger, oldState, state):
387 """Update the data."""
388 gobject.idle_add(self._monitorWindow.setData, state)
389 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
390
391 def resetFlightStatus(self):
392 """Reset the status of the flight."""
393 self._statusbar.resetFlightStatus()
394 self._statusbar.updateTime()
395 self._statusIcon.resetFlightStatus()
396
397 def setStage(self, stage):
398 """Set the stage of the flight."""
399 gobject.idle_add(self._setStage, stage)
400
401 def _setStage(self, stage):
402 """Set the stage of the flight."""
403 self._statusbar.setStage(stage)
404 self._statusIcon.setStage(stage)
405 self._wizard.setStage(stage)
406 if stage==const.STAGE_END:
407 self._disconnect()
408
409 def setRating(self, rating):
410 """Set the rating of the flight."""
411 gobject.idle_add(self._setRating, rating)
412
413 def _setRating(self, rating):
414 """Set the rating of the flight."""
415 self._statusbar.setRating(rating)
416 self._statusIcon.setRating(rating)
417
418 def setNoGo(self, reason):
419 """Set the rating of the flight to No-Go with the given reason."""
420 gobject.idle_add(self._setNoGo, reason)
421
422 def _setNoGo(self, reason):
423 """Set the rating of the flight."""
424 self._statusbar.setNoGo(reason)
425 self._statusIcon.setNoGo(reason)
426
427 def _handleMainWindowState(self, window, event):
428 """Hande a change in the state of the window"""
429 iconified = gdk.WindowState.ICONIFIED if pygobject \
430 else gdk.WINDOW_STATE_ICONIFIED
431 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
432 self.hideMainWindow(savePosition = False)
433
434 def hideMainWindow(self, savePosition = True):
435 """Hide the main window and save its position."""
436 if savePosition:
437 (self._mainWindowX, self._mainWindowY) = \
438 self._mainWindow.get_window().get_root_origin()
439 else:
440 self._mainWindowX = self._mainWindowY = None
441 self._mainWindow.hide()
442 self._statusIcon.mainWindowHidden()
443 return True
444
445 def showMainWindow(self):
446 """Show the main window at its former position."""
447 if self._mainWindowX is not None and self._mainWindowY is not None:
448 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
449
450 self._mainWindow.show()
451 self._mainWindow.deiconify()
452
453 self._statusIcon.mainWindowShown()
454
455 def toggleMainWindow(self):
456 """Toggle the main window."""
457 if self._mainWindow.get_visible():
458 self.hideMainWindow()
459 else:
460 self.showMainWindow()
461
462 def hideMonitorWindow(self, savePosition = True):
463 """Hide the monitor window."""
464 if savePosition:
465 (self._monitorWindowX, self._monitorWindowY) = \
466 self._monitorWindow.get_window().get_root_origin()
467 else:
468 self._monitorWindowX = self._monitorWindowY = None
469 self._monitorWindow.hide()
470 self._statusIcon.monitorWindowHidden()
471 if self._showMonitorMenuItem.get_active():
472 self._selfToggling = True
473 self._showMonitorMenuItem.set_active(False)
474 return True
475
476 def showMonitorWindow(self):
477 """Show the monitor window."""
478 if self._monitorWindowX is not None and self._monitorWindowY is not None:
479 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
480 self._monitorWindow.show_all()
481 self._statusIcon.monitorWindowShown()
482 if not self._showMonitorMenuItem.get_active():
483 self._selfToggling = True
484 self._showMonitorMenuItem.set_active(True)
485
486 def _toggleMonitorWindow(self, menuItem):
487 if self._selfToggling:
488 self._selfToggling = False
489 elif self._monitorWindow.get_visible():
490 self.hideMonitorWindow()
491 else:
492 self.showMonitorWindow()
493
494 def restart(self):
495 """Quit and restart the application."""
496 self.toRestart = True
497 self._quit(force = True)
498
499 def flushStdIO(self):
500 """Flush any text to the standard error that could not be logged."""
501 if self._stdioText:
502 sys.__stderr__.write(self._stdioText)
503
504 def writeStdIO(self, text):
505 """Write the given text into standard I/O log."""
506 with self._stdioLock:
507 self._stdioText += text
508
509 gobject.idle_add(self._writeStdIO)
510
511 def beginBusy(self, message):
512 """Begin a period of background processing."""
513 self._wizard.set_sensitive(False)
514 self._mainWindow.get_window().set_cursor(self._busyCursor)
515 self._statusbar.updateBusyState(message)
516
517 def endBusy(self):
518 """End a period of background processing."""
519 self._mainWindow.get_window().set_cursor(None)
520 self._wizard.set_sensitive(True)
521 self._statusbar.updateBusyState(None)
522
523 def _writeStdIO(self):
524 """Perform the real writing."""
525 with self._stdioLock:
526 text = self._stdioText
527 self._stdioText = ""
528 if not text: return
529
530 lines = text.splitlines()
531 if text[-1]=="\n":
532 text = ""
533 else:
534 text = lines[-1]
535 lines = lines[:-1]
536
537 for line in lines:
538 #print >> sys.__stdout__, line
539 self._writeLog(line + "\n", self._debugLogView)
540
541 if text:
542 #print >> sys.__stdout__, text,
543 self._writeLog(text, self._debugLogView)
544
545 def connectSimulator(self, aircraftType):
546 """Connect to the simulator for the first time."""
547 self._logger.reset()
548
549 self._flight = flight.Flight(self._logger, self)
550 self._flight.aircraftType = aircraftType
551 self._flight.aircraft = acft.Aircraft.create(self._flight)
552 self._flight.aircraft._checkers.append(self)
553
554 if self._simulator is None:
555 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
556
557 self._flight.simulator = self._simulator
558
559 self.beginBusy("Connecting to the simulator...")
560 self._statusbar.updateConnection(self._connecting, self._connected)
561
562 self._connecting = True
563 self._simulator.connect(self._flight.aircraft)
564
565 def startMonitoring(self):
566 """Start monitoring."""
567 if not self._monitoring:
568 self.simulator.startMonitoring()
569 self._monitoring = True
570
571 def stopMonitoring(self):
572 """Stop monitoring."""
573 if self._monitoring:
574 self.simulator.stopMonitoring()
575 self._monitoring = False
576
577 def _buildMenuBar(self, accelGroup):
578 """Build the main menu bar."""
579 menuBar = gtk.MenuBar()
580
581 fileMenuItem = gtk.MenuItem("File")
582 fileMenu = gtk.Menu()
583 fileMenuItem.set_submenu(fileMenu)
584 menuBar.append(fileMenuItem)
585
586 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
587 quitMenuItem.set_use_stock(True)
588 quitMenuItem.add_accelerator("activate", accelGroup,
589 ord("q"), CONTROL_MASK,
590 ACCEL_VISIBLE)
591 quitMenuItem.connect("activate", self._quit)
592 fileMenu.append(quitMenuItem)
593
594
595 viewMenuItem = gtk.MenuItem("View")
596 viewMenu = gtk.Menu()
597 viewMenuItem.set_submenu(viewMenu)
598 menuBar.append(viewMenuItem)
599
600 self._showMonitorMenuItem = gtk.CheckMenuItem()
601 self._showMonitorMenuItem.set_label("Show _monitor window")
602 self._showMonitorMenuItem.set_use_underline(True)
603 self._showMonitorMenuItem.set_active(False)
604 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
605 ord("m"), CONTROL_MASK,
606 ACCEL_VISIBLE)
607 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
608 viewMenu.append(self._showMonitorMenuItem)
609
610 showDebugMenuItem = gtk.CheckMenuItem()
611 showDebugMenuItem.set_label("Show _debug log")
612 showDebugMenuItem.set_use_underline(True)
613 showDebugMenuItem.set_active(False)
614 showDebugMenuItem.add_accelerator("activate", accelGroup,
615 ord("d"), CONTROL_MASK,
616 ACCEL_VISIBLE)
617 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
618 viewMenu.append(showDebugMenuItem)
619
620 return menuBar
621
622 def _toggleDebugLog(self, menuItem):
623 """Toggle the debug log."""
624 if menuItem.get_active():
625 label = gtk.Label("_Debug log")
626 label.set_use_underline(True)
627 label.set_tooltip_text("Log with debugging information.")
628 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
629 self._notebook.set_current_page(self._debugLogPage)
630 else:
631 self._notebook.remove_page(self._debugLogPage)
632
633 def _buildLogWidget(self):
634 """Build the widget for the log."""
635 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
636
637 alignment.set_padding(padding_top = 8, padding_bottom = 8,
638 padding_left = 16, padding_right = 16)
639
640 logScroller = gtk.ScrolledWindow()
641 # FIXME: these should be constants in common
642 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
643 else gtk.POLICY_AUTOMATIC,
644 gtk.PolicyType.AUTOMATIC if pygobject
645 else gtk.POLICY_AUTOMATIC)
646 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
647 else gtk.SHADOW_IN)
648 logView = gtk.TextView()
649 logView.set_editable(False)
650 logScroller.add(logView)
651
652 logBox = gtk.VBox()
653 logBox.pack_start(logScroller, True, True, 0)
654 logBox.set_size_request(-1, 200)
655
656 alignment.add(logBox)
657
658 return (alignment, logView)
659
660 def _writeLog(self, msg, logView):
661 """Write the given message to the log."""
662 buffer = logView.get_buffer()
663 buffer.insert(buffer.get_end_iter(), msg)
664 logView.scroll_mark_onscreen(buffer.get_insert())
665
666 def _quit(self, what = None, force = False):
667 """Quit from the application."""
668 if force:
669 result=RESPONSETYPE_YES
670 else:
671 dialog = gtk.MessageDialog(type = MESSAGETYPE_QUESTION,
672 buttons = BUTTONSTYPE_YES_NO,
673 message_format =
674 "Are you sure to quit the logger?")
675 result = dialog.run()
676 dialog.hide()
677
678 if result==RESPONSETYPE_YES:
679 self._statusIcon.destroy()
680 return gtk.main_quit()
681
682 def _notebookPageSwitch(self, notebook, page, page_num):
683 """Called when the current page of the notebook has changed."""
684 if page_num==0:
685 gobject.idle_add(self._wizard.grabDefault)
686 else:
687 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.