source: src/mlx/gui/gui.py@ 117:af3d52b9adc4

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

Implemented the weight help tab

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