source: src/mlx/gui/gui.py@ 252:90c41540a206

Last change on this file since 252:90c41540a206 was 249:d055e454a7ea, checked in by István Váradi <ivaradi@…>, 12 years ago

Implemented option to control whether to quit or hide when the window close button is pressed

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