source: src/mlx/gui/gui.py@ 951:579389f54dae

version_0.39_maint
Last change on this file since 951:579389f54dae was 951:579389f54dae, checked in by István Váradi <ivaradi@…>, 6 years ago

CEF initialization is put after updating (re #347)

File size: 71.5 KB
Line 
1# -*- coding: utf-8 -*-
2
3from statusicon import StatusIcon
4from statusbar import Statusbar
5from info import FlightInfo
6from update import Updater
7from mlx.gui.common import *
8from mlx.gui.flight import Wizard
9from mlx.gui.monitor import MonitorWindow
10from mlx.gui.weighthelp import WeightHelp
11from mlx.gui.gates import FleetGateStatus
12from mlx.gui.prefs import Preferences
13from mlx.gui.checklist import ChecklistEditor
14from mlx.gui.callouts import ApproachCalloutsEditor
15from mlx.gui.flightlist import AcceptedFlightsWindow
16from mlx.gui.pirep import PIREPViewer, PIREPEditor
17from mlx.gui.bugreport import BugReportDialog
18from mlx.gui.acars import ACARS
19from mlx.gui.timetable import TimetableWindow
20import cef
21
22import mlx.const as const
23import mlx.fs as fs
24import mlx.flight as flight
25import mlx.logger as logger
26import mlx.acft as acft
27import mlx.web as web
28import mlx.singleton as singleton
29import mlx.airports as airports
30from mlx.i18n import xstr, getLanguage
31from mlx.pirep import PIREP
32
33import time
34import threading
35import sys
36import datetime
37import webbrowser
38
39#------------------------------------------------------------------------------
40
41## @package mlx.gui.gui
42#
43# The main GUI class.
44#
45# The \ref GUI class is the main class of the GUI. It is a connection listener,
46# and aggregates all the windows, the menu, etc. It maintains the connection to
47# the simulator as well as the flight object.
48
49#------------------------------------------------------------------------------
50
51class GUI(fs.ConnectionListener):
52 """The main GUI class."""
53 _authors = [ (u"Váradi", u"István", "prog_test"),
54 (u"Galyassy", u"Tamás", "negotiation"),
55 (u"Kurják", u"Ákos", "test"),
56 (u"Nagy", u"Dániel", "test"),
57 (u"Radó", u"Iván", "test"),
58 (u"Petrovszki", u"Gábor", "test"),
59 (u"Serfőző", u"Tamás", "test"),
60 (u"Szebenyi", u"Bálint", "test"),
61 (u"Zsebényi-Loksa", u"Gergely", "test") ]
62
63 def __init__(self, programDirectory, config):
64 """Construct the GUI."""
65 gobject.threads_init()
66
67 self._programDirectory = programDirectory
68 self.config = config
69 self._connecting = False
70 self._reconnecting = False
71 self._connected = False
72 self._logger = logger.Logger(self)
73 self._flight = None
74 self._simulator = None
75 self._fsType = None
76 self._monitoring = False
77
78 self._fleet = None
79
80 self._fleetCallback = None
81
82 self._updatePlaneCallback = None
83 self._updatePlaneTailNumber = None
84 self._updatePlaneStatus = None
85 self._updatePlaneGateNumber = None
86
87 self._stdioLock = threading.Lock()
88 self._stdioText = ""
89 self._stdioStartingLine = True
90
91 self._sendPIREPCallback = None
92 self._sendBugReportCallback = None
93
94 self._credentialsCondition = threading.Condition()
95 self._credentialsAvailable = False
96 self._credentialsUserName = None
97 self._credentialsPassword = None
98
99 self._bookFlightsUserCallback = None
100 self._bookFlightsBusyCallback = None
101
102 self.webHandler = web.Handler(config, self._getCredentialsCallback)
103 self.webHandler.start()
104
105 self.toRestart = False
106
107 def build(self, iconDirectory):
108 """Build the GUI."""
109
110 self._mainWindow = window = gtk.Window()
111 window.set_title(WINDOW_TITLE_BASE)
112 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
113 window.set_resizable(False)
114 window.connect("delete-event", self.deleteMainWindow)
115 window.connect("window-state-event", self._handleMainWindowState)
116 if os.name=="nt":
117 window.connect("leave-notify-event", self._handleLeaveNotify)
118 accelGroup = gtk.AccelGroup()
119 window.add_accel_group(accelGroup)
120 window.realize()
121
122 mainVBox = gtk.VBox()
123 window.add(mainVBox)
124
125 self._preferences = Preferences(self)
126 self._timetableWindow = TimetableWindow(self)
127 self._timetableWindow.connect("delete-event", self._hideTimetableWindow)
128 self._flightsWindow = AcceptedFlightsWindow(self)
129 self._flightsWindow.connect("delete-event", self._hideFlightsWindow)
130 self._checklistEditor = ChecklistEditor(self)
131 self._approachCalloutsEditor = ApproachCalloutsEditor(self)
132 self._bugReportDialog = BugReportDialog(self)
133
134 menuBar = self._buildMenuBar(accelGroup)
135 mainVBox.pack_start(menuBar, False, False, 0)
136
137 self._notebook = gtk.Notebook()
138 mainVBox.pack_start(self._notebook, True, True, 4)
139
140 self._wizard = Wizard(self)
141 label = gtk.Label(xstr("tab_flight"))
142 label.set_use_underline(True)
143 label.set_tooltip_text(xstr("tab_flight_tooltip"))
144 self._notebook.append_page(self._wizard, label)
145
146 self._flightInfo = FlightInfo(self)
147 label = gtk.Label(xstr("tab_flight_info"))
148 label.set_use_underline(True)
149 label.set_tooltip_text(xstr("tab_flight_info_tooltip"))
150 self._notebook.append_page(self._flightInfo, label)
151 self._flightInfo.disable()
152
153 self._weightHelp = WeightHelp(self)
154 label = gtk.Label(xstr("tab_weight_help"))
155 label.set_use_underline(True)
156 label.set_tooltip_text(xstr("tab_weight_help_tooltip"))
157 self._notebook.append_page(self._weightHelp, label)
158
159 (logWidget, self._logView) = self._buildLogWidget()
160 addFaultTag(self._logView.get_buffer())
161 label = gtk.Label(xstr("tab_log"))
162 label.set_use_underline(True)
163 label.set_tooltip_text(xstr("tab_log_tooltip"))
164 self._notebook.append_page(logWidget, label)
165
166 self._fleetGateStatus = FleetGateStatus(self)
167 label = gtk.Label(xstr("tab_gates"))
168 label.set_use_underline(True)
169 label.set_tooltip_text(xstr("tab_gates_tooltip"))
170 self._notebook.append_page(self._fleetGateStatus, label)
171
172 self._acars = ACARS(self)
173 label = gtk.Label("ACARS")
174 label.set_use_underline(True)
175 self._notebook.append_page(self._acars, label)
176
177 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
178 self._debugLogWidget.show_all()
179
180 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
181
182 self._statusbar = Statusbar(iconDirectory)
183 mainVBox.pack_start(self._statusbar, False, False, 0)
184
185 self._notebook.connect("switch-page", self._notebookPageSwitch)
186
187 self._monitorWindow = MonitorWindow(self, iconDirectory)
188 self._monitorWindow.add_accel_group(accelGroup)
189 self._monitorWindowX = None
190 self._monitorWindowY = None
191 self._selfToggling = False
192
193 self._pirepViewer = PIREPViewer(self)
194 self._messagedPIREPViewer = PIREPViewer(self, showMessages = True)
195
196 self._pirepEditor = PIREPEditor(self)
197
198 window.show_all()
199 if os.name=="nt":
200 window.get_window().focus()
201
202 self._wizard.grabDefault()
203 self._weightHelp.reset()
204 self._weightHelp.disable()
205
206 self._statusIcon = StatusIcon(iconDirectory, self)
207
208 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
209 else gdk.WATCH)
210
211 self._loadPIREPDialog = None
212 self._lastLoadedPIREP = None
213
214 self._hotkeySetID = None
215 self._pilotHotkeyIndex = None
216 self._checklistHotkeyIndex = None
217
218 self._aboutDialog = None
219
220 @property
221 def mainWindow(self):
222 """Get the main window of the GUI."""
223 return self._mainWindow
224
225 @property
226 def logger(self):
227 """Get the logger used by us."""
228 return self._logger
229
230 @property
231 def simulator(self):
232 """Get the simulator used by us."""
233 return self._simulator
234
235 @property
236 def flight(self):
237 """Get the flight being performed."""
238 return self._flight
239
240 @property
241 def fsType(self):
242 """Get the flight simulator type."""
243 return self._fsType
244
245 @property
246 def entranceExam(self):
247 """Get whether an entrance exam is about to be taken."""
248 return self._wizard.entranceExam
249
250 @property
251 def loggedIn(self):
252 """Indicate if the user has logged in properly."""
253 return self._wizard.loggedIn
254
255 @property
256 def loginResult(self):
257 """Get the result of the login."""
258 return self._wizard.loginResult
259
260 @property
261 def bookedFlight(self):
262 """Get the booked flight selected, if any."""
263 return self._wizard.bookedFlight
264
265 @property
266 def numCrew(self):
267 """Get the number of crew members."""
268 return self._wizard.numCrew
269
270 @property
271 def numPassengers(self):
272 """Get the number of passengers."""
273 return self._wizard.numPassengers
274
275 @property
276 def bagWeight(self):
277 """Get the bag weight."""
278 return self._wizard.bagWeight
279
280 @property
281 def cargoWeight(self):
282 """Get the cargo weight."""
283 return self._wizard.cargoWeight
284
285 @property
286 def mailWeight(self):
287 """Get the mail weight."""
288 return self._wizard.mailWeight
289
290 @property
291 def zfw(self):
292 """Get Zero-Fuel Weight calculated for the current flight."""
293 return self._wizard.zfw
294
295 @property
296 def filedCruiseAltitude(self):
297 """Get cruise altitude filed for the current flight."""
298 return self._wizard.filedCruiseAltitude
299
300 @property
301 def cruiseAltitude(self):
302 """Get cruise altitude set for the current flight."""
303 return self._wizard.cruiseAltitude
304
305 @property
306 def loggableCruiseAltitude(self):
307 """Get the cruise altitude that can be logged."""
308 return self._wizard.loggableCruiseAltitude
309
310 @property
311 def route(self):
312 """Get the flight route."""
313 return self._wizard.route
314
315 @property
316 def departureMETAR(self):
317 """Get the METAR of the deprature airport."""
318 return self._wizard.departureMETAR
319
320 @property
321 def arrivalMETAR(self):
322 """Get the METAR of the deprature airport."""
323 return self._wizard.arrivalMETAR
324
325 @property
326 def departureRunway(self):
327 """Get the name of the departure runway."""
328 return self._wizard.departureRunway
329
330 @property
331 def sid(self):
332 """Get the SID."""
333 return self._wizard.sid
334
335 @property
336 def v1(self):
337 """Get the V1 speed calculated for the flight."""
338 return self._wizard.v1
339
340 @property
341 def vr(self):
342 """Get the Vr speed calculated for the flight."""
343 return self._wizard.vr
344
345 @property
346 def v2(self):
347 """Get the V2 speed calculated for the flight."""
348 return self._wizard.v2
349
350 @property
351 def derate(self):
352 """Get the derate value calculated for the flight."""
353 return self._wizard.derate
354
355 @property
356 def takeoffAntiIceOn(self):
357 """Get whether the anti-ice system was on during take-off."""
358 return self._wizard.takeoffAntiIceOn
359
360 @takeoffAntiIceOn.setter
361 def takeoffAntiIceOn(self, value):
362 """Set the anti-ice on indicator."""
363 gobject.idle_add(self._setTakeoffAntiIceOn, value)
364
365 @property
366 def rtoIndicated(self):
367 """Get whether the pilot has indicated than an RTO has occured."""
368 return self._wizard.rtoIndicated
369
370 @property
371 def arrivalRunway(self):
372 """Get the arrival runway."""
373 return self._wizard.arrivalRunway
374
375 @property
376 def star(self):
377 """Get the STAR."""
378 return self._wizard.star
379
380 @property
381 def transition(self):
382 """Get the transition."""
383 return self._wizard.transition
384
385 @property
386 def approachType(self):
387 """Get the approach type."""
388 return self._wizard.approachType
389
390 @property
391 def vref(self):
392 """Get the Vref speed calculated for the flight."""
393 return self._wizard.vref
394
395 @property
396 def landingAntiIceOn(self):
397 """Get whether the anti-ice system was on during landing."""
398 return self._wizard.landingAntiIceOn
399
400 @landingAntiIceOn.setter
401 def landingAntiIceOn(self, value):
402 """Set the anti-ice on indicator."""
403 gobject.idle_add(self._setLandingAntiIceOn, value)
404
405 @property
406 def flightType(self):
407 """Get the flight type."""
408 return self._wizard.flightType
409
410 @property
411 def online(self):
412 """Get whether the flight was online or not."""
413 return self._wizard.online
414
415 @property
416 def comments(self):
417 """Get the comments."""
418 return self._flightInfo.comments
419
420 @property
421 def hasComments(self):
422 """Indicate whether there is a comment."""
423 return self._flightInfo.hasComments
424
425 @property
426 def flightDefects(self):
427 """Get the flight defects."""
428 return self._flightInfo.faultsAndExplanations
429
430 @property
431 def delayCodes(self):
432 """Get the delay codes."""
433 return self._flightInfo.delayCodes
434
435 @property
436 def hasDelayCode(self):
437 """Determine if there is at least one delay code selected."""
438 return self._flightInfo.hasDelayCode
439
440 @property
441 def faultsFullyExplained(self):
442 """Determine if all the faults have been fully explained by the
443 user."""
444 return self._flightInfo.faultsFullyExplained
445
446 if pygobject:
447 @property
448 def backgroundColour(self):
449 """Get the background colour of the main window."""
450 return self._mainWindow.get_style_context().\
451 get_background_color(gtk.StateFlags.NORMAL)
452
453 def run(self):
454 """Run the GUI."""
455 if self.config.autoUpdate:
456 self._updater = Updater(self,
457 self._programDirectory,
458 self.config.updateURL,
459 self._mainWindow)
460 self._updater.start()
461 else:
462 self.updateDone()
463
464 singleton.raiseCallback = self.raiseCallback
465 gtk.main()
466 singleton.raiseCallback = None
467
468 cef.finalize()
469
470 self._disconnect()
471
472 def updateDone(self):
473 """Called when the update is done (and there is no need to restart)."""
474 gobject.idle_add(self._updateDone)
475
476 def connected(self, fsType, descriptor):
477 """Called when we have connected to the simulator."""
478 self._connected = True
479 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
480 (const.VERSION, descriptor))
481 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
482 "Welcome to MAVA Logger X " + const.VERSION)
483 gobject.idle_add(self._handleConnected, fsType, descriptor)
484
485 def _handleConnected(self, fsType, descriptor):
486 """Called when the connection to the simulator has succeeded."""
487 self._statusbar.updateConnection(self._connecting, self._connected)
488 self.endBusy()
489 if not self._reconnecting:
490 self._wizard.connected(fsType, descriptor)
491 self._reconnecting = False
492 self._fsType = fsType
493 self._listenHotkeys()
494
495 def connectionFailed(self):
496 """Called when the connection failed."""
497 self._logger.untimedMessage("Connection to the simulator failed")
498 gobject.idle_add(self._connectionFailed)
499
500 def _connectionFailed(self):
501 """Called when the connection failed."""
502 self.endBusy()
503 self._statusbar.updateConnection(self._connecting, self._connected)
504
505 dialog = gtk.MessageDialog(parent = self._mainWindow,
506 type = MESSAGETYPE_ERROR,
507 message_format = xstr("conn_failed"))
508
509 dialog.set_title(WINDOW_TITLE_BASE)
510 dialog.format_secondary_markup(xstr("conn_failed_sec"))
511
512 dialog.add_button(xstr("button_cancel"), 0)
513 dialog.add_button(xstr("button_tryagain"), 1)
514 dialog.set_default_response(1)
515
516 result = dialog.run()
517 dialog.hide()
518 if result == 1:
519 self.beginBusy(xstr("connect_busy"))
520 self._simulator.reconnect()
521 else:
522 self.reset()
523
524 def disconnected(self):
525 """Called when we have disconnected from the simulator."""
526 self._connected = False
527 self._logger.untimedMessage("Disconnected from the simulator")
528 if self._flight is not None:
529 self._flight.disconnected()
530
531 gobject.idle_add(self._disconnected)
532
533 def _disconnected(self):
534 """Called when we have disconnected from the simulator unexpectedly."""
535 self._statusbar.updateConnection(self._connecting, self._connected)
536
537 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
538 message_format = xstr("conn_broken"),
539 parent = self._mainWindow)
540 dialog.set_title(WINDOW_TITLE_BASE)
541 dialog.format_secondary_markup(xstr("conn_broken_sec"))
542
543 dialog.add_button(xstr("button_cancel"), 0)
544 dialog.add_button(xstr("button_reconnect"), 1)
545 dialog.set_default_response(1)
546
547 result = dialog.run()
548 dialog.hide()
549 if result == 1:
550 self.beginBusy(xstr("connect_busy"))
551 self._reconnecting = True
552 self._simulator.reconnect()
553 else:
554 self.reset()
555
556 def enableFlightInfo(self, aircraftType):
557 """Enable the flight info tab."""
558 self._flightInfo.enable(aircraftType)
559
560 def bookFlights(self, callback, flightIDs, date, tailNumber,
561 busyCallback = None):
562 """Initiate the booking of flights with the given timetable IDs and
563 other data"""
564 self._bookFlightsUserCallback = callback
565 self._bookFlightsBusyCallback = busyCallback
566
567 self.beginBusy(xstr("bookflights_busy"))
568 if busyCallback is not None:
569 busyCallback(True)
570
571 self.webHandler.bookFlights(self._bookFlightsCallback,
572 flightIDs, date, tailNumber)
573
574 def _bookFlightsCallback(self, returned, result):
575 """Called when the booking of flights has finished."""
576 gobject.idle_add(self._handleBookFlightsResult, returned, result)
577
578 def _handleBookFlightsResult(self, returned, result):
579 """Called when the booking of flights is done.
580
581 If it was successful, the booked flights are added to the list of the
582 flight selector."""
583 if self._bookFlightsBusyCallback is not None:
584 self._bookFlightsBusyCallback(False)
585 self.endBusy()
586
587 if returned:
588 for bookedFlight in result.bookedFlights:
589 self._wizard.addFlight(bookedFlight)
590
591 self._bookFlightsUserCallback(returned, result)
592
593 def cancelFlight(self):
594 """Cancel the current file, if the user confirms it."""
595 dialog = gtk.MessageDialog(parent = self._mainWindow,
596 type = MESSAGETYPE_QUESTION,
597 message_format = xstr("cancelFlight_question"))
598
599 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
600 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
601
602 dialog.set_title(WINDOW_TITLE_BASE)
603 result = dialog.run()
604 dialog.hide()
605
606 if result==RESPONSETYPE_YES:
607 self.reset()
608
609 def reset(self):
610 """Reset the GUI."""
611 self._disconnect()
612
613 self._simulator = None
614
615 self._flightInfo.reset()
616 self._flightInfo.disable()
617 self.resetFlightStatus()
618
619 self._weightHelp.reset()
620 self._weightHelp.disable()
621 self._notebook.set_current_page(0)
622
623 self._logView.get_buffer().set_text("")
624
625 if self.loggedIn:
626 self._wizard.cancelFlight(self._handleReloadResult)
627 else:
628 self._wizard.reset(None)
629
630 def _handleReloadResult(self, returned, result):
631 """Handle the result of the reloading of the flights."""
632 self._wizard.reset(result if returned and result.loggedIn else None)
633
634 def _disconnect(self, closingMessage = None, duration = 3):
635 """Disconnect from the simulator if connected."""
636 self.stopMonitoring()
637 self._clearHotkeys()
638
639 if self._connected:
640 if closingMessage is None:
641 self._flight.simulator.disconnect()
642 else:
643 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
644 closingMessage, duration,
645 disconnect = True)
646 self._connected = False
647
648 self._connecting = False
649 self._reconnecting = False
650 self._statusbar.updateConnection(False, False)
651 self._weightHelp.disable()
652
653 return True
654
655 def insertFlightLogLine(self, index, timestampString, text, isFault):
656 """Insert the flight log line with the given data."""
657 gobject.idle_add(self._insertFlightLogLine, index,
658 formatFlightLogLine(timestampString, text),
659 isFault)
660
661 def _insertFlightLogLine(self, index, line, isFault):
662 """Perform the real insertion.
663
664 To be called from the event loop."""
665 buffer = self._logView.get_buffer()
666 lineIter = buffer.get_iter_at_line(index)
667 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
668 self._logView.scroll_mark_onscreen(buffer.get_insert())
669
670 def removeFlightLogLine(self, index):
671 """Remove the flight log line with the given index."""
672 gobject.idle_add(self._removeFlightLogLine, index)
673
674 def addFault(self, id, timestampString, text):
675 """Add a fault to the list of faults."""
676 faultText = formatFlightLogLine(timestampString, text).strip()
677 gobject.idle_add(self._flightInfo.addFault, id, faultText)
678
679 def updateFault(self, id, timestampString, text):
680 """Update a fault in the list of faults."""
681 faultText = formatFlightLogLine(timestampString, text).strip()
682 gobject.idle_add(self._flightInfo.updateFault, id, faultText)
683
684 def clearFault(self, id):
685 """Clear a fault in the list of faults."""
686 gobject.idle_add(self._flightInfo.clearFault, id)
687
688 def _removeFlightLogLine(self, index):
689 """Perform the real removal."""
690 buffer = self._logView.get_buffer()
691 startIter = buffer.get_iter_at_line(index)
692 endIter = buffer.get_iter_at_line(index+1)
693 buffer.delete(startIter, endIter)
694 self._logView.scroll_mark_onscreen(buffer.get_insert())
695
696 def check(self, flight, aircraft, logger, oldState, state):
697 """Update the data."""
698 gobject.idle_add(self._monitorWindow.setData, state)
699 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
700
701 def resetFlightStatus(self):
702 """Reset the status of the flight."""
703 self._statusbar.resetFlightStatus()
704 self._statusbar.updateTime()
705 self._statusIcon.resetFlightStatus()
706
707 def setStage(self, stage):
708 """Set the stage of the flight."""
709 gobject.idle_add(self._setStage, stage)
710
711 def _setStage(self, stage):
712 """Set the stage of the flight."""
713 self._statusbar.setStage(stage)
714 self._statusIcon.setStage(stage)
715 self._wizard.setStage(stage)
716 if stage==const.STAGE_END:
717 welcomeMessage = \
718 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
719 self._disconnect(closingMessage =
720 "Flight plan closed. " + welcomeMessage,
721 duration = 5)
722
723 def setRating(self, rating):
724 """Set the rating of the flight."""
725 gobject.idle_add(self._setRating, rating)
726
727 def _setRating(self, rating):
728 """Set the rating of the flight."""
729 self._statusbar.setRating(rating)
730 self._statusIcon.setRating(rating)
731
732 def setNoGo(self, reason):
733 """Set the rating of the flight to No-Go with the given reason."""
734 gobject.idle_add(self._setNoGo, reason)
735
736 def _setNoGo(self, reason):
737 """Set the rating of the flight."""
738 self._statusbar.setNoGo(reason)
739 self._statusIcon.setNoGo(reason)
740
741 def _handleMainWindowState(self, window, event):
742 """Hande a change in the state of the window"""
743 iconified = gdk.WindowState.ICONIFIED if pygobject \
744 else gdk.WINDOW_STATE_ICONIFIED
745
746 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
747 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
748 self._statusIcon.mainWindowHidden()
749 else:
750 self._statusIcon.mainWindowShown()
751
752 if self.config.hideMinimizedWindow and not pygobject and \
753 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
754 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
755 self.hideMainWindow(savePosition = False)
756 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
757 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
758 self._mainWindow.present()
759
760 def _handleLeaveNotify(self, widget, event):
761 """Handle the leave-notify event.
762
763 Here we reset the focus to the main window as CEF might have acquired
764 it earlier."""
765 self._mainWindow.get_window().focus()
766
767 def raiseCallback(self):
768 """Callback for the singleton handling code."""
769 gobject.idle_add(self.raiseMainWindow)
770
771 def raiseMainWindow(self):
772 """Show the main window if invisible, and raise it."""
773 if not self._mainWindow.get_visible():
774 self.showMainWindow()
775 self._mainWindow.present()
776
777 def deleteMainWindow(self, window, event):
778 """Handle the delete event for the main window."""
779 if self.config.quitOnClose:
780 self._quit()
781 else:
782 self.hideMainWindow()
783 return True
784
785 def hideMainWindow(self, savePosition = True):
786 """Hide the main window and save its position."""
787 if savePosition:
788 (self._mainWindowX, self._mainWindowY) = \
789 self._mainWindow.get_window().get_root_origin()
790 else:
791 self._mainWindowX = self._mainWindowY = None
792 self._mainWindow.hide()
793 return True
794
795 def showMainWindow(self):
796 """Show the main window at its former position."""
797 if self._mainWindowX is not None and self._mainWindowY is not None:
798 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
799
800 if pygobject:
801 self._mainWindow.show()
802 else:
803 self._mainWindow.present()
804 self._mainWindow.deiconify()
805
806 def toggleMainWindow(self):
807 """Toggle the main window."""
808 if self._mainWindow.get_visible():
809 self.hideMainWindow()
810 else:
811 self.showMainWindow()
812
813 def hideMonitorWindow(self, savePosition = True):
814 """Hide the monitor window."""
815 if savePosition:
816 (self._monitorWindowX, self._monitorWindowY) = \
817 self._monitorWindow.get_window().get_root_origin()
818 else:
819 self._monitorWindowX = self._monitorWindowY = None
820 self._monitorWindow.hide()
821 self._statusIcon.monitorWindowHidden()
822 if self._showMonitorMenuItem.get_active():
823 self._selfToggling = True
824 self._showMonitorMenuItem.set_active(False)
825 return True
826
827 def showMonitorWindow(self):
828 """Show the monitor window."""
829 if self._monitorWindowX is not None and self._monitorWindowY is not None:
830 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
831 self._monitorWindow.show_all()
832 self._statusIcon.monitorWindowShown()
833 if not self._showMonitorMenuItem.get_active():
834 self._selfToggling = True
835 self._showMonitorMenuItem.set_active(True)
836
837 def _toggleMonitorWindow(self, menuItem):
838 if self._selfToggling:
839 self._selfToggling = False
840 elif self._monitorWindow.get_visible():
841 self.hideMonitorWindow()
842 else:
843 self.showMonitorWindow()
844
845 def restart(self):
846 """Quit and restart the application."""
847 self.toRestart = True
848 self._quit(force = True)
849
850 def flushStdIO(self):
851 """Flush any text to the standard error that could not be logged."""
852 if self._stdioText:
853 sys.__stderr__.write(self._stdioText)
854
855 def writeStdIO(self, text):
856 """Write the given text into standard I/O log."""
857 with self._stdioLock:
858 self._stdioText += text
859
860 gobject.idle_add(self._writeStdIO)
861
862 def beginBusy(self, message):
863 """Begin a period of background processing."""
864 self._wizard.set_sensitive(False)
865 self._weightHelp.set_sensitive(False)
866 self._mainWindow.get_window().set_cursor(self._busyCursor)
867 self._statusbar.updateBusyState(message)
868
869 def updateBusyState(self, message):
870 """Update the busy state."""
871 self._statusbar.updateBusyState(message)
872
873 def endBusy(self):
874 """End a period of background processing."""
875 self._mainWindow.get_window().set_cursor(None)
876 self._weightHelp.set_sensitive(True)
877 self._wizard.set_sensitive(True)
878 self._statusbar.updateBusyState(None)
879
880 def initializeWeightHelp(self):
881 """Initialize the weight help tab."""
882 self._weightHelp.reset()
883 self._weightHelp.enable()
884
885 def getFleetAsync(self, callback = None, force = None):
886 """Get the fleet asynchronously."""
887 gobject.idle_add(self.getFleet, callback, force)
888
889 def getFleet(self, callback = None, force = False, busyCallback = None):
890 """Get the fleet.
891
892 If force is False, and we already have a fleet retrieved,
893 that one will be used."""
894 if self._fleet is None or force:
895 self._fleetCallback = callback
896 self._fleetBusyCallback = busyCallback
897 if busyCallback is not None:
898 busyCallback(True)
899 self.beginBusy(xstr("fleet_busy"))
900 self.webHandler.getFleet(self._fleetResultCallback)
901 else:
902 callback(self._fleet)
903
904 def commentsChanged(self):
905 """Indicate that the comments have changed."""
906 self._wizard.commentsChanged()
907
908 def delayCodesChanged(self):
909 """Called when the delay codes have changed."""
910 self._wizard.delayCodesChanged()
911
912 def faultExplanationsChanged(self):
913 """Called when the status of the explanations of the faults have
914 changed."""
915 self._wizard.faultExplanationsChanged()
916
917 def updateRTO(self, inLoop = False):
918 """Indicate that the RTO state should be updated."""
919 if inLoop:
920 self._wizard.updateRTO()
921 else:
922 gobject.idle_add(self.updateRTO, True)
923
924 def rtoToggled(self, indicated):
925 """Called when the user has toggled the RTO checkbox."""
926 self._flight.rtoToggled(indicated)
927
928 def _fleetResultCallback(self, returned, result):
929 """Called when the fleet has been queried."""
930 gobject.idle_add(self._handleFleetResult, returned, result)
931
932 def _handleFleetResult(self, returned, result):
933 """Handle the fleet result."""
934 self.endBusy()
935 if self._fleetBusyCallback is not None:
936 self._fleetBusyCallback(False)
937 if returned:
938 self._fleet = result.fleet
939 else:
940 self._fleet = None
941
942 dialog = gtk.MessageDialog(parent = self.mainWindow,
943 type = MESSAGETYPE_ERROR,
944 message_format = xstr("fleet_failed"))
945 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
946 dialog.set_title(WINDOW_TITLE_BASE)
947 dialog.run()
948 dialog.hide()
949
950 callback = self._fleetCallback
951 self._fleetCallback = None
952 self._fleetBusyCallback = None
953 if callback is not None:
954 callback(self._fleet)
955 self._fleetGateStatus.handleFleet(self._fleet)
956
957 def updatePlane(self, tailNumber, status,
958 gateNumber = None, callback = None):
959 """Update the status of the given plane."""
960 self.beginBusy(xstr("fleet_update_busy"))
961
962 self._updatePlaneCallback = callback
963
964 self._updatePlaneTailNumber = tailNumber
965 self._updatePlaneStatus = status
966 self._updatePlaneGateNumber = gateNumber
967
968 self.webHandler.updatePlane(self._updatePlaneResultCallback,
969 tailNumber, status, gateNumber)
970
971 def _updatePlaneResultCallback(self, returned, result):
972 """Called when the status of a plane has been updated."""
973 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
974
975 def _handleUpdatePlaneResult(self, returned, result):
976 """Handle the plane update result."""
977 self.endBusy()
978 if returned:
979 success = result.success
980 if success:
981 if self._fleet is not None:
982 self._fleet.updatePlane(self._updatePlaneTailNumber,
983 self._updatePlaneStatus,
984 self._updatePlaneGateNumber)
985 self._fleetGateStatus.handleFleet(self._fleet)
986 else:
987 dialog = gtk.MessageDialog(parent = self.mainWindow,
988 type = MESSAGETYPE_ERROR,
989 message_format = xstr("fleet_update_failed"))
990 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
991 dialog.set_title(WINDOW_TITLE_BASE)
992 dialog.run()
993 dialog.hide()
994
995 success = None
996
997 callback = self._updatePlaneCallback
998 self._updatePlaneCallback = None
999 if callback is not None:
1000 callback(success)
1001
1002 def _writeStdIO(self):
1003 """Perform the real writing."""
1004 with self._stdioLock:
1005 text = self._stdioText
1006 self._stdioText = ""
1007 if not text: return
1008
1009 lines = text.splitlines()
1010 if text[-1]=="\n":
1011 text = ""
1012 else:
1013 text = lines[-1]
1014 lines = lines[:-1]
1015
1016 now = datetime.datetime.now()
1017 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
1018
1019 for line in lines:
1020 #print >> sys.__stdout__, line
1021 if self._stdioStartingLine:
1022 self._writeLog(timeStr, self._debugLogView)
1023 self._writeLog(line + "\n", self._debugLogView)
1024 self._stdioStartingLine = True
1025
1026 if text:
1027 #print >> sys.__stdout__, text,
1028 if self._stdioStartingLine:
1029 self._writeLog(timeStr, self._debugLogView)
1030 self._writeLog(text, self._debugLogView)
1031 self._stdioStartingLine = False
1032
1033 def connectSimulator(self, bookedFlight, simulatorType):
1034 """Connect to the simulator for the first time."""
1035 self._logger.reset()
1036
1037 self._flight = flight.Flight(self._logger, self)
1038 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
1039 self._flight.aircraftType = bookedFlight.aircraftType
1040 self._flight.aircraft = acft.Aircraft.create(self._flight, bookedFlight)
1041 self._flight.aircraft._checkers.append(self)
1042
1043 if self._simulator is None:
1044 self._simulator = fs.createSimulator(simulatorType, self)
1045 fs.setupMessageSending(self.config, self._simulator)
1046 self._setupTimeSync()
1047
1048 self._flight.simulator = self._simulator
1049
1050 self.beginBusy(xstr("connect_busy"))
1051 self._statusbar.updateConnection(self._connecting, self._connected)
1052
1053 self._connecting = True
1054 self._simulator.connect(self._flight.aircraft)
1055
1056 def startMonitoring(self):
1057 """Start monitoring."""
1058 if not self._monitoring:
1059 self.simulator.startMonitoring()
1060 self._monitoring = True
1061
1062 def stopMonitoring(self):
1063 """Stop monitoring."""
1064 if self._monitoring:
1065 self.simulator.stopMonitoring()
1066 self._monitoring = False
1067
1068 def cruiseLevelChanged(self):
1069 """Called when the cruise level is changed in the flight wizard."""
1070 if self._flight is not None:
1071 return self._flight.cruiseLevelChanged()
1072 else:
1073 return False
1074
1075 def _buildMenuBar(self, accelGroup):
1076 """Build the main menu bar."""
1077 menuBar = gtk.MenuBar()
1078
1079 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
1080 fileMenu = gtk.Menu()
1081 fileMenuItem.set_submenu(fileMenu)
1082 menuBar.append(fileMenuItem)
1083
1084 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
1085 loadPIREPMenuItem.set_use_stock(True)
1086 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
1087 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
1088 ord(xstr("menu_file_loadPIREP_key")),
1089 CONTROL_MASK, ACCEL_VISIBLE)
1090 loadPIREPMenuItem.connect("activate", self._loadPIREP)
1091 fileMenu.append(loadPIREPMenuItem)
1092
1093 fileMenu.append(gtk.SeparatorMenuItem())
1094
1095 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
1096 quitMenuItem.set_use_stock(True)
1097 quitMenuItem.set_label(xstr("menu_file_quit"))
1098 quitMenuItem.add_accelerator("activate", accelGroup,
1099 ord(xstr("menu_file_quit_key")),
1100 CONTROL_MASK, ACCEL_VISIBLE)
1101 quitMenuItem.connect("activate", self._quit)
1102 fileMenu.append(quitMenuItem)
1103
1104 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
1105 toolsMenu = gtk.Menu()
1106 toolsMenuItem.set_submenu(toolsMenu)
1107 menuBar.append(toolsMenuItem)
1108
1109 self._timetableMenuItem = timetableMenuItem = \
1110 gtk.ImageMenuItem(gtk.STOCK_INDENT)
1111 timetableMenuItem.set_use_stock(True)
1112 timetableMenuItem.set_label(xstr("menu_tools_timetable"))
1113 timetableMenuItem.add_accelerator("activate", accelGroup,
1114 ord(xstr("menu_tools_timetable_key")),
1115 CONTROL_MASK, ACCEL_VISIBLE)
1116 timetableMenuItem.connect("activate", self.showTimetable)
1117 self._timetableMenuItem.set_sensitive(False)
1118 toolsMenu.append(timetableMenuItem)
1119
1120 self._flightsMenuItem = flightsMenuItem = \
1121 gtk.ImageMenuItem(gtk.STOCK_SPELL_CHECK)
1122 flightsMenuItem.set_use_stock(True)
1123 flightsMenuItem.set_label(xstr("menu_tools_flights"))
1124 flightsMenuItem.add_accelerator("activate", accelGroup,
1125 ord(xstr("menu_tools_flights_key")),
1126 CONTROL_MASK, ACCEL_VISIBLE)
1127 flightsMenuItem.connect("activate", self.showFlights)
1128 self._flightsMenuItem.set_sensitive(False)
1129 toolsMenu.append(flightsMenuItem)
1130
1131 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
1132 checklistMenuItem.set_use_stock(True)
1133 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
1134 checklistMenuItem.add_accelerator("activate", accelGroup,
1135 ord(xstr("menu_tools_chklst_key")),
1136 CONTROL_MASK, ACCEL_VISIBLE)
1137 checklistMenuItem.connect("activate", self._editChecklist)
1138 toolsMenu.append(checklistMenuItem)
1139
1140 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
1141 approachCalloutsMenuItem.set_use_stock(True)
1142 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
1143 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
1144 ord(xstr("menu_tools_callouts_key")),
1145 CONTROL_MASK, ACCEL_VISIBLE)
1146 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
1147 toolsMenu.append(approachCalloutsMenuItem)
1148
1149 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
1150 prefsMenuItem.set_use_stock(True)
1151 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
1152 prefsMenuItem.add_accelerator("activate", accelGroup,
1153 ord(xstr("menu_tools_prefs_key")),
1154 CONTROL_MASK, ACCEL_VISIBLE)
1155 prefsMenuItem.connect("activate", self._editPreferences)
1156 toolsMenu.append(prefsMenuItem)
1157
1158 toolsMenu.append(gtk.SeparatorMenuItem())
1159
1160 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
1161 bugReportMenuItem.set_use_stock(True)
1162 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
1163 bugReportMenuItem.add_accelerator("activate", accelGroup,
1164 ord(xstr("menu_tools_bugreport_key")),
1165 CONTROL_MASK, ACCEL_VISIBLE)
1166 bugReportMenuItem.connect("activate", self._reportBug)
1167 toolsMenu.append(bugReportMenuItem)
1168
1169 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
1170 viewMenu = gtk.Menu()
1171 viewMenuItem.set_submenu(viewMenu)
1172 menuBar.append(viewMenuItem)
1173
1174 self._showMonitorMenuItem = gtk.CheckMenuItem()
1175 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
1176 self._showMonitorMenuItem.set_use_underline(True)
1177 self._showMonitorMenuItem.set_active(False)
1178 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
1179 ord(xstr("menu_view_monitor_key")),
1180 CONTROL_MASK, ACCEL_VISIBLE)
1181 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1182 viewMenu.append(self._showMonitorMenuItem)
1183
1184 showDebugMenuItem = gtk.CheckMenuItem()
1185 showDebugMenuItem.set_label(xstr("menu_view_debug"))
1186 showDebugMenuItem.set_use_underline(True)
1187 showDebugMenuItem.set_active(False)
1188 showDebugMenuItem.add_accelerator("activate", accelGroup,
1189 ord(xstr("menu_view_debug_key")),
1190 CONTROL_MASK, ACCEL_VISIBLE)
1191 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1192 viewMenu.append(showDebugMenuItem)
1193
1194 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1195 helpMenu = gtk.Menu()
1196 helpMenuItem.set_submenu(helpMenu)
1197 menuBar.append(helpMenuItem)
1198
1199 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1200 manualMenuItem.set_use_stock(True)
1201 manualMenuItem.set_label(xstr("menu_help_manual"))
1202 manualMenuItem.add_accelerator("activate", accelGroup,
1203 ord(xstr("menu_help_manual_key")),
1204 CONTROL_MASK, ACCEL_VISIBLE)
1205 manualMenuItem.connect("activate", self._showManual)
1206 helpMenu.append(manualMenuItem)
1207
1208 helpMenu.append(gtk.SeparatorMenuItem())
1209
1210 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1211 aboutMenuItem.set_use_stock(True)
1212 aboutMenuItem.set_label(xstr("menu_help_about"))
1213 aboutMenuItem.add_accelerator("activate", accelGroup,
1214 ord(xstr("menu_help_about_key")),
1215 CONTROL_MASK, ACCEL_VISIBLE)
1216 aboutMenuItem.connect("activate", self._showAbout)
1217 helpMenu.append(aboutMenuItem)
1218
1219 return menuBar
1220
1221 def _toggleDebugLog(self, menuItem):
1222 """Toggle the debug log."""
1223 if menuItem.get_active():
1224 label = gtk.Label(xstr("tab_debug_log"))
1225 label.set_use_underline(True)
1226 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
1227 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1228 self._notebook.set_current_page(self._debugLogPage)
1229 else:
1230 self._notebook.remove_page(self._debugLogPage)
1231
1232 def _buildLogWidget(self):
1233 """Build the widget for the log."""
1234 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1235
1236 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1237 padding_left = 16, padding_right = 16)
1238
1239 logScroller = gtk.ScrolledWindow()
1240 # FIXME: these should be constants in common
1241 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1242 else gtk.POLICY_AUTOMATIC,
1243 gtk.PolicyType.AUTOMATIC if pygobject
1244 else gtk.POLICY_AUTOMATIC)
1245 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1246 else gtk.SHADOW_IN)
1247 logView = gtk.TextView()
1248 logView.set_editable(False)
1249 logView.set_cursor_visible(False)
1250 logScroller.add(logView)
1251
1252 logBox = gtk.VBox()
1253 logBox.pack_start(logScroller, True, True, 0)
1254 logBox.set_size_request(-1, 200)
1255
1256 alignment.add(logBox)
1257
1258 return (alignment, logView)
1259
1260 def _writeLog(self, msg, logView, isFault = False):
1261 """Write the given message to the log."""
1262 buffer = logView.get_buffer()
1263 appendTextBuffer(buffer, msg, isFault = isFault)
1264 logView.scroll_mark_onscreen(buffer.get_insert())
1265
1266 def _quit(self, what = None, force = False):
1267 """Quit from the application."""
1268 if force:
1269 result=RESPONSETYPE_YES
1270 else:
1271 dialog = gtk.MessageDialog(parent = self._mainWindow,
1272 type = MESSAGETYPE_QUESTION,
1273 message_format = xstr("quit_question"))
1274
1275 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1276 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1277
1278 dialog.set_title(WINDOW_TITLE_BASE)
1279 result = dialog.run()
1280 dialog.hide()
1281
1282 if result==RESPONSETYPE_YES:
1283 self._statusIcon.destroy()
1284 return gtk.main_quit()
1285
1286 def _notebookPageSwitch(self, notebook, page, page_num):
1287 """Called when the current page of the notebook has changed."""
1288 if page_num==0:
1289 gobject.idle_add(self._wizard.grabDefault)
1290 else:
1291 self._mainWindow.set_default(None)
1292
1293 def loginSuccessful(self):
1294 """Called when the login is successful."""
1295 self._flightsMenuItem.set_sensitive(True)
1296 self._timetableMenuItem.set_sensitive(True)
1297
1298 def isWizardActive(self):
1299 """Determine if the flight wizard is active."""
1300 return self._notebook.get_current_page()==0
1301
1302 def showTimetable(self, menuItem = None):
1303 """Callback for showing the timetable."""
1304 if self._timetableWindow.hasFlightPairs:
1305 self._timetableWindow.show_all()
1306 else:
1307 date = datetime.date.today()
1308 self._timetableWindow.setTypes(self.loginResult.types)
1309 self._timetableWindow.setDate(date)
1310 self.updateTimeTable(date)
1311 self.beginBusy(xstr("timetable_query_busy"))
1312
1313 def updateTimeTable(self, date):
1314 """Update the time table for the given date."""
1315 self.beginBusy(xstr("timetable_query_busy"))
1316 self._timetableWindow.set_sensitive(False)
1317 window = self._timetableWindow.get_window()
1318 if window is not None:
1319 window.set_cursor(self._busyCursor)
1320 self.webHandler.getTimetable(self._timetableCallback, date,
1321 self.loginResult.types)
1322
1323 def _timetableCallback(self, returned, result):
1324 """Called when the timetable has been received."""
1325 gobject.idle_add(self._handleTimetable, returned, result)
1326
1327 def _handleTimetable(self, returned, result):
1328 """Handle the result of the query for the timetable."""
1329 self.endBusy()
1330 window = self._timetableWindow.get_window()
1331 if window is not None:
1332 window.set_cursor(None)
1333 self._timetableWindow.set_sensitive(True)
1334 if returned:
1335 self._timetableWindow.setFlightPairs(result.flightPairs)
1336 self._timetableWindow.show_all()
1337 else:
1338 dialog = gtk.MessageDialog(parent = self.mainWindow,
1339 type = MESSAGETYPE_ERROR,
1340 message_format = xstr("timetable_failed"))
1341 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1342 dialog.set_title(WINDOW_TITLE_BASE)
1343 dialog.run()
1344 dialog.hide()
1345 self._timetableWindow.clear()
1346
1347 def showFlights(self, menuItem):
1348 """Callback for showing the flight list."""
1349 if self._flightsWindow.hasFlights:
1350 self._flightsWindow.show_all()
1351 else:
1352 self.beginBusy(xstr("acceptedflt_query_busy"))
1353 self.webHandler.getAcceptedFlights(self._acceptedFlightsCallback)
1354
1355 def _acceptedFlightsCallback(self, returned, result):
1356 """Called when the accepted flights have been received."""
1357 gobject.idle_add(self._handleAcceptedFlights, returned, result)
1358
1359 def _handleAcceptedFlights(self, returned, result):
1360 """Handle the result of the query for accepted flights."""
1361 self.endBusy()
1362 if returned:
1363 self._flightsWindow.clear()
1364 for flight in result.flights:
1365 self._flightsWindow.addFlight(flight)
1366 self._flightsWindow.show_all()
1367 else:
1368 dialog = gtk.MessageDialog(parent = self.mainWindow,
1369 type = MESSAGETYPE_ERROR,
1370 message_format = xstr("acceptedflt_failed"))
1371 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1372 dialog.set_title(WINDOW_TITLE_BASE)
1373 dialog.run()
1374 dialog.hide()
1375
1376 def _hideTimetableWindow(self, window, event):
1377 """Hide the window of the timetable."""
1378 self._timetableWindow.hide()
1379 return True
1380
1381 def _hideFlightsWindow(self, window, event):
1382 """Hide the window of the accepted flights."""
1383 self._flightsWindow.hide()
1384 return True
1385
1386 def _editChecklist(self, menuItem):
1387 """Callback for editing the checklists."""
1388 self._checklistEditor.run()
1389
1390 def _editApproachCallouts(self, menuItem):
1391 """Callback for editing the approach callouts."""
1392 self._approachCalloutsEditor.run()
1393
1394 def _editPreferences(self, menuItem):
1395 """Callback for editing the preferences."""
1396 self._clearHotkeys()
1397 self._preferences.run(self.config)
1398 self._setupTimeSync()
1399 self._listenHotkeys()
1400
1401 def _reportBug(self, menuItem):
1402 """Callback for reporting a bug."""
1403 self._bugReportDialog.run()
1404
1405 def _setupTimeSync(self):
1406 """Enable or disable the simulator time synchronization based on the
1407 configuration."""
1408 simulator = self._simulator
1409 if simulator is not None:
1410 if self.config.syncFSTime:
1411 simulator.enableTimeSync()
1412 else:
1413 simulator.disableTimeSync()
1414
1415 def viewPIREP(self, pirep):
1416 """Display the PIREP viewer window with the given PIREP."""
1417 self._pirepViewer.setPIREP(pirep)
1418 self._pirepViewer.show_all()
1419 self._pirepViewer.run()
1420 self._pirepViewer.hide()
1421
1422 def viewMessagedPIREP(self, pirep):
1423 """Display the PIREP viewer window with the given PIREP containing
1424 messages as well."""
1425 self._messagedPIREPViewer.setPIREP(pirep)
1426 self._messagedPIREPViewer.show_all()
1427 self._messagedPIREPViewer.run()
1428 self._messagedPIREPViewer.hide()
1429
1430 def editPIREP(self, pirep):
1431 """Display the PIREP editor window and allow editing the PIREP."""
1432 self._pirepEditor.setPIREP(pirep)
1433 self._pirepEditor.show_all()
1434 if self._pirepEditor.run()==RESPONSETYPE_OK:
1435 self.beginBusy(xstr("pirepEdit_save_busy"))
1436 self.webHandler.sendPIREP(self._pirepUpdatedCallback, pirep,
1437 update = True)
1438 else:
1439 self._pirepEditor.hide()
1440
1441 def _pirepUpdatedCallback(self, returned, result):
1442 """Callback for the PIREP updating result."""
1443 gobject.idle_add(self._handlePIREPUpdated, returned, result)
1444
1445 def _handlePIREPUpdated(self, returned, result):
1446 """Callback for the PIREP updating result."""
1447 self.endBusy()
1448 secondaryMarkup = None
1449 type = MESSAGETYPE_ERROR
1450 if returned:
1451 if result.success:
1452 type = None
1453 elif result.alreadyFlown:
1454 messageFormat = xstr("sendPIREP_already")
1455 secondaryMarkup = xstr("sendPIREP_already_sec")
1456 elif result.notAvailable:
1457 messageFormat = xstr("sendPIREP_notavail")
1458 else:
1459 messageFormat = xstr("sendPIREP_unknown")
1460 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1461 else:
1462 print "PIREP sending failed", result
1463 messageFormat = xstr("sendPIREP_failed")
1464 secondaryMarkup = xstr("sendPIREP_failed_sec")
1465
1466 if type is not None:
1467 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1468 type = type, message_format = messageFormat)
1469 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1470 dialog.set_title(WINDOW_TITLE_BASE)
1471 if secondaryMarkup is not None:
1472 dialog.format_secondary_markup(secondaryMarkup)
1473
1474 dialog.run()
1475 dialog.hide()
1476
1477 self._pirepEditor.hide()
1478
1479 def _loadPIREP(self, menuItem):
1480 """Load a PIREP for sending."""
1481 dialog = self._getLoadPirepDialog()
1482
1483 if self._lastLoadedPIREP:
1484 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1485 else:
1486 pirepDirectory = self.config.pirepDirectory
1487 if pirepDirectory is not None:
1488 dialog.set_current_folder(pirepDirectory)
1489
1490 result = dialog.run()
1491 dialog.hide()
1492
1493 if result==RESPONSETYPE_OK:
1494 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1495
1496 pirep = PIREP.load(self._lastLoadedPIREP)
1497 if pirep is None:
1498 dialog = gtk.MessageDialog(parent = self._mainWindow,
1499 type = MESSAGETYPE_ERROR,
1500 message_format = xstr("loadPIREP_failed"))
1501 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1502 dialog.set_title(WINDOW_TITLE_BASE)
1503 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1504 dialog.run()
1505 dialog.hide()
1506 else:
1507 dialog = self._getSendLoadedDialog(pirep)
1508 dialog.show_all()
1509 while True:
1510 result = dialog.run()
1511
1512 if result==RESPONSETYPE_OK:
1513 self.sendPIREP(pirep)
1514 elif result==1:
1515 self.viewPIREP(pirep)
1516 else:
1517 break
1518
1519 dialog.hide()
1520
1521 def _getLoadPirepDialog(self):
1522 """Get the PIREP loading file chooser dialog.
1523
1524 If it is not created yet, it will be created."""
1525 if self._loadPIREPDialog is None:
1526 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1527 xstr("loadPIREP_browser_title"),
1528 action = FILE_CHOOSER_ACTION_OPEN,
1529 buttons = (gtk.STOCK_CANCEL,
1530 RESPONSETYPE_CANCEL,
1531 gtk.STOCK_OK, RESPONSETYPE_OK),
1532 parent = self._mainWindow)
1533 dialog.set_modal(True)
1534
1535
1536 filter = gtk.FileFilter()
1537 filter.set_name(xstr("file_filter_pireps"))
1538 filter.add_pattern("*.pirep")
1539 dialog.add_filter(filter)
1540
1541 filter = gtk.FileFilter()
1542 filter.set_name(xstr("file_filter_all"))
1543 filter.add_pattern("*.*")
1544 dialog.add_filter(filter)
1545
1546 self._loadPIREPDialog = dialog
1547
1548 return self._loadPIREPDialog
1549
1550 def _getSendLoadedDialog(self, pirep):
1551 """Get a dialog displaying the main information of the flight from the
1552 PIREP and providing Cancel and Send buttons."""
1553 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1554 xstr("loadPIREP_send_title"),
1555 parent = self._mainWindow,
1556 flags = DIALOG_MODAL)
1557
1558 contentArea = dialog.get_content_area()
1559
1560 label = gtk.Label(xstr("loadPIREP_send_help"))
1561 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1562 xscale = 0.0, yscale = 0.0)
1563 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1564 padding_left = 48, padding_right = 48)
1565 alignment.add(label)
1566 contentArea.pack_start(alignment, False, False, 8)
1567
1568 table = gtk.Table(5, 2)
1569 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1570 xscale = 0.0, yscale = 0.0)
1571 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1572 padding_left = 48, padding_right = 48)
1573 table.set_row_spacings(4)
1574 table.set_col_spacings(16)
1575 tableAlignment.add(table)
1576 contentArea.pack_start(tableAlignment, True, True, 8)
1577
1578 bookedFlight = pirep.bookedFlight
1579
1580 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1581 label.set_use_markup(True)
1582 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1583 xscale = 0.0, yscale = 0.0)
1584 labelAlignment.add(label)
1585 table.attach(labelAlignment, 0, 1, 0, 1)
1586
1587 label = gtk.Label(bookedFlight.callsign)
1588 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1589 xscale = 0.0, yscale = 0.0)
1590 labelAlignment.add(label)
1591 table.attach(labelAlignment, 1, 2, 0, 1)
1592
1593 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1594 label.set_use_markup(True)
1595 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1596 xscale = 0.0, yscale = 0.0)
1597 labelAlignment.add(label)
1598 table.attach(labelAlignment, 0, 1, 1, 2)
1599
1600 label = gtk.Label(str(bookedFlight.departureTime.date()))
1601 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1602 xscale = 0.0, yscale = 0.0)
1603 labelAlignment.add(label)
1604 table.attach(labelAlignment, 1, 2, 1, 2)
1605
1606 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1607 label.set_use_markup(True)
1608 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1609 xscale = 0.0, yscale = 0.0)
1610 labelAlignment.add(label)
1611 table.attach(labelAlignment, 0, 1, 2, 3)
1612
1613 label = gtk.Label(bookedFlight.departureICAO)
1614 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1615 xscale = 0.0, yscale = 0.0)
1616 labelAlignment.add(label)
1617 table.attach(labelAlignment, 1, 2, 2, 3)
1618
1619 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1620 label.set_use_markup(True)
1621 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1622 xscale = 0.0, yscale = 0.0)
1623 labelAlignment.add(label)
1624 table.attach(labelAlignment, 0, 1, 3, 4)
1625
1626 label = gtk.Label(bookedFlight.arrivalICAO)
1627 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1628 xscale = 0.0, yscale = 0.0)
1629 labelAlignment.add(label)
1630 table.attach(labelAlignment, 1, 2, 3, 4)
1631
1632 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1633 label.set_use_markup(True)
1634 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1635 xscale = 0.0, yscale = 0.0)
1636 labelAlignment.add(label)
1637 table.attach(labelAlignment, 0, 1, 4, 5)
1638
1639 rating = pirep.rating
1640 label = gtk.Label()
1641 if rating<0:
1642 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1643 else:
1644 label.set_text("%.1f %%" % (rating,))
1645
1646 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1647 xscale = 0.0, yscale = 0.0)
1648 labelAlignment.add(label)
1649 table.attach(labelAlignment, 1, 2, 4, 5)
1650
1651 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1652 dialog.add_button(xstr("viewPIREP"), 1)
1653 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1654
1655 return dialog
1656
1657 def sendPIREP(self, pirep, callback = None):
1658 """Send the given PIREP."""
1659 self.beginBusy(xstr("sendPIREP_busy"))
1660 self._sendPIREPCallback = callback
1661 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1662
1663 def _pirepSentCallback(self, returned, result):
1664 """Callback for the PIREP sending result."""
1665 gobject.idle_add(self._handlePIREPSent, returned, result)
1666
1667 def _handlePIREPSent(self, returned, result):
1668 """Callback for the PIREP sending result."""
1669 self.endBusy()
1670 secondaryMarkup = None
1671 type = MESSAGETYPE_ERROR
1672 if returned:
1673 if result.success:
1674 type = MESSAGETYPE_INFO
1675 messageFormat = xstr("sendPIREP_success")
1676 secondaryMarkup = xstr("sendPIREP_success_sec")
1677 elif result.alreadyFlown:
1678 messageFormat = xstr("sendPIREP_already")
1679 secondaryMarkup = xstr("sendPIREP_already_sec")
1680 elif result.notAvailable:
1681 messageFormat = xstr("sendPIREP_notavail")
1682 else:
1683 messageFormat = xstr("sendPIREP_unknown")
1684 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1685 else:
1686 print "PIREP sending failed", result
1687 messageFormat = xstr("sendPIREP_failed")
1688 secondaryMarkup = xstr("sendPIREP_failed_sec")
1689
1690 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1691 type = type, message_format = messageFormat)
1692 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1693 dialog.set_title(WINDOW_TITLE_BASE)
1694 if secondaryMarkup is not None:
1695 dialog.format_secondary_markup(secondaryMarkup)
1696
1697 dialog.run()
1698 dialog.hide()
1699
1700 callback = self._sendPIREPCallback
1701 self._sendPIREPCallback = None
1702 if callback is not None:
1703 callback(returned, result)
1704
1705 def sendBugReport(self, summary, description, email, callback = None):
1706 """Send the bug report with the given data."""
1707 description += "\n\n" + ("=" * 40)
1708 description += "\n\nThe contents of the log:\n\n"
1709
1710 for (timestampString, text) in self._logger.lines:
1711 description += unicode(formatFlightLogLine(timestampString, text))
1712
1713 description += "\n\n" + ("=" * 40)
1714 description += "\n\nThe contents of the debug log:\n\n"
1715
1716 buffer = self._debugLogView.get_buffer()
1717 description += buffer.get_text(buffer.get_start_iter(),
1718 buffer.get_end_iter(), True)
1719
1720 self.beginBusy(xstr("sendBugReport_busy"))
1721 self._sendBugReportCallback = callback
1722 self.webHandler.sendBugReport(self._bugReportSentCallback,
1723 summary, description, email)
1724
1725 def _cefInitialized(self):
1726 """Called when CEF has been initialized."""
1727 self._acars.start()
1728 cef.initializeSimBrief()
1729
1730 def _bugReportSentCallback(self, returned, result):
1731 """Callback function for the bug report sending result."""
1732 gobject.idle_add(self._handleBugReportSent, returned, result)
1733
1734 def _handleBugReportSent(self, returned, result):
1735 """Callback for the bug report sending result."""
1736 self.endBusy()
1737 secondaryMarkup = None
1738 type = MESSAGETYPE_ERROR
1739 if returned:
1740 if result.success:
1741 type = MESSAGETYPE_INFO
1742 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1743 secondaryMarkup = xstr("sendBugReport_success_sec")
1744 else:
1745 messageFormat = xstr("sendBugReport_error")
1746 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1747 else:
1748 messageFormat = xstr("sendBugReport_error")
1749 secondaryMarkup = xstr("sendBugReport_error_sec")
1750
1751 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1752 type = type, message_format = messageFormat)
1753 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1754 dialog.set_title(WINDOW_TITLE_BASE)
1755 if secondaryMarkup is not None:
1756 dialog.format_secondary_markup(secondaryMarkup)
1757
1758 dialog.run()
1759 dialog.hide()
1760
1761 callback = self._sendBugReportCallback
1762 self._sendBugReportCallback = None
1763 if callback is not None:
1764 callback(returned, result)
1765
1766 def _listenHotkeys(self):
1767 """Setup the hotkeys based on the configuration."""
1768 if self._hotkeySetID is None and self._simulator is not None:
1769 self._pilotHotkeyIndex = None
1770 self._checklistHotkeyIndex = None
1771
1772 hotkeys = []
1773
1774 config = self.config
1775 if config.enableSounds and config.pilotControlsSounds:
1776 self._pilotHotkeyIndex = len(hotkeys)
1777 hotkeys.append(config.pilotHotkey)
1778
1779 if config.enableChecklists:
1780 self._checklistHotkeyIndex = len(hotkeys)
1781 hotkeys.append(config.checklistHotkey)
1782
1783 if hotkeys:
1784 self._hotkeySetID = \
1785 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1786
1787 def _clearHotkeys(self):
1788 """Clear the hotkeys."""
1789 if self._hotkeySetID is not None:
1790 self._hotkeySetID=None
1791 self._simulator.clearHotkeys()
1792
1793 def _handleHotkeys(self, id, hotkeys):
1794 """Handle the hotkeys."""
1795 if id==self._hotkeySetID:
1796 for index in hotkeys:
1797 if index==self._pilotHotkeyIndex:
1798 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1799 self._flight.pilotHotkeyPressed()
1800 elif index==self._checklistHotkeyIndex:
1801 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1802 self._flight.checklistHotkeyPressed()
1803 else:
1804 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1805
1806 def _showManual(self, menuitem):
1807 """Show the user's manual."""
1808 webbrowser.open(url ="file://" +
1809 os.path.join(self._programDirectory, "doc", "manual",
1810 getLanguage(), "index.html"),
1811 new = 1)
1812
1813 def _showAbout(self, menuitem):
1814 """Show the about dialog."""
1815 dialog = self._getAboutDialog()
1816 dialog.show_all()
1817 dialog.run()
1818 dialog.hide()
1819
1820 def _getAboutDialog(self):
1821 """Get the about dialog.
1822
1823 If it does not exist yet, it will be created."""
1824 if self._aboutDialog is None:
1825 dialog = gtk.AboutDialog()
1826 dialog.set_transient_for(self._mainWindow)
1827 dialog.set_modal(True)
1828
1829 logoPath = os.path.join(self._programDirectory, "logo.png")
1830 logo = pixbuf_new_from_file(logoPath)
1831 dialog.set_logo(logo)
1832
1833 dialog.set_program_name(PROGRAM_NAME)
1834 dialog.set_version(const.VERSION)
1835 dialog.set_copyright("(c) 2012 by István Váradi")
1836 dialog.set_website("http://mlx.varadiistvan.hu")
1837 dialog.set_website_label(xstr("about_website"))
1838
1839 isHungarian = getLanguage()=="hu"
1840 authors = []
1841 for (familyName, firstName, role) in GUI._authors:
1842 author = "%s %s" % \
1843 (familyName if isHungarian else firstName,
1844 firstName if isHungarian else familyName)
1845 role = xstr("about_role_" + role)
1846 authors.append(author + " (" + role + ")")
1847 dialog.set_authors(authors)
1848
1849 dialog.set_license(xstr("about_license"))
1850
1851 if not pygobject:
1852 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1853
1854 self._aboutDialog = dialog
1855
1856 return self._aboutDialog
1857
1858 def _showAboutURL(self, dialog, link, user_data):
1859 """Show the about URL."""
1860 webbrowser.open(url = link, new = 1)
1861
1862 def _setTakeoffAntiIceOn(self, value):
1863 """Set the anti-ice on indicator."""
1864 self._wizard.takeoffAntiIceOn = value
1865
1866 def _setLandingAntiIceOn(self, value):
1867 """Set the anti-ice on indicator."""
1868 self._wizard.landingAntiIceOn = value
1869
1870 def _getCredentialsCallback(self):
1871 """Called when the web handler asks for the credentials."""
1872 # FIXME: this is almost the same as
1873 # SimBriefSetupPage._getCredentialsCallback
1874 with self._credentialsCondition:
1875 self._credentialsAvailable = False
1876
1877 gobject.idle_add(self._getCredentials)
1878
1879 while not self._credentialsAvailable:
1880 self._credentialsCondition.wait()
1881
1882 return (self._credentialsUserName, self._credentialsPassword)
1883
1884 def _getCredentials(self):
1885 """Get the credentials."""
1886 # FIXME: this is almost the same as
1887 # SimBriefSetupPage._getCredentials
1888 with self._credentialsCondition:
1889 config = self.config
1890
1891 dialog = CredentialsDialog(self, config.pilotID, config.password,
1892 xstr("login_title"),
1893 xstr("button_cancel"),
1894 xstr("button_ok"),
1895 xstr("label_pilotID"),
1896 xstr("login_pilotID_tooltip"),
1897 xstr("label_password"),
1898 xstr("login_password_tooltip"),
1899 xstr("login_info"),
1900 config.rememberPassword,
1901 xstr("remember_password"),
1902 xstr("login_remember_tooltip"))
1903 response = dialog.run()
1904
1905 if response==RESPONSETYPE_OK:
1906 self._credentialsUserName = dialog.userName
1907 self._credentialsPassword = dialog.password
1908 rememberPassword = dialog.rememberPassword
1909
1910 config.pilotID = self._credentialsUserName
1911
1912 config.password = \
1913 self._credentialsPassword if rememberPassword else ""
1914 config.rememberPassword = rememberPassword
1915
1916 config.save()
1917 else:
1918 self._credentialsUserName = None
1919 self._credentialsPassword = None
1920
1921 self._credentialsAvailable = True
1922 self._credentialsCondition.notify()
1923
1924 def _updateDone(self):
1925 """Called when the update is done.
1926
1927 It checks if we already know the PID, and if not, asks the user whether
1928 to register."""
1929 cef.initialize(self._cefInitialized)
1930
1931 if not self.config.pilotID and not self.config.password:
1932 dialog = gtk.MessageDialog(parent = self._mainWindow,
1933 type = MESSAGETYPE_QUESTION,
1934 message_format = xstr("register_ask"))
1935
1936 dialog.set_title(WINDOW_TITLE_BASE)
1937 dialog.format_secondary_markup(xstr("register_ask_sec"))
1938
1939 dialog.add_button(xstr("button_cancel"), 0)
1940 dialog.add_button(xstr("button_register"), 1)
1941 dialog.set_default_response(1)
1942
1943 result = dialog.run()
1944 dialog.hide()
1945 if result == 1:
1946 self._wizard.jumpPage("register")
Note: See TracBrowser for help on using the repository browser.