source: src/mlx/gui/gui.py@ 118:aaa1bc00131b

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

Implemented the gates tab

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