source: src/mlx/gui/gui.py@ 248:d1a42111f067

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

Fixed the problem with the main window not being responsive to keystrokes after showing after being hidden by minimization

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