source: src/mlx/gui/gui.py@ 148:453ebaea9090

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

Implemented FS time synchronization

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