source: src/mlx/gui/gui.py@ 292:8528c186485c

Last change on this file since 292:8528c186485c was 281:c9e392a0a8b1, checked in by István Váradi <ivaradi@…>, 12 years ago

Using icons instead of a drawing for the connection state

File size: 49.9 KB
Line 
1# The main file for the GUI
2# -*- coding: utf-8 -*-
3
4from statusicon import StatusIcon
5from statusbar import Statusbar
6from info import FlightInfo
7from update import Updater
8from mlx.gui.common import *
9from mlx.gui.flight import Wizard
10from mlx.gui.monitor import MonitorWindow
11from mlx.gui.weighthelp import WeightHelp
12from mlx.gui.gates import FleetGateStatus
13from mlx.gui.prefs import Preferences
14from mlx.gui.checklist import ChecklistEditor
15from mlx.gui.callouts import ApproachCalloutsEditor
16from mlx.gui.pirep import PIREPViewer
17
18import mlx.const as const
19import mlx.fs as fs
20import mlx.flight as flight
21import mlx.logger as logger
22import mlx.acft as acft
23import mlx.web as web
24import mlx.singleton as singleton
25import mlx.airports as airports
26from mlx.i18n import xstr, getLanguage
27from mlx.pirep import PIREP
28
29import time
30import threading
31import sys
32import datetime
33import webbrowser
34
35#------------------------------------------------------------------------------
36
37class GUI(fs.ConnectionListener):
38 """The main GUI class."""
39 _authors = [ (u"Váradi", u"István", "prog_test"),
40 (u"Galyassy", u"Tamás", "negotiation"),
41 (u"Petrovszki", u"Gábor", "test"),
42 (u"Zsebényi-Loksa", u"Gergely", "test"),
43 (u"Kurják", u"Ákos", "test"),
44 (u"Nagy", u"Dániel", "test"),
45 (u"Radó", u"Iván", "test") ]
46
47 def __init__(self, programDirectory, config):
48 """Construct the GUI."""
49 gobject.threads_init()
50
51 self._programDirectory = programDirectory
52 self.config = config
53 self._connecting = False
54 self._reconnecting = False
55 self._connected = False
56 self._logger = logger.Logger(self)
57 self._flight = None
58 self._simulator = None
59 self._monitoring = False
60
61 self._fleet = None
62
63 self._fleetCallback = None
64
65 self._updatePlaneCallback = None
66 self._updatePlaneTailNumber = None
67 self._updatePlaneStatus = None
68 self._updatePlaneGateNumber = None
69
70 self._stdioLock = threading.Lock()
71 self._stdioText = ""
72 self._stdioStartingLine = True
73
74 self._sendPIREPCallback = None
75
76 self.webHandler = web.Handler()
77 self.webHandler.start()
78
79 self.toRestart = False
80
81 def build(self, iconDirectory):
82 """Build the GUI."""
83
84 self._mainWindow = window = gtk.Window()
85 window.set_title(WINDOW_TITLE_BASE)
86 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
87 window.set_resizable(False)
88 window.connect("delete-event", self.deleteMainWindow)
89 window.connect("window-state-event", self._handleMainWindowState)
90 accelGroup = gtk.AccelGroup()
91 window.add_accel_group(accelGroup)
92
93 mainVBox = gtk.VBox()
94 window.add(mainVBox)
95
96 self._preferences = Preferences(self)
97 self._checklistEditor = ChecklistEditor(self)
98 self._approachCalloutsEditor = ApproachCalloutsEditor(self)
99
100 menuBar = self._buildMenuBar(accelGroup)
101 mainVBox.pack_start(menuBar, False, False, 0)
102
103 self._notebook = gtk.Notebook()
104 mainVBox.pack_start(self._notebook, True, True, 4)
105
106 self._wizard = Wizard(self)
107 label = gtk.Label(xstr("tab_flight"))
108 label.set_use_underline(True)
109 label.set_tooltip_text(xstr("tab_flight_tooltip"))
110 self._notebook.append_page(self._wizard, label)
111
112 self._flightInfo = FlightInfo(self)
113 label = gtk.Label(xstr("tab_flight_info"))
114 label.set_use_underline(True)
115 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
116 self._notebook.append_page(self._flightInfo, label)
117 self._flightInfo.disable()
118
119 self._weightHelp = WeightHelp(self)
120 label = gtk.Label(xstr("tab_weight_help"))
121 label.set_use_underline(True)
122 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
123 self._notebook.append_page(self._weightHelp, label)
124
125 (logWidget, self._logView) = self._buildLogWidget()
126 addFaultTag(self._logView.get_buffer())
127 label = gtk.Label(xstr("tab_log"))
128 label.set_use_underline(True)
129 label.set_tooltip_text(xstr("tab_log_tooltip"))
130 self._notebook.append_page(logWidget, label)
131
132 self._fleetGateStatus = FleetGateStatus(self)
133 label = gtk.Label(xstr("tab_gates"))
134 label.set_use_underline(True)
135 label.set_tooltip_text(xstr("tab_gates_tooltip"))
136 self._notebook.append_page(self._fleetGateStatus, label)
137
138 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
139 self._debugLogWidget.show_all()
140
141 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
142
143 self._statusbar = Statusbar(iconDirectory)
144 mainVBox.pack_start(self._statusbar, False, False, 0)
145
146 self._notebook.connect("switch-page", self._notebookPageSwitch)
147
148 self._monitorWindow = MonitorWindow(self, iconDirectory)
149 self._monitorWindow.add_accel_group(accelGroup)
150 self._monitorWindowX = None
151 self._monitorWindowY = None
152 self._selfToggling = False
153
154 self._pirepViewer = PIREPViewer(self)
155
156 window.show_all()
157 self._wizard.grabDefault()
158 self._weightHelp.reset()
159 self._weightHelp.disable()
160
161 self._statusIcon = StatusIcon(iconDirectory, self)
162
163 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
164 else gdk.WATCH)
165
166 self._loadPIREPDialog = None
167 self._lastLoadedPIREP = None
168
169 self._hotkeySetID = None
170 self._pilotHotkeyIndex = None
171 self._checklistHotkeyIndex = None
172
173 self._aboutDialog = None
174
175 @property
176 def mainWindow(self):
177 """Get the main window of the GUI."""
178 return self._mainWindow
179
180 @property
181 def logger(self):
182 """Get the logger used by us."""
183 return self._logger
184
185 @property
186 def simulator(self):
187 """Get the simulator used by us."""
188 return self._simulator
189
190 @property
191 def flight(self):
192 """Get the flight being performed."""
193 return self._flight
194
195 @property
196 def entranceExam(self):
197 """Get whether an entrance exam is about to be taken."""
198 return self._wizard.entranceExam
199
200 @property
201 def loggedIn(self):
202 """Indicate if the user has logged in properly."""
203 return self._wizard.loggedIn
204
205 @property
206 def loginResult(self):
207 """Get the result of the login."""
208 return self._wizard.loginResult
209
210 @property
211 def bookedFlight(self):
212 """Get the booked flight selected, if any."""
213 return self._wizard.bookedFlight
214
215 @property
216 def cargoWeight(self):
217 """Get the cargo weight."""
218 return self._wizard.cargoWeight
219
220 @property
221 def zfw(self):
222 """Get Zero-Fuel Weight calculated for the current flight."""
223 return self._wizard.zfw
224
225 @property
226 def filedCruiseAltitude(self):
227 """Get cruise altitude filed for the current flight."""
228 return self._wizard.filedCruiseAltitude
229
230 @property
231 def cruiseAltitude(self):
232 """Get cruise altitude set for the current flight."""
233 return self._wizard.cruiseAltitude
234
235 @property
236 def route(self):
237 """Get the flight route."""
238 return self._wizard.route
239
240 @property
241 def departureMETAR(self):
242 """Get the METAR of the deprature airport."""
243 return self._wizard.departureMETAR
244
245 @property
246 def arrivalMETAR(self):
247 """Get the METAR of the deprature airport."""
248 return self._wizard.arrivalMETAR
249
250 @property
251 def departureRunway(self):
252 """Get the name of the departure runway."""
253 return self._wizard.departureRunway
254
255 @property
256 def sid(self):
257 """Get the SID."""
258 return self._wizard.sid
259
260 @property
261 def v1(self):
262 """Get the V1 speed calculated for the flight."""
263 return self._wizard.v1
264
265 @property
266 def vr(self):
267 """Get the Vr speed calculated for the flight."""
268 return self._wizard.vr
269
270 @property
271 def v2(self):
272 """Get the V2 speed calculated for the flight."""
273 return self._wizard.v2
274
275 @property
276 def arrivalRunway(self):
277 """Get the arrival runway."""
278 return self._wizard.arrivalRunway
279
280 @property
281 def star(self):
282 """Get the STAR."""
283 return self._wizard.star
284
285 @property
286 def transition(self):
287 """Get the transition."""
288 return self._wizard.transition
289
290 @property
291 def approachType(self):
292 """Get the approach type."""
293 return self._wizard.approachType
294
295 @property
296 def vref(self):
297 """Get the Vref speed calculated for the flight."""
298 return self._wizard.vref
299
300 @property
301 def flightType(self):
302 """Get the flight type."""
303 return self._wizard.flightType
304
305 @property
306 def online(self):
307 """Get whether the flight was online or not."""
308 return self._wizard.online
309
310 @property
311 def comments(self):
312 """Get the comments."""
313 return self._flightInfo.comments
314
315 @property
316 def flightDefects(self):
317 """Get the flight defects."""
318 return self._flightInfo.flightDefects
319
320 @property
321 def delayCodes(self):
322 """Get the delay codes."""
323 return self._flightInfo.delayCodes
324
325 def run(self):
326 """Run the GUI."""
327 if self.config.autoUpdate:
328 self._updater = Updater(self,
329 self._programDirectory,
330 self.config.updateURL,
331 self._mainWindow)
332 self._updater.start()
333
334 singleton.raiseCallback = self.raiseCallback
335 gtk.main()
336 singleton.raiseCallback = None
337
338 self._disconnect()
339
340 def connected(self, fsType, descriptor):
341 """Called when we have connected to the simulator."""
342 self._connected = True
343 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
344 (const.VERSION, descriptor))
345 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
346 "Welcome to MAVA Logger X " + const.VERSION)
347 gobject.idle_add(self._handleConnected, fsType, descriptor)
348
349 def _handleConnected(self, fsType, descriptor):
350 """Called when the connection to the simulator has succeeded."""
351 self._statusbar.updateConnection(self._connecting, self._connected)
352 self.endBusy()
353 if not self._reconnecting:
354 self._wizard.connected(fsType, descriptor)
355 self._reconnecting = False
356 self._listenHotkeys()
357
358 def connectionFailed(self):
359 """Called when the connection failed."""
360 self._logger.untimedMessage("Connection to the simulator failed")
361 gobject.idle_add(self._connectionFailed)
362
363 def _connectionFailed(self):
364 """Called when the connection failed."""
365 self.endBusy()
366 self._statusbar.updateConnection(self._connecting, self._connected)
367
368 dialog = gtk.MessageDialog(parent = self._mainWindow,
369 type = MESSAGETYPE_ERROR,
370 message_format = xstr("conn_failed"))
371
372 dialog.set_title(WINDOW_TITLE_BASE)
373 dialog.format_secondary_markup(xstr("conn_failed_sec"))
374
375 dialog.add_button(xstr("button_cancel"), 0)
376 dialog.add_button(xstr("button_tryagain"), 1)
377 dialog.set_default_response(1)
378
379 result = dialog.run()
380 dialog.hide()
381 if result == 1:
382 self.beginBusy(xstr("connect_busy"))
383 self._simulator.reconnect()
384 else:
385 self.reset()
386
387 def disconnected(self):
388 """Called when we have disconnected from the simulator."""
389 self._connected = False
390 self._logger.untimedMessage("Disconnected from the simulator")
391
392 gobject.idle_add(self._disconnected)
393
394 def _disconnected(self):
395 """Called when we have disconnected from the simulator unexpectedly."""
396 self._statusbar.updateConnection(self._connecting, self._connected)
397
398 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
399 message_format = xstr("conn_broken"),
400 parent = self._mainWindow)
401 dialog.set_title(WINDOW_TITLE_BASE)
402 dialog.format_secondary_markup(xstr("conn_broken_sec"))
403
404 dialog.add_button(xstr("button_cancel"), 0)
405 dialog.add_button(xstr("button_reconnect"), 1)
406 dialog.set_default_response(1)
407
408 result = dialog.run()
409 dialog.hide()
410 if result == 1:
411 self.beginBusy(xstr("connect_busy"))
412 self._reconnecting = True
413 self._simulator.reconnect()
414 else:
415 self.reset()
416
417 def enableFlightInfo(self):
418 """Enable the flight info tab."""
419 self._flightInfo.enable()
420
421 def cancelFlight(self):
422 """Cancel the current file, if the user confirms it."""
423 dialog = gtk.MessageDialog(parent = self._mainWindow,
424 type = MESSAGETYPE_QUESTION,
425 message_format = xstr("cancelFlight_question"))
426
427 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
428 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
429
430 dialog.set_title(WINDOW_TITLE_BASE)
431 result = dialog.run()
432 dialog.hide()
433
434 if result==RESPONSETYPE_YES:
435 self.reset()
436
437 def reset(self):
438 """Reset the GUI."""
439 self._disconnect()
440
441 self._flightInfo.reset()
442 self._flightInfo.disable()
443 self.resetFlightStatus()
444
445 self._weightHelp.reset()
446 self._weightHelp.disable()
447 self._notebook.set_current_page(0)
448
449 self._logView.get_buffer().set_text("")
450
451 if self.loggedIn:
452 self._wizard.reloadFlights(self._handleReloadResult)
453 else:
454 self._wizard.reset(None)
455
456 def _handleReloadResult(self, returned, result):
457 """Handle the result of the reloading of the flights."""
458 self._wizard.reset(result if returned and result.loggedIn else None)
459
460 def _disconnect(self, closingMessage = None, duration = 3):
461 """Disconnect from the simulator if connected."""
462 self.stopMonitoring()
463 self._clearHotkeys()
464
465 if self._connected:
466 if closingMessage is None:
467 self._flight.simulator.disconnect()
468 else:
469 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
470 closingMessage, duration,
471 disconnect = True)
472 self._connected = False
473
474 self._connecting = False
475 self._reconnecting = False
476 self._statusbar.updateConnection(False, False)
477 self._weightHelp.disable()
478
479 return True
480
481 def addFlightLogLine(self, timeStr, line, isFault = False):
482 """Write the given message line to the log."""
483 gobject.idle_add(self._writeLog,
484 formatFlightLogLine(timeStr, line),
485 self._logView, isFault)
486
487 def updateFlightLogLine(self, index, timeStr, line):
488 """Update the line with the given index."""
489 gobject.idle_add(self._updateFlightLogLine, index,
490 formatFlightLogLine(timeStr, line))
491
492 def _updateFlightLogLine(self, index, line):
493 """Replace the contents of the given line in the log."""
494 buffer = self._logView.get_buffer()
495 startIter = buffer.get_iter_at_line(index)
496 endIter = buffer.get_iter_at_line(index + 1)
497 buffer.delete(startIter, endIter)
498 buffer.insert(startIter, line)
499 self._logView.scroll_mark_onscreen(buffer.get_insert())
500
501 def check(self, flight, aircraft, logger, oldState, state):
502 """Update the data."""
503 gobject.idle_add(self._monitorWindow.setData, state)
504 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
505
506 def resetFlightStatus(self):
507 """Reset the status of the flight."""
508 self._statusbar.resetFlightStatus()
509 self._statusbar.updateTime()
510 self._statusIcon.resetFlightStatus()
511
512 def setStage(self, stage):
513 """Set the stage of the flight."""
514 gobject.idle_add(self._setStage, stage)
515
516 def _setStage(self, stage):
517 """Set the stage of the flight."""
518 self._statusbar.setStage(stage)
519 self._statusIcon.setStage(stage)
520 self._wizard.setStage(stage)
521 if stage==const.STAGE_END:
522 welcomeMessage = \
523 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
524 self._disconnect(closingMessage =
525 "Flight plan closed. " + welcomeMessage,
526 duration = 5)
527
528 def setRating(self, rating):
529 """Set the rating of the flight."""
530 gobject.idle_add(self._setRating, rating)
531
532 def _setRating(self, rating):
533 """Set the rating of the flight."""
534 self._statusbar.setRating(rating)
535 self._statusIcon.setRating(rating)
536
537 def setNoGo(self, reason):
538 """Set the rating of the flight to No-Go with the given reason."""
539 gobject.idle_add(self._setNoGo, reason)
540
541 def _setNoGo(self, reason):
542 """Set the rating of the flight."""
543 self._statusbar.setNoGo(reason)
544 self._statusIcon.setNoGo(reason)
545
546 def _handleMainWindowState(self, window, event):
547 """Hande a change in the state of the window"""
548 iconified = gdk.WindowState.ICONIFIED if pygobject \
549 else gdk.WINDOW_STATE_ICONIFIED
550
551 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
552 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
553 self._statusIcon.mainWindowHidden()
554 else:
555 self._statusIcon.mainWindowShown()
556
557 if self.config.hideMinimizedWindow and not pygobject and \
558 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
559 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
560 self.hideMainWindow(savePosition = False)
561 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
562 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
563 self._mainWindow.present()
564
565 def raiseCallback(self):
566 """Callback for the singleton handling code."""
567 gobject.idle_add(self.raiseMainWindow)
568
569 def raiseMainWindow(self):
570 """Show the main window if invisible, and raise it."""
571 if not self._mainWindow.get_visible():
572 self.showMainWindow()
573 self._mainWindow.present()
574
575 def deleteMainWindow(self, window, event):
576 """Handle the delete event for the main window."""
577 if self.config.quitOnClose:
578 self._quit()
579 else:
580 self.hideMainWindow()
581 return True
582
583 def hideMainWindow(self, savePosition = True):
584 """Hide the main window and save its position."""
585 if savePosition:
586 (self._mainWindowX, self._mainWindowY) = \
587 self._mainWindow.get_window().get_root_origin()
588 else:
589 self._mainWindowX = self._mainWindowY = None
590 self._mainWindow.hide()
591 return True
592
593 def showMainWindow(self):
594 """Show the main window at its former position."""
595 if self._mainWindowX is not None and self._mainWindowY is not None:
596 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
597
598 if pygobject:
599 self._mainWindow.show()
600 else:
601 self._mainWindow.present()
602 self._mainWindow.deiconify()
603
604 def toggleMainWindow(self):
605 """Toggle the main window."""
606 if self._mainWindow.get_visible():
607 self.hideMainWindow()
608 else:
609 self.showMainWindow()
610
611 def hideMonitorWindow(self, savePosition = True):
612 """Hide the monitor window."""
613 if savePosition:
614 (self._monitorWindowX, self._monitorWindowY) = \
615 self._monitorWindow.get_window().get_root_origin()
616 else:
617 self._monitorWindowX = self._monitorWindowY = None
618 self._monitorWindow.hide()
619 self._statusIcon.monitorWindowHidden()
620 if self._showMonitorMenuItem.get_active():
621 self._selfToggling = True
622 self._showMonitorMenuItem.set_active(False)
623 return True
624
625 def showMonitorWindow(self):
626 """Show the monitor window."""
627 if self._monitorWindowX is not None and self._monitorWindowY is not None:
628 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
629 self._monitorWindow.show_all()
630 self._statusIcon.monitorWindowShown()
631 if not self._showMonitorMenuItem.get_active():
632 self._selfToggling = True
633 self._showMonitorMenuItem.set_active(True)
634
635 def _toggleMonitorWindow(self, menuItem):
636 if self._selfToggling:
637 self._selfToggling = False
638 elif self._monitorWindow.get_visible():
639 self.hideMonitorWindow()
640 else:
641 self.showMonitorWindow()
642
643 def restart(self):
644 """Quit and restart the application."""
645 self.toRestart = True
646 self._quit(force = True)
647
648 def flushStdIO(self):
649 """Flush any text to the standard error that could not be logged."""
650 if self._stdioText:
651 sys.__stderr__.write(self._stdioText)
652
653 def writeStdIO(self, text):
654 """Write the given text into standard I/O log."""
655 with self._stdioLock:
656 self._stdioText += text
657
658 gobject.idle_add(self._writeStdIO)
659
660 def beginBusy(self, message):
661 """Begin a period of background processing."""
662 self._wizard.set_sensitive(False)
663 self._weightHelp.set_sensitive(False)
664 self._mainWindow.get_window().set_cursor(self._busyCursor)
665 self._statusbar.updateBusyState(message)
666
667 def endBusy(self):
668 """End a period of background processing."""
669 self._mainWindow.get_window().set_cursor(None)
670 self._weightHelp.set_sensitive(True)
671 self._wizard.set_sensitive(True)
672 self._statusbar.updateBusyState(None)
673
674 def initializeWeightHelp(self):
675 """Initialize the weight help tab."""
676 self._weightHelp.reset()
677 self._weightHelp.enable()
678
679 def getFleetAsync(self, callback = None, force = None):
680 """Get the fleet asynchronously."""
681 gobject.idle_add(self.getFleet, callback, force)
682
683 def getFleet(self, callback = None, force = False):
684 """Get the fleet.
685
686 If force is False, and we already have a fleet retrieved,
687 that one will be used."""
688 if self._fleet is None or force:
689 self._fleetCallback = callback
690 self.beginBusy(xstr("fleet_busy"))
691 self.webHandler.getFleet(self._fleetResultCallback)
692 else:
693 callback(self._fleet)
694
695 def _fleetResultCallback(self, returned, result):
696 """Called when the fleet has been queried."""
697 gobject.idle_add(self._handleFleetResult, returned, result)
698
699 def _handleFleetResult(self, returned, result):
700 """Handle the fleet result."""
701 self.endBusy()
702 if returned:
703 self._fleet = result.fleet
704 else:
705 self._fleet = None
706
707 dialog = gtk.MessageDialog(parent = self.mainWindow,
708 type = MESSAGETYPE_ERROR,
709 message_format = xstr("fleet_failed"))
710 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
711 dialog.set_title(WINDOW_TITLE_BASE)
712 dialog.run()
713 dialog.hide()
714
715 callback = self._fleetCallback
716 self._fleetCallback = None
717 if callback is not None:
718 callback(self._fleet)
719 self._fleetGateStatus.handleFleet(self._fleet)
720
721 def updatePlane(self, tailNumber, status,
722 gateNumber = None, callback = None):
723 """Update the status of the given plane."""
724 self.beginBusy(xstr("fleet_update_busy"))
725
726 self._updatePlaneCallback = callback
727
728 self._updatePlaneTailNumber = tailNumber
729 self._updatePlaneStatus = status
730 self._updatePlaneGateNumber = gateNumber
731
732 self.webHandler.updatePlane(self._updatePlaneResultCallback,
733 tailNumber, status, gateNumber)
734
735 def _updatePlaneResultCallback(self, returned, result):
736 """Called when the status of a plane has been updated."""
737 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
738
739 def _handleUpdatePlaneResult(self, returned, result):
740 """Handle the plane update result."""
741 self.endBusy()
742 if returned:
743 success = result.success
744 if success:
745 if self._fleet is not None:
746 self._fleet.updatePlane(self._updatePlaneTailNumber,
747 self._updatePlaneStatus,
748 self._updatePlaneGateNumber)
749 self._fleetGateStatus.handleFleet(self._fleet)
750 else:
751 dialog = gtk.MessageDialog(parent = self.mainWindow,
752 type = MESSAGETYPE_ERROR,
753 message_format = xstr("fleet_update_failed"))
754 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
755 dialog.set_title(WINDOW_TITLE_BASE)
756 dialog.run()
757 dialog.hide()
758
759 success = None
760
761 callback = self._updatePlaneCallback
762 self._updatePlaneCallback = None
763 if callback is not None:
764 callback(success)
765
766 def _writeStdIO(self):
767 """Perform the real writing."""
768 with self._stdioLock:
769 text = self._stdioText
770 self._stdioText = ""
771 if not text: return
772
773 lines = text.splitlines()
774 if text[-1]=="\n":
775 text = ""
776 else:
777 text = lines[-1]
778 lines = lines[:-1]
779
780 now = datetime.datetime.now()
781 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
782
783 for line in lines:
784 #print >> sys.__stdout__, line
785 if self._stdioStartingLine:
786 self._writeLog(timeStr, self._debugLogView)
787 self._writeLog(line + "\n", self._debugLogView)
788 self._stdioStartingLine = True
789
790 if text:
791 #print >> sys.__stdout__, text,
792 if self._stdioStartingLine:
793 self._writeLog(timeStr, self._debugLogView)
794 self._writeLog(text, self._debugLogView)
795 self._stdioStartingLine = False
796
797 def connectSimulator(self, aircraftType):
798 """Connect to the simulator for the first time."""
799 self._logger.reset()
800
801 self._flight = flight.Flight(self._logger, self)
802 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
803 self._flight.aircraftType = aircraftType
804 self._flight.aircraft = acft.Aircraft.create(self._flight)
805 self._flight.aircraft._checkers.append(self)
806
807 if self._simulator is None:
808 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
809 fs.setupMessageSending(self.config, self._simulator)
810 self._setupTimeSync()
811
812 self._flight.simulator = self._simulator
813
814 self.beginBusy(xstr("connect_busy"))
815 self._statusbar.updateConnection(self._connecting, self._connected)
816
817 self._connecting = True
818 self._simulator.connect(self._flight.aircraft)
819
820 def startMonitoring(self):
821 """Start monitoring."""
822 if not self._monitoring:
823 self.simulator.startMonitoring()
824 self._monitoring = True
825
826 def stopMonitoring(self):
827 """Stop monitoring."""
828 if self._monitoring:
829 self.simulator.stopMonitoring()
830 self._monitoring = False
831
832 def _buildMenuBar(self, accelGroup):
833 """Build the main menu bar."""
834 menuBar = gtk.MenuBar()
835
836 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
837 fileMenu = gtk.Menu()
838 fileMenuItem.set_submenu(fileMenu)
839 menuBar.append(fileMenuItem)
840
841 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
842 loadPIREPMenuItem.set_use_stock(True)
843 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
844 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
845 ord(xstr("menu_file_loadPIREP_key")),
846 CONTROL_MASK, ACCEL_VISIBLE)
847 loadPIREPMenuItem.connect("activate", self._loadPIREP)
848 fileMenu.append(loadPIREPMenuItem)
849
850 fileMenu.append(gtk.SeparatorMenuItem())
851
852 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
853 quitMenuItem.set_use_stock(True)
854 quitMenuItem.set_label(xstr("menu_file_quit"))
855 quitMenuItem.add_accelerator("activate", accelGroup,
856 ord(xstr("menu_file_quit_key")),
857 CONTROL_MASK, ACCEL_VISIBLE)
858 quitMenuItem.connect("activate", self._quit)
859 fileMenu.append(quitMenuItem)
860
861 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
862 toolsMenu = gtk.Menu()
863 toolsMenuItem.set_submenu(toolsMenu)
864 menuBar.append(toolsMenuItem)
865
866 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
867 checklistMenuItem.set_use_stock(True)
868 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
869 checklistMenuItem.add_accelerator("activate", accelGroup,
870 ord(xstr("menu_tools_chklst_key")),
871 CONTROL_MASK, ACCEL_VISIBLE)
872 checklistMenuItem.connect("activate", self._editChecklist)
873 toolsMenu.append(checklistMenuItem)
874
875 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
876 approachCalloutsMenuItem.set_use_stock(True)
877 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
878 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
879 ord(xstr("menu_tools_callouts_key")),
880 CONTROL_MASK, ACCEL_VISIBLE)
881 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
882 toolsMenu.append(approachCalloutsMenuItem)
883
884 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
885 prefsMenuItem.set_use_stock(True)
886 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
887 prefsMenuItem.add_accelerator("activate", accelGroup,
888 ord(xstr("menu_tools_prefs_key")),
889 CONTROL_MASK, ACCEL_VISIBLE)
890 prefsMenuItem.connect("activate", self._editPreferences)
891 toolsMenu.append(prefsMenuItem)
892
893 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
894 viewMenu = gtk.Menu()
895 viewMenuItem.set_submenu(viewMenu)
896 menuBar.append(viewMenuItem)
897
898 self._showMonitorMenuItem = gtk.CheckMenuItem()
899 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
900 self._showMonitorMenuItem.set_use_underline(True)
901 self._showMonitorMenuItem.set_active(False)
902 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
903 ord(xstr("menu_view_monitor_key")),
904 CONTROL_MASK, ACCEL_VISIBLE)
905 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
906 viewMenu.append(self._showMonitorMenuItem)
907
908 showDebugMenuItem = gtk.CheckMenuItem()
909 showDebugMenuItem.set_label(xstr("menu_view_debug"))
910 showDebugMenuItem.set_use_underline(True)
911 showDebugMenuItem.set_active(False)
912 showDebugMenuItem.add_accelerator("activate", accelGroup,
913 ord(xstr("menu_view_debug_key")),
914 CONTROL_MASK, ACCEL_VISIBLE)
915 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
916 viewMenu.append(showDebugMenuItem)
917
918 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
919 helpMenu = gtk.Menu()
920 helpMenuItem.set_submenu(helpMenu)
921 menuBar.append(helpMenuItem)
922
923 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
924 manualMenuItem.set_use_stock(True)
925 manualMenuItem.set_label(xstr("menu_help_manual"))
926 manualMenuItem.add_accelerator("activate", accelGroup,
927 ord(xstr("menu_help_manual_key")),
928 CONTROL_MASK, ACCEL_VISIBLE)
929 manualMenuItem.connect("activate", self._showManual)
930 helpMenu.append(manualMenuItem)
931
932 helpMenu.append(gtk.SeparatorMenuItem())
933
934 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
935 aboutMenuItem.set_use_stock(True)
936 aboutMenuItem.set_label(xstr("menu_help_about"))
937 aboutMenuItem.add_accelerator("activate", accelGroup,
938 ord(xstr("menu_help_about_key")),
939 CONTROL_MASK, ACCEL_VISIBLE)
940 aboutMenuItem.connect("activate", self._showAbout)
941 helpMenu.append(aboutMenuItem)
942
943 return menuBar
944
945 def _toggleDebugLog(self, menuItem):
946 """Toggle the debug log."""
947 if menuItem.get_active():
948 label = gtk.Label(xstr("tab_debug_log"))
949 label.set_use_underline(True)
950 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
951 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
952 self._notebook.set_current_page(self._debugLogPage)
953 else:
954 self._notebook.remove_page(self._debugLogPage)
955
956 def _buildLogWidget(self):
957 """Build the widget for the log."""
958 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
959
960 alignment.set_padding(padding_top = 8, padding_bottom = 8,
961 padding_left = 16, padding_right = 16)
962
963 logScroller = gtk.ScrolledWindow()
964 # FIXME: these should be constants in common
965 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
966 else gtk.POLICY_AUTOMATIC,
967 gtk.PolicyType.AUTOMATIC if pygobject
968 else gtk.POLICY_AUTOMATIC)
969 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
970 else gtk.SHADOW_IN)
971 logView = gtk.TextView()
972 logView.set_editable(False)
973 logView.set_cursor_visible(False)
974 logScroller.add(logView)
975
976 logBox = gtk.VBox()
977 logBox.pack_start(logScroller, True, True, 0)
978 logBox.set_size_request(-1, 200)
979
980 alignment.add(logBox)
981
982 return (alignment, logView)
983
984 def _writeLog(self, msg, logView, isFault = False):
985 """Write the given message to the log."""
986 buffer = logView.get_buffer()
987 appendTextBuffer(buffer, msg, isFault = isFault)
988 logView.scroll_mark_onscreen(buffer.get_insert())
989
990 def _quit(self, what = None, force = False):
991 """Quit from the application."""
992 if force:
993 result=RESPONSETYPE_YES
994 else:
995 dialog = gtk.MessageDialog(parent = self._mainWindow,
996 type = MESSAGETYPE_QUESTION,
997 message_format = xstr("quit_question"))
998
999 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1000 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1001
1002 dialog.set_title(WINDOW_TITLE_BASE)
1003 result = dialog.run()
1004 dialog.hide()
1005
1006 if result==RESPONSETYPE_YES:
1007 self._statusIcon.destroy()
1008 return gtk.main_quit()
1009
1010 def _notebookPageSwitch(self, notebook, page, page_num):
1011 """Called when the current page of the notebook has changed."""
1012 if page_num==0:
1013 gobject.idle_add(self._wizard.grabDefault)
1014 else:
1015 self._mainWindow.set_default(None)
1016
1017 def _editChecklist(self, menuItem):
1018 """Callback for editing the checklists."""
1019 self._checklistEditor.run()
1020
1021 def _editApproachCallouts(self, menuItem):
1022 """Callback for editing the approach callouts."""
1023 self._approachCalloutsEditor.run()
1024
1025 def _editPreferences(self, menuItem):
1026 """Callback for editing the preferences."""
1027 self._clearHotkeys()
1028 self._preferences.run(self.config)
1029 self._setupTimeSync()
1030 self._listenHotkeys()
1031
1032 def _setupTimeSync(self):
1033 """Enable or disable the simulator time synchronization based on the
1034 configuration."""
1035 simulator = self._simulator
1036 if simulator is not None:
1037 if self.config.syncFSTime:
1038 simulator.enableTimeSync()
1039 else:
1040 simulator.disableTimeSync()
1041
1042 def _loadPIREP(self, menuItem):
1043 """Load a PIREP for sending."""
1044 dialog = self._getLoadPirepDialog()
1045
1046 if self._lastLoadedPIREP:
1047 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1048 else:
1049 pirepDirectory = self.config.pirepDirectory
1050 if pirepDirectory is not None:
1051 dialog.set_current_folder(pirepDirectory)
1052
1053 result = dialog.run()
1054 dialog.hide()
1055
1056 if result==RESPONSETYPE_OK:
1057 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1058
1059 pirep = PIREP.load(self._lastLoadedPIREP)
1060 if pirep is None:
1061 dialog = gtk.MessageDialog(parent = self._mainWindow,
1062 type = MESSAGETYPE_ERROR,
1063 message_format = xstr("loadPIREP_failed"))
1064 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1065 dialog.set_title(WINDOW_TITLE_BASE)
1066 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1067 dialog.run()
1068 dialog.hide()
1069 else:
1070 dialog = self._getSendLoadedDialog(pirep)
1071 dialog.show_all()
1072 while True:
1073 result = dialog.run()
1074
1075 if result==RESPONSETYPE_OK:
1076 self.sendPIREP(pirep)
1077 elif result==1:
1078 self._pirepViewer.setPIREP(pirep)
1079 self._pirepViewer.show_all()
1080 self._pirepViewer.run()
1081 self._pirepViewer.hide()
1082 else:
1083 break
1084
1085 dialog.hide()
1086
1087 def _getLoadPirepDialog(self):
1088 """Get the PIREP loading file chooser dialog.
1089
1090 If it is not created yet, it will be created."""
1091 if self._loadPIREPDialog is None:
1092 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1093 xstr("loadPIREP_browser_title"),
1094 action = FILE_CHOOSER_ACTION_OPEN,
1095 buttons = (gtk.STOCK_CANCEL,
1096 RESPONSETYPE_CANCEL,
1097 gtk.STOCK_OK, RESPONSETYPE_OK),
1098 parent = self._mainWindow)
1099 dialog.set_modal(True)
1100
1101
1102 filter = gtk.FileFilter()
1103 filter.set_name(xstr("file_filter_pireps"))
1104 filter.add_pattern("*.pirep")
1105 dialog.add_filter(filter)
1106
1107 filter = gtk.FileFilter()
1108 filter.set_name(xstr("file_filter_all"))
1109 filter.add_pattern("*.*")
1110 dialog.add_filter(filter)
1111
1112 self._loadPIREPDialog = dialog
1113
1114 return self._loadPIREPDialog
1115
1116 def _getSendLoadedDialog(self, pirep):
1117 """Get a dialog displaying the main information of the flight from the
1118 PIREP and providing Cancel and Send buttons."""
1119 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1120 xstr("loadPIREP_send_title"),
1121 parent = self._mainWindow,
1122 flags = DIALOG_MODAL)
1123
1124 contentArea = dialog.get_content_area()
1125
1126 label = gtk.Label(xstr("loadPIREP_send_help"))
1127 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1128 xscale = 0.0, yscale = 0.0)
1129 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1130 padding_left = 48, padding_right = 48)
1131 alignment.add(label)
1132 contentArea.pack_start(alignment, False, False, 8)
1133
1134 table = gtk.Table(5, 2)
1135 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1136 xscale = 0.0, yscale = 0.0)
1137 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1138 padding_left = 48, padding_right = 48)
1139 table.set_row_spacings(4)
1140 table.set_col_spacings(16)
1141 tableAlignment.add(table)
1142 contentArea.pack_start(tableAlignment, True, True, 8)
1143
1144 bookedFlight = pirep.bookedFlight
1145
1146 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1147 label.set_use_markup(True)
1148 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1149 xscale = 0.0, yscale = 0.0)
1150 labelAlignment.add(label)
1151 table.attach(labelAlignment, 0, 1, 0, 1)
1152
1153 label = gtk.Label(bookedFlight.callsign)
1154 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1155 xscale = 0.0, yscale = 0.0)
1156 labelAlignment.add(label)
1157 table.attach(labelAlignment, 1, 2, 0, 1)
1158
1159 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1160 label.set_use_markup(True)
1161 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1162 xscale = 0.0, yscale = 0.0)
1163 labelAlignment.add(label)
1164 table.attach(labelAlignment, 0, 1, 1, 2)
1165
1166 label = gtk.Label(str(bookedFlight.departureTime.date()))
1167 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1168 xscale = 0.0, yscale = 0.0)
1169 labelAlignment.add(label)
1170 table.attach(labelAlignment, 1, 2, 1, 2)
1171
1172 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1173 label.set_use_markup(True)
1174 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1175 xscale = 0.0, yscale = 0.0)
1176 labelAlignment.add(label)
1177 table.attach(labelAlignment, 0, 1, 2, 3)
1178
1179 label = gtk.Label(bookedFlight.departureICAO)
1180 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1181 xscale = 0.0, yscale = 0.0)
1182 labelAlignment.add(label)
1183 table.attach(labelAlignment, 1, 2, 2, 3)
1184
1185 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1186 label.set_use_markup(True)
1187 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1188 xscale = 0.0, yscale = 0.0)
1189 labelAlignment.add(label)
1190 table.attach(labelAlignment, 0, 1, 3, 4)
1191
1192 label = gtk.Label(bookedFlight.arrivalICAO)
1193 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1194 xscale = 0.0, yscale = 0.0)
1195 labelAlignment.add(label)
1196 table.attach(labelAlignment, 1, 2, 3, 4)
1197
1198 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1199 label.set_use_markup(True)
1200 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1201 xscale = 0.0, yscale = 0.0)
1202 labelAlignment.add(label)
1203 table.attach(labelAlignment, 0, 1, 4, 5)
1204
1205 rating = pirep.rating
1206 label = gtk.Label()
1207 if rating<0:
1208 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1209 else:
1210 label.set_text("%.1f %%" % (rating,))
1211
1212 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1213 xscale = 0.0, yscale = 0.0)
1214 labelAlignment.add(label)
1215 table.attach(labelAlignment, 1, 2, 4, 5)
1216
1217 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1218 dialog.add_button(xstr("viewPIREP"), 1)
1219 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1220
1221 return dialog
1222
1223 def sendPIREP(self, pirep, callback = None):
1224 """Send the given PIREP."""
1225 self.beginBusy(xstr("sendPIREP_busy"))
1226 self._sendPIREPCallback = callback
1227 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1228
1229 def _pirepSentCallback(self, returned, result):
1230 """Callback for the PIREP sending result."""
1231 gobject.idle_add(self._handlePIREPSent, returned, result)
1232
1233 def _handlePIREPSent(self, returned, result):
1234 """Callback for the PIREP sending result."""
1235 self.endBusy()
1236 secondaryMarkup = None
1237 type = MESSAGETYPE_ERROR
1238 if returned:
1239 if result.success:
1240 type = MESSAGETYPE_INFO
1241 messageFormat = xstr("sendPIREP_success")
1242 secondaryMarkup = xstr("sendPIREP_success_sec")
1243 elif result.alreadyFlown:
1244 messageFormat = xstr("sendPIREP_already")
1245 secondaryMarkup = xstr("sendPIREP_already_sec")
1246 elif result.notAvailable:
1247 messageFormat = xstr("sendPIREP_notavail")
1248 else:
1249 messageFormat = xstr("sendPIREP_unknown")
1250 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1251 else:
1252 print "PIREP sending failed", result
1253 messageFormat = xstr("sendPIREP_failed")
1254 secondaryMarkup = xstr("sendPIREP_failed_sec")
1255
1256 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1257 type = type, message_format = messageFormat)
1258 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1259 dialog.set_title(WINDOW_TITLE_BASE)
1260 if secondaryMarkup is not None:
1261 dialog.format_secondary_markup(secondaryMarkup)
1262
1263 dialog.run()
1264 dialog.hide()
1265
1266 callback = self._sendPIREPCallback
1267 self._sendPIREPCallback = None
1268 if callback is not None:
1269 callback(returned, result)
1270
1271 def _listenHotkeys(self):
1272 """Setup the hotkeys based on the configuration."""
1273 if self._hotkeySetID is None and self._simulator is not None:
1274 self._pilotHotkeyIndex = None
1275 self._checklistHotkeyIndex = None
1276
1277 hotkeys = []
1278
1279 config = self.config
1280 if config.enableSounds and config.pilotControlsSounds:
1281 self._pilotHotkeyIndex = len(hotkeys)
1282 hotkeys.append(config.pilotHotkey)
1283
1284 if config.enableChecklists:
1285 self._checklistHotkeyIndex = len(hotkeys)
1286 hotkeys.append(config.checklistHotkey)
1287
1288 if hotkeys:
1289 self._hotkeySetID = \
1290 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1291
1292 def _clearHotkeys(self):
1293 """Clear the hotkeys."""
1294 if self._hotkeySetID is not None:
1295 self._hotkeySetID=None
1296 self._simulator.clearHotkeys()
1297
1298 def _handleHotkeys(self, id, hotkeys):
1299 """Handle the hotkeys."""
1300 if id==self._hotkeySetID:
1301 for index in hotkeys:
1302 if index==self._pilotHotkeyIndex:
1303 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1304 self._flight.pilotHotkeyPressed()
1305 elif index==self._checklistHotkeyIndex:
1306 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1307 self._flight.checklistHotkeyPressed()
1308 else:
1309 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1310
1311 def _showManual(self, menuitem):
1312 """Show the user's manual."""
1313 webbrowser.open(url ="file://" +
1314 os.path.join(self._programDirectory, "doc", "manual",
1315 getLanguage(), "index.html"),
1316 new = 1)
1317
1318 def _showAbout(self, menuitem):
1319 """Show the about dialog."""
1320 dialog = self._getAboutDialog()
1321 dialog.show_all()
1322 dialog.run()
1323 dialog.hide()
1324
1325 def _getAboutDialog(self):
1326 """Get the about dialog.
1327
1328 If it does not exist yet, it will be created."""
1329 if self._aboutDialog is None:
1330 dialog = gtk.AboutDialog()
1331 dialog.set_transient_for(self._mainWindow)
1332 dialog.set_modal(True)
1333
1334 logoPath = os.path.join(self._programDirectory, "logo.png")
1335 logo = pixbuf_new_from_file(logoPath)
1336 dialog.set_logo(logo)
1337
1338 dialog.set_program_name(PROGRAM_NAME)
1339 dialog.set_version(const.VERSION)
1340 dialog.set_copyright("(c) 2012 by István Váradi")
1341 dialog.set_website("http://mlx.varadiistvan.hu")
1342 dialog.set_website_label(xstr("about_website"))
1343
1344 isHungarian = getLanguage()=="hu"
1345 authors = []
1346 for (familyName, firstName, role) in GUI._authors:
1347 author = "%s %s" % \
1348 (familyName if isHungarian else firstName,
1349 firstName if isHungarian else familyName)
1350 role = xstr("about_role_" + role)
1351 authors.append(author + " (" + role + ")")
1352 dialog.set_authors(authors)
1353
1354 dialog.set_license(xstr("about_license"))
1355
1356 if not pygobject:
1357 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1358
1359 self._aboutDialog = dialog
1360
1361 return self._aboutDialog
1362
1363 def _showAboutURL(self, dialog, link, user_data):
1364 """Show the about URL."""
1365 webbrowser.open(url = link, new = 1)
Note: See TracBrowser for help on using the repository browser.