source: src/mlx/gui/gui.py@ 874:f9410f9a8d84

Last change on this file since 874:f9410f9a8d84 was 869:0797a6796f55, checked in by István Váradi <ivaradi@…>, 7 years ago

It is possible to book a flight from the flight selection page too (re #304)

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 cef.initialize(self._cefInitialized)
465
466 singleton.raiseCallback = self.raiseCallback
467 gtk.main()
468 singleton.raiseCallback = None
469
470 cef.finalize()
471
472 self._disconnect()
473
474 def updateDone(self):
475 """Called when the update is done (and there is no need to restart)."""
476 gobject.idle_add(self._updateDone)
477
478 def connected(self, fsType, descriptor):
479 """Called when we have connected to the simulator."""
480 self._connected = True
481 self._logger.untimedMessage("MLX %s connected to the simulator %s" % \
482 (const.VERSION, descriptor))
483 fs.sendMessage(const.MESSAGETYPE_INFORMATION,
484 "Welcome to MAVA Logger X " + const.VERSION)
485 gobject.idle_add(self._handleConnected, fsType, descriptor)
486
487 def _handleConnected(self, fsType, descriptor):
488 """Called when the connection to the simulator has succeeded."""
489 self._statusbar.updateConnection(self._connecting, self._connected)
490 self.endBusy()
491 if not self._reconnecting:
492 self._wizard.connected(fsType, descriptor)
493 self._reconnecting = False
494 self._fsType = fsType
495 self._listenHotkeys()
496
497 def connectionFailed(self):
498 """Called when the connection failed."""
499 self._logger.untimedMessage("Connection to the simulator failed")
500 gobject.idle_add(self._connectionFailed)
501
502 def _connectionFailed(self):
503 """Called when the connection failed."""
504 self.endBusy()
505 self._statusbar.updateConnection(self._connecting, self._connected)
506
507 dialog = gtk.MessageDialog(parent = self._mainWindow,
508 type = MESSAGETYPE_ERROR,
509 message_format = xstr("conn_failed"))
510
511 dialog.set_title(WINDOW_TITLE_BASE)
512 dialog.format_secondary_markup(xstr("conn_failed_sec"))
513
514 dialog.add_button(xstr("button_cancel"), 0)
515 dialog.add_button(xstr("button_tryagain"), 1)
516 dialog.set_default_response(1)
517
518 result = dialog.run()
519 dialog.hide()
520 if result == 1:
521 self.beginBusy(xstr("connect_busy"))
522 self._simulator.reconnect()
523 else:
524 self.reset()
525
526 def disconnected(self):
527 """Called when we have disconnected from the simulator."""
528 self._connected = False
529 self._logger.untimedMessage("Disconnected from the simulator")
530 if self._flight is not None:
531 self._flight.disconnected()
532
533 gobject.idle_add(self._disconnected)
534
535 def _disconnected(self):
536 """Called when we have disconnected from the simulator unexpectedly."""
537 self._statusbar.updateConnection(self._connecting, self._connected)
538
539 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
540 message_format = xstr("conn_broken"),
541 parent = self._mainWindow)
542 dialog.set_title(WINDOW_TITLE_BASE)
543 dialog.format_secondary_markup(xstr("conn_broken_sec"))
544
545 dialog.add_button(xstr("button_cancel"), 0)
546 dialog.add_button(xstr("button_reconnect"), 1)
547 dialog.set_default_response(1)
548
549 result = dialog.run()
550 dialog.hide()
551 if result == 1:
552 self.beginBusy(xstr("connect_busy"))
553 self._reconnecting = True
554 self._simulator.reconnect()
555 else:
556 self.reset()
557
558 def enableFlightInfo(self, aircraftType):
559 """Enable the flight info tab."""
560 self._flightInfo.enable(aircraftType)
561
562 def bookFlights(self, callback, flightIDs, date, tailNumber,
563 busyCallback = None):
564 """Initiate the booking of flights with the given timetable IDs and
565 other data"""
566 self._bookFlightsUserCallback = callback
567 self._bookFlightsBusyCallback = busyCallback
568
569 self.beginBusy(xstr("bookflights_busy"))
570 if busyCallback is not None:
571 busyCallback(True)
572
573 self.webHandler.bookFlights(self._bookFlightsCallback,
574 flightIDs, date, tailNumber)
575
576 def _bookFlightsCallback(self, returned, result):
577 """Called when the booking of flights has finished."""
578 gobject.idle_add(self._handleBookFlightsResult, returned, result)
579
580 def _handleBookFlightsResult(self, returned, result):
581 """Called when the booking of flights is done.
582
583 If it was successful, the booked flights are added to the list of the
584 flight selector."""
585 if self._bookFlightsBusyCallback is not None:
586 self._bookFlightsBusyCallback(False)
587 self.endBusy()
588
589 if returned:
590 for bookedFlight in result.bookedFlights:
591 self._wizard.addFlight(bookedFlight)
592
593 self._bookFlightsUserCallback(returned, result)
594
595 def cancelFlight(self):
596 """Cancel the current file, if the user confirms it."""
597 dialog = gtk.MessageDialog(parent = self._mainWindow,
598 type = MESSAGETYPE_QUESTION,
599 message_format = xstr("cancelFlight_question"))
600
601 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
602 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
603
604 dialog.set_title(WINDOW_TITLE_BASE)
605 result = dialog.run()
606 dialog.hide()
607
608 if result==RESPONSETYPE_YES:
609 self.reset()
610
611 def reset(self):
612 """Reset the GUI."""
613 self._disconnect()
614
615 self._simulator = None
616
617 self._flightInfo.reset()
618 self._flightInfo.disable()
619 self.resetFlightStatus()
620
621 self._weightHelp.reset()
622 self._weightHelp.disable()
623 self._notebook.set_current_page(0)
624
625 self._logView.get_buffer().set_text("")
626
627 if self.loggedIn:
628 self._wizard.cancelFlight(self._handleReloadResult)
629 else:
630 self._wizard.reset(None)
631
632 def _handleReloadResult(self, returned, result):
633 """Handle the result of the reloading of the flights."""
634 self._wizard.reset(result if returned and result.loggedIn else None)
635
636 def _disconnect(self, closingMessage = None, duration = 3):
637 """Disconnect from the simulator if connected."""
638 self.stopMonitoring()
639 self._clearHotkeys()
640
641 if self._connected:
642 if closingMessage is None:
643 self._flight.simulator.disconnect()
644 else:
645 fs.sendMessage(const.MESSAGETYPE_ENVIRONMENT,
646 closingMessage, duration,
647 disconnect = True)
648 self._connected = False
649
650 self._connecting = False
651 self._reconnecting = False
652 self._statusbar.updateConnection(False, False)
653 self._weightHelp.disable()
654
655 return True
656
657 def insertFlightLogLine(self, index, timestampString, text, isFault):
658 """Insert the flight log line with the given data."""
659 gobject.idle_add(self._insertFlightLogLine, index,
660 formatFlightLogLine(timestampString, text),
661 isFault)
662
663 def _insertFlightLogLine(self, index, line, isFault):
664 """Perform the real insertion.
665
666 To be called from the event loop."""
667 buffer = self._logView.get_buffer()
668 lineIter = buffer.get_iter_at_line(index)
669 insertTextBuffer(buffer, lineIter, line, isFault = isFault)
670 self._logView.scroll_mark_onscreen(buffer.get_insert())
671
672 def removeFlightLogLine(self, index):
673 """Remove the flight log line with the given index."""
674 gobject.idle_add(self._removeFlightLogLine, index)
675
676 def addFault(self, id, timestampString, text):
677 """Add a fault to the list of faults."""
678 faultText = formatFlightLogLine(timestampString, text).strip()
679 gobject.idle_add(self._flightInfo.addFault, id, faultText)
680
681 def updateFault(self, id, timestampString, text):
682 """Update a fault in the list of faults."""
683 faultText = formatFlightLogLine(timestampString, text).strip()
684 gobject.idle_add(self._flightInfo.updateFault, id, faultText)
685
686 def clearFault(self, id):
687 """Clear a fault in the list of faults."""
688 gobject.idle_add(self._flightInfo.clearFault, id)
689
690 def _removeFlightLogLine(self, index):
691 """Perform the real removal."""
692 buffer = self._logView.get_buffer()
693 startIter = buffer.get_iter_at_line(index)
694 endIter = buffer.get_iter_at_line(index+1)
695 buffer.delete(startIter, endIter)
696 self._logView.scroll_mark_onscreen(buffer.get_insert())
697
698 def check(self, flight, aircraft, logger, oldState, state):
699 """Update the data."""
700 gobject.idle_add(self._monitorWindow.setData, state)
701 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
702
703 def resetFlightStatus(self):
704 """Reset the status of the flight."""
705 self._statusbar.resetFlightStatus()
706 self._statusbar.updateTime()
707 self._statusIcon.resetFlightStatus()
708
709 def setStage(self, stage):
710 """Set the stage of the flight."""
711 gobject.idle_add(self._setStage, stage)
712
713 def _setStage(self, stage):
714 """Set the stage of the flight."""
715 self._statusbar.setStage(stage)
716 self._statusIcon.setStage(stage)
717 self._wizard.setStage(stage)
718 if stage==const.STAGE_END:
719 welcomeMessage = \
720 airports.getWelcomeMessage(self.bookedFlight.arrivalICAO)
721 self._disconnect(closingMessage =
722 "Flight plan closed. " + welcomeMessage,
723 duration = 5)
724
725 def setRating(self, rating):
726 """Set the rating of the flight."""
727 gobject.idle_add(self._setRating, rating)
728
729 def _setRating(self, rating):
730 """Set the rating of the flight."""
731 self._statusbar.setRating(rating)
732 self._statusIcon.setRating(rating)
733
734 def setNoGo(self, reason):
735 """Set the rating of the flight to No-Go with the given reason."""
736 gobject.idle_add(self._setNoGo, reason)
737
738 def _setNoGo(self, reason):
739 """Set the rating of the flight."""
740 self._statusbar.setNoGo(reason)
741 self._statusIcon.setNoGo(reason)
742
743 def _handleMainWindowState(self, window, event):
744 """Hande a change in the state of the window"""
745 iconified = gdk.WindowState.ICONIFIED if pygobject \
746 else gdk.WINDOW_STATE_ICONIFIED
747
748 if (event.changed_mask&WINDOW_STATE_WITHDRAWN)!=0:
749 if (event.new_window_state&WINDOW_STATE_WITHDRAWN)!=0:
750 self._statusIcon.mainWindowHidden()
751 else:
752 self._statusIcon.mainWindowShown()
753
754 if self.config.hideMinimizedWindow and not pygobject and \
755 (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
756 (event.new_window_state&WINDOW_STATE_ICONIFIED)!=0:
757 self.hideMainWindow(savePosition = False)
758 elif (event.changed_mask&WINDOW_STATE_ICONIFIED)!=0 and \
759 (event.new_window_state&WINDOW_STATE_ICONIFIED)==0:
760 self._mainWindow.present()
761
762 def _handleLeaveNotify(self, widget, event):
763 """Handle the leave-notify event.
764
765 Here we reset the focus to the main window as CEF might have acquired
766 it earlier."""
767 self._mainWindow.get_window().focus()
768
769 def raiseCallback(self):
770 """Callback for the singleton handling code."""
771 gobject.idle_add(self.raiseMainWindow)
772
773 def raiseMainWindow(self):
774 """Show the main window if invisible, and raise it."""
775 if not self._mainWindow.get_visible():
776 self.showMainWindow()
777 self._mainWindow.present()
778
779 def deleteMainWindow(self, window, event):
780 """Handle the delete event for the main window."""
781 if self.config.quitOnClose:
782 self._quit()
783 else:
784 self.hideMainWindow()
785 return True
786
787 def hideMainWindow(self, savePosition = True):
788 """Hide the main window and save its position."""
789 if savePosition:
790 (self._mainWindowX, self._mainWindowY) = \
791 self._mainWindow.get_window().get_root_origin()
792 else:
793 self._mainWindowX = self._mainWindowY = None
794 self._mainWindow.hide()
795 return True
796
797 def showMainWindow(self):
798 """Show the main window at its former position."""
799 if self._mainWindowX is not None and self._mainWindowY is not None:
800 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
801
802 if pygobject:
803 self._mainWindow.show()
804 else:
805 self._mainWindow.present()
806 self._mainWindow.deiconify()
807
808 def toggleMainWindow(self):
809 """Toggle the main window."""
810 if self._mainWindow.get_visible():
811 self.hideMainWindow()
812 else:
813 self.showMainWindow()
814
815 def hideMonitorWindow(self, savePosition = True):
816 """Hide the monitor window."""
817 if savePosition:
818 (self._monitorWindowX, self._monitorWindowY) = \
819 self._monitorWindow.get_window().get_root_origin()
820 else:
821 self._monitorWindowX = self._monitorWindowY = None
822 self._monitorWindow.hide()
823 self._statusIcon.monitorWindowHidden()
824 if self._showMonitorMenuItem.get_active():
825 self._selfToggling = True
826 self._showMonitorMenuItem.set_active(False)
827 return True
828
829 def showMonitorWindow(self):
830 """Show the monitor window."""
831 if self._monitorWindowX is not None and self._monitorWindowY is not None:
832 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
833 self._monitorWindow.show_all()
834 self._statusIcon.monitorWindowShown()
835 if not self._showMonitorMenuItem.get_active():
836 self._selfToggling = True
837 self._showMonitorMenuItem.set_active(True)
838
839 def _toggleMonitorWindow(self, menuItem):
840 if self._selfToggling:
841 self._selfToggling = False
842 elif self._monitorWindow.get_visible():
843 self.hideMonitorWindow()
844 else:
845 self.showMonitorWindow()
846
847 def restart(self):
848 """Quit and restart the application."""
849 self.toRestart = True
850 self._quit(force = True)
851
852 def flushStdIO(self):
853 """Flush any text to the standard error that could not be logged."""
854 if self._stdioText:
855 sys.__stderr__.write(self._stdioText)
856
857 def writeStdIO(self, text):
858 """Write the given text into standard I/O log."""
859 with self._stdioLock:
860 self._stdioText += text
861
862 gobject.idle_add(self._writeStdIO)
863
864 def beginBusy(self, message):
865 """Begin a period of background processing."""
866 self._wizard.set_sensitive(False)
867 self._weightHelp.set_sensitive(False)
868 self._mainWindow.get_window().set_cursor(self._busyCursor)
869 self._statusbar.updateBusyState(message)
870
871 def updateBusyState(self, message):
872 """Update the busy state."""
873 self._statusbar.updateBusyState(message)
874
875 def endBusy(self):
876 """End a period of background processing."""
877 self._mainWindow.get_window().set_cursor(None)
878 self._weightHelp.set_sensitive(True)
879 self._wizard.set_sensitive(True)
880 self._statusbar.updateBusyState(None)
881
882 def initializeWeightHelp(self):
883 """Initialize the weight help tab."""
884 self._weightHelp.reset()
885 self._weightHelp.enable()
886
887 def getFleetAsync(self, callback = None, force = None):
888 """Get the fleet asynchronously."""
889 gobject.idle_add(self.getFleet, callback, force)
890
891 def getFleet(self, callback = None, force = False, busyCallback = None):
892 """Get the fleet.
893
894 If force is False, and we already have a fleet retrieved,
895 that one will be used."""
896 if self._fleet is None or force:
897 self._fleetCallback = callback
898 self._fleetBusyCallback = busyCallback
899 if busyCallback is not None:
900 busyCallback(True)
901 self.beginBusy(xstr("fleet_busy"))
902 self.webHandler.getFleet(self._fleetResultCallback)
903 else:
904 callback(self._fleet)
905
906 def commentsChanged(self):
907 """Indicate that the comments have changed."""
908 self._wizard.commentsChanged()
909
910 def delayCodesChanged(self):
911 """Called when the delay codes have changed."""
912 self._wizard.delayCodesChanged()
913
914 def faultExplanationsChanged(self):
915 """Called when the status of the explanations of the faults have
916 changed."""
917 self._wizard.faultExplanationsChanged()
918
919 def updateRTO(self, inLoop = False):
920 """Indicate that the RTO state should be updated."""
921 if inLoop:
922 self._wizard.updateRTO()
923 else:
924 gobject.idle_add(self.updateRTO, True)
925
926 def rtoToggled(self, indicated):
927 """Called when the user has toggled the RTO checkbox."""
928 self._flight.rtoToggled(indicated)
929
930 def _fleetResultCallback(self, returned, result):
931 """Called when the fleet has been queried."""
932 gobject.idle_add(self._handleFleetResult, returned, result)
933
934 def _handleFleetResult(self, returned, result):
935 """Handle the fleet result."""
936 self.endBusy()
937 if self._fleetBusyCallback is not None:
938 self._fleetBusyCallback(False)
939 if returned:
940 self._fleet = result.fleet
941 else:
942 self._fleet = None
943
944 dialog = gtk.MessageDialog(parent = self.mainWindow,
945 type = MESSAGETYPE_ERROR,
946 message_format = xstr("fleet_failed"))
947 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
948 dialog.set_title(WINDOW_TITLE_BASE)
949 dialog.run()
950 dialog.hide()
951
952 callback = self._fleetCallback
953 self._fleetCallback = None
954 self._fleetBusyCallback = None
955 if callback is not None:
956 callback(self._fleet)
957 self._fleetGateStatus.handleFleet(self._fleet)
958
959 def updatePlane(self, tailNumber, status,
960 gateNumber = None, callback = None):
961 """Update the status of the given plane."""
962 self.beginBusy(xstr("fleet_update_busy"))
963
964 self._updatePlaneCallback = callback
965
966 self._updatePlaneTailNumber = tailNumber
967 self._updatePlaneStatus = status
968 self._updatePlaneGateNumber = gateNumber
969
970 self.webHandler.updatePlane(self._updatePlaneResultCallback,
971 tailNumber, status, gateNumber)
972
973 def _updatePlaneResultCallback(self, returned, result):
974 """Called when the status of a plane has been updated."""
975 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
976
977 def _handleUpdatePlaneResult(self, returned, result):
978 """Handle the plane update result."""
979 self.endBusy()
980 if returned:
981 success = result.success
982 if success:
983 if self._fleet is not None:
984 self._fleet.updatePlane(self._updatePlaneTailNumber,
985 self._updatePlaneStatus,
986 self._updatePlaneGateNumber)
987 self._fleetGateStatus.handleFleet(self._fleet)
988 else:
989 dialog = gtk.MessageDialog(parent = self.mainWindow,
990 type = MESSAGETYPE_ERROR,
991 message_format = xstr("fleet_update_failed"))
992 dialog.add_button(xstr("button_ok"), RESPONSETYPE_ACCEPT)
993 dialog.set_title(WINDOW_TITLE_BASE)
994 dialog.run()
995 dialog.hide()
996
997 success = None
998
999 callback = self._updatePlaneCallback
1000 self._updatePlaneCallback = None
1001 if callback is not None:
1002 callback(success)
1003
1004 def _writeStdIO(self):
1005 """Perform the real writing."""
1006 with self._stdioLock:
1007 text = self._stdioText
1008 self._stdioText = ""
1009 if not text: return
1010
1011 lines = text.splitlines()
1012 if text[-1]=="\n":
1013 text = ""
1014 else:
1015 text = lines[-1]
1016 lines = lines[:-1]
1017
1018 now = datetime.datetime.now()
1019 timeStr = "%02d:%02d:%02d: " % (now.hour, now.minute, now.second)
1020
1021 for line in lines:
1022 #print >> sys.__stdout__, line
1023 if self._stdioStartingLine:
1024 self._writeLog(timeStr, self._debugLogView)
1025 self._writeLog(line + "\n", self._debugLogView)
1026 self._stdioStartingLine = True
1027
1028 if text:
1029 #print >> sys.__stdout__, text,
1030 if self._stdioStartingLine:
1031 self._writeLog(timeStr, self._debugLogView)
1032 self._writeLog(text, self._debugLogView)
1033 self._stdioStartingLine = False
1034
1035 def connectSimulator(self, bookedFlight, simulatorType):
1036 """Connect to the simulator for the first time."""
1037 self._logger.reset()
1038
1039 self._flight = flight.Flight(self._logger, self)
1040 self._flight.flareTimeFromFS = self.config.flareTimeFromFS
1041 self._flight.aircraftType = bookedFlight.aircraftType
1042 self._flight.aircraft = acft.Aircraft.create(self._flight, bookedFlight)
1043 self._flight.aircraft._checkers.append(self)
1044
1045 if self._simulator is None:
1046 self._simulator = fs.createSimulator(simulatorType, self)
1047 fs.setupMessageSending(self.config, self._simulator)
1048 self._setupTimeSync()
1049
1050 self._flight.simulator = self._simulator
1051
1052 self.beginBusy(xstr("connect_busy"))
1053 self._statusbar.updateConnection(self._connecting, self._connected)
1054
1055 self._connecting = True
1056 self._simulator.connect(self._flight.aircraft)
1057
1058 def startMonitoring(self):
1059 """Start monitoring."""
1060 if not self._monitoring:
1061 self.simulator.startMonitoring()
1062 self._monitoring = True
1063
1064 def stopMonitoring(self):
1065 """Stop monitoring."""
1066 if self._monitoring:
1067 self.simulator.stopMonitoring()
1068 self._monitoring = False
1069
1070 def cruiseLevelChanged(self):
1071 """Called when the cruise level is changed in the flight wizard."""
1072 if self._flight is not None:
1073 return self._flight.cruiseLevelChanged()
1074 else:
1075 return False
1076
1077 def _buildMenuBar(self, accelGroup):
1078 """Build the main menu bar."""
1079 menuBar = gtk.MenuBar()
1080
1081 fileMenuItem = gtk.MenuItem(xstr("menu_file"))
1082 fileMenu = gtk.Menu()
1083 fileMenuItem.set_submenu(fileMenu)
1084 menuBar.append(fileMenuItem)
1085
1086 loadPIREPMenuItem = gtk.ImageMenuItem(gtk.STOCK_OPEN)
1087 loadPIREPMenuItem.set_use_stock(True)
1088 loadPIREPMenuItem.set_label(xstr("menu_file_loadPIREP"))
1089 loadPIREPMenuItem.add_accelerator("activate", accelGroup,
1090 ord(xstr("menu_file_loadPIREP_key")),
1091 CONTROL_MASK, ACCEL_VISIBLE)
1092 loadPIREPMenuItem.connect("activate", self._loadPIREP)
1093 fileMenu.append(loadPIREPMenuItem)
1094
1095 fileMenu.append(gtk.SeparatorMenuItem())
1096
1097 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
1098 quitMenuItem.set_use_stock(True)
1099 quitMenuItem.set_label(xstr("menu_file_quit"))
1100 quitMenuItem.add_accelerator("activate", accelGroup,
1101 ord(xstr("menu_file_quit_key")),
1102 CONTROL_MASK, ACCEL_VISIBLE)
1103 quitMenuItem.connect("activate", self._quit)
1104 fileMenu.append(quitMenuItem)
1105
1106 toolsMenuItem = gtk.MenuItem(xstr("menu_tools"))
1107 toolsMenu = gtk.Menu()
1108 toolsMenuItem.set_submenu(toolsMenu)
1109 menuBar.append(toolsMenuItem)
1110
1111 self._timetableMenuItem = timetableMenuItem = \
1112 gtk.ImageMenuItem(gtk.STOCK_INDENT)
1113 timetableMenuItem.set_use_stock(True)
1114 timetableMenuItem.set_label(xstr("menu_tools_timetable"))
1115 timetableMenuItem.add_accelerator("activate", accelGroup,
1116 ord(xstr("menu_tools_timetable_key")),
1117 CONTROL_MASK, ACCEL_VISIBLE)
1118 timetableMenuItem.connect("activate", self.showTimetable)
1119 self._timetableMenuItem.set_sensitive(False)
1120 toolsMenu.append(timetableMenuItem)
1121
1122 self._flightsMenuItem = flightsMenuItem = \
1123 gtk.ImageMenuItem(gtk.STOCK_SPELL_CHECK)
1124 flightsMenuItem.set_use_stock(True)
1125 flightsMenuItem.set_label(xstr("menu_tools_flights"))
1126 flightsMenuItem.add_accelerator("activate", accelGroup,
1127 ord(xstr("menu_tools_flights_key")),
1128 CONTROL_MASK, ACCEL_VISIBLE)
1129 flightsMenuItem.connect("activate", self.showFlights)
1130 self._flightsMenuItem.set_sensitive(False)
1131 toolsMenu.append(flightsMenuItem)
1132
1133 checklistMenuItem = gtk.ImageMenuItem(gtk.STOCK_APPLY)
1134 checklistMenuItem.set_use_stock(True)
1135 checklistMenuItem.set_label(xstr("menu_tools_chklst"))
1136 checklistMenuItem.add_accelerator("activate", accelGroup,
1137 ord(xstr("menu_tools_chklst_key")),
1138 CONTROL_MASK, ACCEL_VISIBLE)
1139 checklistMenuItem.connect("activate", self._editChecklist)
1140 toolsMenu.append(checklistMenuItem)
1141
1142 approachCalloutsMenuItem = gtk.ImageMenuItem(gtk.STOCK_EDIT)
1143 approachCalloutsMenuItem.set_use_stock(True)
1144 approachCalloutsMenuItem.set_label(xstr("menu_tools_callouts"))
1145 approachCalloutsMenuItem.add_accelerator("activate", accelGroup,
1146 ord(xstr("menu_tools_callouts_key")),
1147 CONTROL_MASK, ACCEL_VISIBLE)
1148 approachCalloutsMenuItem.connect("activate", self._editApproachCallouts)
1149 toolsMenu.append(approachCalloutsMenuItem)
1150
1151 prefsMenuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
1152 prefsMenuItem.set_use_stock(True)
1153 prefsMenuItem.set_label(xstr("menu_tools_prefs"))
1154 prefsMenuItem.add_accelerator("activate", accelGroup,
1155 ord(xstr("menu_tools_prefs_key")),
1156 CONTROL_MASK, ACCEL_VISIBLE)
1157 prefsMenuItem.connect("activate", self._editPreferences)
1158 toolsMenu.append(prefsMenuItem)
1159
1160 toolsMenu.append(gtk.SeparatorMenuItem())
1161
1162 bugReportMenuItem = gtk.ImageMenuItem(gtk.STOCK_PASTE)
1163 bugReportMenuItem.set_use_stock(True)
1164 bugReportMenuItem.set_label(xstr("menu_tools_bugreport"))
1165 bugReportMenuItem.add_accelerator("activate", accelGroup,
1166 ord(xstr("menu_tools_bugreport_key")),
1167 CONTROL_MASK, ACCEL_VISIBLE)
1168 bugReportMenuItem.connect("activate", self._reportBug)
1169 toolsMenu.append(bugReportMenuItem)
1170
1171 viewMenuItem = gtk.MenuItem(xstr("menu_view"))
1172 viewMenu = gtk.Menu()
1173 viewMenuItem.set_submenu(viewMenu)
1174 menuBar.append(viewMenuItem)
1175
1176 self._showMonitorMenuItem = gtk.CheckMenuItem()
1177 self._showMonitorMenuItem.set_label(xstr("menu_view_monitor"))
1178 self._showMonitorMenuItem.set_use_underline(True)
1179 self._showMonitorMenuItem.set_active(False)
1180 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
1181 ord(xstr("menu_view_monitor_key")),
1182 CONTROL_MASK, ACCEL_VISIBLE)
1183 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
1184 viewMenu.append(self._showMonitorMenuItem)
1185
1186 showDebugMenuItem = gtk.CheckMenuItem()
1187 showDebugMenuItem.set_label(xstr("menu_view_debug"))
1188 showDebugMenuItem.set_use_underline(True)
1189 showDebugMenuItem.set_active(False)
1190 showDebugMenuItem.add_accelerator("activate", accelGroup,
1191 ord(xstr("menu_view_debug_key")),
1192 CONTROL_MASK, ACCEL_VISIBLE)
1193 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
1194 viewMenu.append(showDebugMenuItem)
1195
1196 helpMenuItem = gtk.MenuItem(xstr("menu_help"))
1197 helpMenu = gtk.Menu()
1198 helpMenuItem.set_submenu(helpMenu)
1199 menuBar.append(helpMenuItem)
1200
1201 manualMenuItem = gtk.ImageMenuItem(gtk.STOCK_HELP)
1202 manualMenuItem.set_use_stock(True)
1203 manualMenuItem.set_label(xstr("menu_help_manual"))
1204 manualMenuItem.add_accelerator("activate", accelGroup,
1205 ord(xstr("menu_help_manual_key")),
1206 CONTROL_MASK, ACCEL_VISIBLE)
1207 manualMenuItem.connect("activate", self._showManual)
1208 helpMenu.append(manualMenuItem)
1209
1210 helpMenu.append(gtk.SeparatorMenuItem())
1211
1212 aboutMenuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
1213 aboutMenuItem.set_use_stock(True)
1214 aboutMenuItem.set_label(xstr("menu_help_about"))
1215 aboutMenuItem.add_accelerator("activate", accelGroup,
1216 ord(xstr("menu_help_about_key")),
1217 CONTROL_MASK, ACCEL_VISIBLE)
1218 aboutMenuItem.connect("activate", self._showAbout)
1219 helpMenu.append(aboutMenuItem)
1220
1221 return menuBar
1222
1223 def _toggleDebugLog(self, menuItem):
1224 """Toggle the debug log."""
1225 if menuItem.get_active():
1226 label = gtk.Label(xstr("tab_debug_log"))
1227 label.set_use_underline(True)
1228 label.set_tooltip_text(xstr("tab_debug_log_tooltip"))
1229 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
1230 self._notebook.set_current_page(self._debugLogPage)
1231 else:
1232 self._notebook.remove_page(self._debugLogPage)
1233
1234 def _buildLogWidget(self):
1235 """Build the widget for the log."""
1236 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
1237
1238 alignment.set_padding(padding_top = 8, padding_bottom = 8,
1239 padding_left = 16, padding_right = 16)
1240
1241 logScroller = gtk.ScrolledWindow()
1242 # FIXME: these should be constants in common
1243 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
1244 else gtk.POLICY_AUTOMATIC,
1245 gtk.PolicyType.AUTOMATIC if pygobject
1246 else gtk.POLICY_AUTOMATIC)
1247 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
1248 else gtk.SHADOW_IN)
1249 logView = gtk.TextView()
1250 logView.set_editable(False)
1251 logView.set_cursor_visible(False)
1252 logScroller.add(logView)
1253
1254 logBox = gtk.VBox()
1255 logBox.pack_start(logScroller, True, True, 0)
1256 logBox.set_size_request(-1, 200)
1257
1258 alignment.add(logBox)
1259
1260 return (alignment, logView)
1261
1262 def _writeLog(self, msg, logView, isFault = False):
1263 """Write the given message to the log."""
1264 buffer = logView.get_buffer()
1265 appendTextBuffer(buffer, msg, isFault = isFault)
1266 logView.scroll_mark_onscreen(buffer.get_insert())
1267
1268 def _quit(self, what = None, force = False):
1269 """Quit from the application."""
1270 if force:
1271 result=RESPONSETYPE_YES
1272 else:
1273 dialog = gtk.MessageDialog(parent = self._mainWindow,
1274 type = MESSAGETYPE_QUESTION,
1275 message_format = xstr("quit_question"))
1276
1277 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
1278 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
1279
1280 dialog.set_title(WINDOW_TITLE_BASE)
1281 result = dialog.run()
1282 dialog.hide()
1283
1284 if result==RESPONSETYPE_YES:
1285 self._statusIcon.destroy()
1286 return gtk.main_quit()
1287
1288 def _notebookPageSwitch(self, notebook, page, page_num):
1289 """Called when the current page of the notebook has changed."""
1290 if page_num==0:
1291 gobject.idle_add(self._wizard.grabDefault)
1292 else:
1293 self._mainWindow.set_default(None)
1294
1295 def loginSuccessful(self):
1296 """Called when the login is successful."""
1297 self._flightsMenuItem.set_sensitive(True)
1298 self._timetableMenuItem.set_sensitive(True)
1299
1300 def isWizardActive(self):
1301 """Determine if the flight wizard is active."""
1302 return self._notebook.get_current_page()==0
1303
1304 def showTimetable(self, menuItem = None):
1305 """Callback for showing the timetable."""
1306 if self._timetableWindow.hasFlightPairs:
1307 self._timetableWindow.show_all()
1308 else:
1309 date = datetime.date.today()
1310 self._timetableWindow.setTypes(self.loginResult.types)
1311 self._timetableWindow.setDate(date)
1312 self.updateTimeTable(date)
1313 self.beginBusy(xstr("timetable_query_busy"))
1314
1315 def updateTimeTable(self, date):
1316 """Update the time table for the given date."""
1317 self.beginBusy(xstr("timetable_query_busy"))
1318 self._timetableWindow.set_sensitive(False)
1319 window = self._timetableWindow.get_window()
1320 if window is not None:
1321 window.set_cursor(self._busyCursor)
1322 self.webHandler.getTimetable(self._timetableCallback, date,
1323 self.loginResult.types)
1324
1325 def _timetableCallback(self, returned, result):
1326 """Called when the timetable has been received."""
1327 gobject.idle_add(self._handleTimetable, returned, result)
1328
1329 def _handleTimetable(self, returned, result):
1330 """Handle the result of the query for the timetable."""
1331 self.endBusy()
1332 window = self._timetableWindow.get_window()
1333 if window is not None:
1334 window.set_cursor(None)
1335 self._timetableWindow.set_sensitive(True)
1336 if returned:
1337 self._timetableWindow.setFlightPairs(result.flightPairs)
1338 self._timetableWindow.show_all()
1339 else:
1340 dialog = gtk.MessageDialog(parent = self.mainWindow,
1341 type = MESSAGETYPE_ERROR,
1342 message_format = xstr("timetable_failed"))
1343 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1344 dialog.set_title(WINDOW_TITLE_BASE)
1345 dialog.run()
1346 dialog.hide()
1347 self._timetableWindow.clear()
1348
1349 def showFlights(self, menuItem):
1350 """Callback for showing the flight list."""
1351 if self._flightsWindow.hasFlights:
1352 self._flightsWindow.show_all()
1353 else:
1354 self.beginBusy(xstr("acceptedflt_query_busy"))
1355 self.webHandler.getAcceptedFlights(self._acceptedFlightsCallback)
1356
1357 def _acceptedFlightsCallback(self, returned, result):
1358 """Called when the accepted flights have been received."""
1359 gobject.idle_add(self._handleAcceptedFlights, returned, result)
1360
1361 def _handleAcceptedFlights(self, returned, result):
1362 """Handle the result of the query for accepted flights."""
1363 self.endBusy()
1364 if returned:
1365 self._flightsWindow.clear()
1366 for flight in result.flights:
1367 self._flightsWindow.addFlight(flight)
1368 self._flightsWindow.show_all()
1369 else:
1370 dialog = gtk.MessageDialog(parent = self.mainWindow,
1371 type = MESSAGETYPE_ERROR,
1372 message_format = xstr("acceptedflt_failed"))
1373 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1374 dialog.set_title(WINDOW_TITLE_BASE)
1375 dialog.run()
1376 dialog.hide()
1377
1378 def _hideTimetableWindow(self, window, event):
1379 """Hide the window of the timetable."""
1380 self._timetableWindow.hide()
1381 return True
1382
1383 def _hideFlightsWindow(self, window, event):
1384 """Hide the window of the accepted flights."""
1385 self._flightsWindow.hide()
1386 return True
1387
1388 def _editChecklist(self, menuItem):
1389 """Callback for editing the checklists."""
1390 self._checklistEditor.run()
1391
1392 def _editApproachCallouts(self, menuItem):
1393 """Callback for editing the approach callouts."""
1394 self._approachCalloutsEditor.run()
1395
1396 def _editPreferences(self, menuItem):
1397 """Callback for editing the preferences."""
1398 self._clearHotkeys()
1399 self._preferences.run(self.config)
1400 self._setupTimeSync()
1401 self._listenHotkeys()
1402
1403 def _reportBug(self, menuItem):
1404 """Callback for reporting a bug."""
1405 self._bugReportDialog.run()
1406
1407 def _setupTimeSync(self):
1408 """Enable or disable the simulator time synchronization based on the
1409 configuration."""
1410 simulator = self._simulator
1411 if simulator is not None:
1412 if self.config.syncFSTime:
1413 simulator.enableTimeSync()
1414 else:
1415 simulator.disableTimeSync()
1416
1417 def viewPIREP(self, pirep):
1418 """Display the PIREP viewer window with the given PIREP."""
1419 self._pirepViewer.setPIREP(pirep)
1420 self._pirepViewer.show_all()
1421 self._pirepViewer.run()
1422 self._pirepViewer.hide()
1423
1424 def viewMessagedPIREP(self, pirep):
1425 """Display the PIREP viewer window with the given PIREP containing
1426 messages as well."""
1427 self._messagedPIREPViewer.setPIREP(pirep)
1428 self._messagedPIREPViewer.show_all()
1429 self._messagedPIREPViewer.run()
1430 self._messagedPIREPViewer.hide()
1431
1432 def editPIREP(self, pirep):
1433 """Display the PIREP editor window and allow editing the PIREP."""
1434 self._pirepEditor.setPIREP(pirep)
1435 self._pirepEditor.show_all()
1436 if self._pirepEditor.run()==RESPONSETYPE_OK:
1437 self.beginBusy(xstr("pirepEdit_save_busy"))
1438 self.webHandler.sendPIREP(self._pirepUpdatedCallback, pirep,
1439 update = True)
1440 else:
1441 self._pirepEditor.hide()
1442
1443 def _pirepUpdatedCallback(self, returned, result):
1444 """Callback for the PIREP updating result."""
1445 gobject.idle_add(self._handlePIREPUpdated, returned, result)
1446
1447 def _handlePIREPUpdated(self, returned, result):
1448 """Callback for the PIREP updating result."""
1449 self.endBusy()
1450 secondaryMarkup = None
1451 type = MESSAGETYPE_ERROR
1452 if returned:
1453 if result.success:
1454 type = None
1455 elif result.alreadyFlown:
1456 messageFormat = xstr("sendPIREP_already")
1457 secondaryMarkup = xstr("sendPIREP_already_sec")
1458 elif result.notAvailable:
1459 messageFormat = xstr("sendPIREP_notavail")
1460 else:
1461 messageFormat = xstr("sendPIREP_unknown")
1462 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1463 else:
1464 print "PIREP sending failed", result
1465 messageFormat = xstr("sendPIREP_failed")
1466 secondaryMarkup = xstr("sendPIREP_failed_sec")
1467
1468 if type is not None:
1469 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1470 type = type, message_format = messageFormat)
1471 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1472 dialog.set_title(WINDOW_TITLE_BASE)
1473 if secondaryMarkup is not None:
1474 dialog.format_secondary_markup(secondaryMarkup)
1475
1476 dialog.run()
1477 dialog.hide()
1478
1479 self._pirepEditor.hide()
1480
1481 def _loadPIREP(self, menuItem):
1482 """Load a PIREP for sending."""
1483 dialog = self._getLoadPirepDialog()
1484
1485 if self._lastLoadedPIREP:
1486 dialog.set_current_folder(os.path.dirname(self._lastLoadedPIREP))
1487 else:
1488 pirepDirectory = self.config.pirepDirectory
1489 if pirepDirectory is not None:
1490 dialog.set_current_folder(pirepDirectory)
1491
1492 result = dialog.run()
1493 dialog.hide()
1494
1495 if result==RESPONSETYPE_OK:
1496 self._lastLoadedPIREP = text2unicode(dialog.get_filename())
1497
1498 pirep = PIREP.load(self._lastLoadedPIREP)
1499 if pirep is None:
1500 dialog = gtk.MessageDialog(parent = self._mainWindow,
1501 type = MESSAGETYPE_ERROR,
1502 message_format = xstr("loadPIREP_failed"))
1503 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1504 dialog.set_title(WINDOW_TITLE_BASE)
1505 dialog.format_secondary_markup(xstr("loadPIREP_failed_sec"))
1506 dialog.run()
1507 dialog.hide()
1508 else:
1509 dialog = self._getSendLoadedDialog(pirep)
1510 dialog.show_all()
1511 while True:
1512 result = dialog.run()
1513
1514 if result==RESPONSETYPE_OK:
1515 self.sendPIREP(pirep)
1516 elif result==1:
1517 self.viewPIREP(pirep)
1518 else:
1519 break
1520
1521 dialog.hide()
1522
1523 def _getLoadPirepDialog(self):
1524 """Get the PIREP loading file chooser dialog.
1525
1526 If it is not created yet, it will be created."""
1527 if self._loadPIREPDialog is None:
1528 dialog = gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1529 xstr("loadPIREP_browser_title"),
1530 action = FILE_CHOOSER_ACTION_OPEN,
1531 buttons = (gtk.STOCK_CANCEL,
1532 RESPONSETYPE_CANCEL,
1533 gtk.STOCK_OK, RESPONSETYPE_OK),
1534 parent = self._mainWindow)
1535 dialog.set_modal(True)
1536
1537
1538 filter = gtk.FileFilter()
1539 filter.set_name(xstr("file_filter_pireps"))
1540 filter.add_pattern("*.pirep")
1541 dialog.add_filter(filter)
1542
1543 filter = gtk.FileFilter()
1544 filter.set_name(xstr("file_filter_all"))
1545 filter.add_pattern("*.*")
1546 dialog.add_filter(filter)
1547
1548 self._loadPIREPDialog = dialog
1549
1550 return self._loadPIREPDialog
1551
1552 def _getSendLoadedDialog(self, pirep):
1553 """Get a dialog displaying the main information of the flight from the
1554 PIREP and providing Cancel and Send buttons."""
1555 dialog = gtk.Dialog(title = WINDOW_TITLE_BASE + " - " +
1556 xstr("loadPIREP_send_title"),
1557 parent = self._mainWindow,
1558 flags = DIALOG_MODAL)
1559
1560 contentArea = dialog.get_content_area()
1561
1562 label = gtk.Label(xstr("loadPIREP_send_help"))
1563 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1564 xscale = 0.0, yscale = 0.0)
1565 alignment.set_padding(padding_top = 16, padding_bottom = 0,
1566 padding_left = 48, padding_right = 48)
1567 alignment.add(label)
1568 contentArea.pack_start(alignment, False, False, 8)
1569
1570 table = gtk.Table(5, 2)
1571 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
1572 xscale = 0.0, yscale = 0.0)
1573 tableAlignment.set_padding(padding_top = 0, padding_bottom = 32,
1574 padding_left = 48, padding_right = 48)
1575 table.set_row_spacings(4)
1576 table.set_col_spacings(16)
1577 tableAlignment.add(table)
1578 contentArea.pack_start(tableAlignment, True, True, 8)
1579
1580 bookedFlight = pirep.bookedFlight
1581
1582 label = gtk.Label("<b>" + xstr("loadPIREP_send_flightno") + "</b>")
1583 label.set_use_markup(True)
1584 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1585 xscale = 0.0, yscale = 0.0)
1586 labelAlignment.add(label)
1587 table.attach(labelAlignment, 0, 1, 0, 1)
1588
1589 label = gtk.Label(bookedFlight.callsign)
1590 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1591 xscale = 0.0, yscale = 0.0)
1592 labelAlignment.add(label)
1593 table.attach(labelAlignment, 1, 2, 0, 1)
1594
1595 label = gtk.Label("<b>" + xstr("loadPIREP_send_date") + "</b>")
1596 label.set_use_markup(True)
1597 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1598 xscale = 0.0, yscale = 0.0)
1599 labelAlignment.add(label)
1600 table.attach(labelAlignment, 0, 1, 1, 2)
1601
1602 label = gtk.Label(str(bookedFlight.departureTime.date()))
1603 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1604 xscale = 0.0, yscale = 0.0)
1605 labelAlignment.add(label)
1606 table.attach(labelAlignment, 1, 2, 1, 2)
1607
1608 label = gtk.Label("<b>" + xstr("loadPIREP_send_from") + "</b>")
1609 label.set_use_markup(True)
1610 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1611 xscale = 0.0, yscale = 0.0)
1612 labelAlignment.add(label)
1613 table.attach(labelAlignment, 0, 1, 2, 3)
1614
1615 label = gtk.Label(bookedFlight.departureICAO)
1616 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1617 xscale = 0.0, yscale = 0.0)
1618 labelAlignment.add(label)
1619 table.attach(labelAlignment, 1, 2, 2, 3)
1620
1621 label = gtk.Label("<b>" + xstr("loadPIREP_send_to") + "</b>")
1622 label.set_use_markup(True)
1623 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1624 xscale = 0.0, yscale = 0.0)
1625 labelAlignment.add(label)
1626 table.attach(labelAlignment, 0, 1, 3, 4)
1627
1628 label = gtk.Label(bookedFlight.arrivalICAO)
1629 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1630 xscale = 0.0, yscale = 0.0)
1631 labelAlignment.add(label)
1632 table.attach(labelAlignment, 1, 2, 3, 4)
1633
1634 label = gtk.Label("<b>" + xstr("loadPIREP_send_rating") + "</b>")
1635 label.set_use_markup(True)
1636 labelAlignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
1637 xscale = 0.0, yscale = 0.0)
1638 labelAlignment.add(label)
1639 table.attach(labelAlignment, 0, 1, 4, 5)
1640
1641 rating = pirep.rating
1642 label = gtk.Label()
1643 if rating<0:
1644 label.set_markup('<b><span foreground="red">NO GO</span></b>')
1645 else:
1646 label.set_text("%.1f %%" % (rating,))
1647
1648 labelAlignment = gtk.Alignment(xalign = 0.0, yalign = 0.5,
1649 xscale = 0.0, yscale = 0.0)
1650 labelAlignment.add(label)
1651 table.attach(labelAlignment, 1, 2, 4, 5)
1652
1653 dialog.add_button(xstr("button_cancel"), RESPONSETYPE_REJECT)
1654 dialog.add_button(xstr("viewPIREP"), 1)
1655 dialog.add_button(xstr("sendPIREP"), RESPONSETYPE_OK)
1656
1657 return dialog
1658
1659 def sendPIREP(self, pirep, callback = None):
1660 """Send the given PIREP."""
1661 self.beginBusy(xstr("sendPIREP_busy"))
1662 self._sendPIREPCallback = callback
1663 self.webHandler.sendPIREP(self._pirepSentCallback, pirep)
1664
1665 def _pirepSentCallback(self, returned, result):
1666 """Callback for the PIREP sending result."""
1667 gobject.idle_add(self._handlePIREPSent, returned, result)
1668
1669 def _handlePIREPSent(self, returned, result):
1670 """Callback for the PIREP sending result."""
1671 self.endBusy()
1672 secondaryMarkup = None
1673 type = MESSAGETYPE_ERROR
1674 if returned:
1675 if result.success:
1676 type = MESSAGETYPE_INFO
1677 messageFormat = xstr("sendPIREP_success")
1678 secondaryMarkup = xstr("sendPIREP_success_sec")
1679 elif result.alreadyFlown:
1680 messageFormat = xstr("sendPIREP_already")
1681 secondaryMarkup = xstr("sendPIREP_already_sec")
1682 elif result.notAvailable:
1683 messageFormat = xstr("sendPIREP_notavail")
1684 else:
1685 messageFormat = xstr("sendPIREP_unknown")
1686 secondaryMarkup = xstr("sendPIREP_unknown_sec")
1687 else:
1688 print "PIREP sending failed", result
1689 messageFormat = xstr("sendPIREP_failed")
1690 secondaryMarkup = xstr("sendPIREP_failed_sec")
1691
1692 dialog = gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1693 type = type, message_format = messageFormat)
1694 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1695 dialog.set_title(WINDOW_TITLE_BASE)
1696 if secondaryMarkup is not None:
1697 dialog.format_secondary_markup(secondaryMarkup)
1698
1699 dialog.run()
1700 dialog.hide()
1701
1702 callback = self._sendPIREPCallback
1703 self._sendPIREPCallback = None
1704 if callback is not None:
1705 callback(returned, result)
1706
1707 def sendBugReport(self, summary, description, email, callback = None):
1708 """Send the bug report with the given data."""
1709 description += "\n\n" + ("=" * 40)
1710 description += "\n\nThe contents of the log:\n\n"
1711
1712 for (timestampString, text) in self._logger.lines:
1713 description += unicode(formatFlightLogLine(timestampString, text))
1714
1715 description += "\n\n" + ("=" * 40)
1716 description += "\n\nThe contents of the debug log:\n\n"
1717
1718 buffer = self._debugLogView.get_buffer()
1719 description += buffer.get_text(buffer.get_start_iter(),
1720 buffer.get_end_iter(), True)
1721
1722 self.beginBusy(xstr("sendBugReport_busy"))
1723 self._sendBugReportCallback = callback
1724 self.webHandler.sendBugReport(self._bugReportSentCallback,
1725 summary, description, email)
1726
1727 def _cefInitialized(self):
1728 """Called when CEF has been initialized."""
1729 self._acars.start()
1730 cef.initializeSimBrief()
1731
1732 def _bugReportSentCallback(self, returned, result):
1733 """Callback function for the bug report sending result."""
1734 gobject.idle_add(self._handleBugReportSent, returned, result)
1735
1736 def _handleBugReportSent(self, returned, result):
1737 """Callback for the bug report sending result."""
1738 self.endBusy()
1739 secondaryMarkup = None
1740 type = MESSAGETYPE_ERROR
1741 if returned:
1742 if result.success:
1743 type = MESSAGETYPE_INFO
1744 messageFormat = xstr("sendBugReport_success") % (result.ticketID,)
1745 secondaryMarkup = xstr("sendBugReport_success_sec")
1746 else:
1747 messageFormat = xstr("sendBugReport_error")
1748 secondaryMarkup = xstr("sendBugReport_siteerror_sec")
1749 else:
1750 messageFormat = xstr("sendBugReport_error")
1751 secondaryMarkup = xstr("sendBugReport_error_sec")
1752
1753 dialog = gtk.MessageDialog(parent = self._wizard.gui._bugReportDialog,
1754 type = type, message_format = messageFormat)
1755 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
1756 dialog.set_title(WINDOW_TITLE_BASE)
1757 if secondaryMarkup is not None:
1758 dialog.format_secondary_markup(secondaryMarkup)
1759
1760 dialog.run()
1761 dialog.hide()
1762
1763 callback = self._sendBugReportCallback
1764 self._sendBugReportCallback = None
1765 if callback is not None:
1766 callback(returned, result)
1767
1768 def _listenHotkeys(self):
1769 """Setup the hotkeys based on the configuration."""
1770 if self._hotkeySetID is None and self._simulator is not None:
1771 self._pilotHotkeyIndex = None
1772 self._checklistHotkeyIndex = None
1773
1774 hotkeys = []
1775
1776 config = self.config
1777 if config.enableSounds and config.pilotControlsSounds:
1778 self._pilotHotkeyIndex = len(hotkeys)
1779 hotkeys.append(config.pilotHotkey)
1780
1781 if config.enableChecklists:
1782 self._checklistHotkeyIndex = len(hotkeys)
1783 hotkeys.append(config.checklistHotkey)
1784
1785 if hotkeys:
1786 self._hotkeySetID = \
1787 self._simulator.listenHotkeys(hotkeys, self._handleHotkeys)
1788
1789 def _clearHotkeys(self):
1790 """Clear the hotkeys."""
1791 if self._hotkeySetID is not None:
1792 self._hotkeySetID=None
1793 self._simulator.clearHotkeys()
1794
1795 def _handleHotkeys(self, id, hotkeys):
1796 """Handle the hotkeys."""
1797 if id==self._hotkeySetID:
1798 for index in hotkeys:
1799 if index==self._pilotHotkeyIndex:
1800 print "gui.GUI._handleHotkeys: pilot hotkey pressed"
1801 self._flight.pilotHotkeyPressed()
1802 elif index==self._checklistHotkeyIndex:
1803 print "gui.GUI._handleHotkeys: checklist hotkey pressed"
1804 self._flight.checklistHotkeyPressed()
1805 else:
1806 print "gui.GUI._handleHotkeys: unhandled hotkey index:", index
1807
1808 def _showManual(self, menuitem):
1809 """Show the user's manual."""
1810 webbrowser.open(url ="file://" +
1811 os.path.join(self._programDirectory, "doc", "manual",
1812 getLanguage(), "index.html"),
1813 new = 1)
1814
1815 def _showAbout(self, menuitem):
1816 """Show the about dialog."""
1817 dialog = self._getAboutDialog()
1818 dialog.show_all()
1819 dialog.run()
1820 dialog.hide()
1821
1822 def _getAboutDialog(self):
1823 """Get the about dialog.
1824
1825 If it does not exist yet, it will be created."""
1826 if self._aboutDialog is None:
1827 dialog = gtk.AboutDialog()
1828 dialog.set_transient_for(self._mainWindow)
1829 dialog.set_modal(True)
1830
1831 logoPath = os.path.join(self._programDirectory, "logo.png")
1832 logo = pixbuf_new_from_file(logoPath)
1833 dialog.set_logo(logo)
1834
1835 dialog.set_program_name(PROGRAM_NAME)
1836 dialog.set_version(const.VERSION)
1837 dialog.set_copyright("(c) 2012 by István Váradi")
1838 dialog.set_website("http://mlx.varadiistvan.hu")
1839 dialog.set_website_label(xstr("about_website"))
1840
1841 isHungarian = getLanguage()=="hu"
1842 authors = []
1843 for (familyName, firstName, role) in GUI._authors:
1844 author = "%s %s" % \
1845 (familyName if isHungarian else firstName,
1846 firstName if isHungarian else familyName)
1847 role = xstr("about_role_" + role)
1848 authors.append(author + " (" + role + ")")
1849 dialog.set_authors(authors)
1850
1851 dialog.set_license(xstr("about_license"))
1852
1853 if not pygobject:
1854 gtk.about_dialog_set_url_hook(self._showAboutURL, None)
1855
1856 self._aboutDialog = dialog
1857
1858 return self._aboutDialog
1859
1860 def _showAboutURL(self, dialog, link, user_data):
1861 """Show the about URL."""
1862 webbrowser.open(url = link, new = 1)
1863
1864 def _setTakeoffAntiIceOn(self, value):
1865 """Set the anti-ice on indicator."""
1866 self._wizard.takeoffAntiIceOn = value
1867
1868 def _setLandingAntiIceOn(self, value):
1869 """Set the anti-ice on indicator."""
1870 self._wizard.landingAntiIceOn = value
1871
1872 def _getCredentialsCallback(self):
1873 """Called when the web handler asks for the credentials."""
1874 # FIXME: this is almost the same as
1875 # SimBriefSetupPage._getCredentialsCallback
1876 with self._credentialsCondition:
1877 self._credentialsAvailable = False
1878
1879 gobject.idle_add(self._getCredentials)
1880
1881 while not self._credentialsAvailable:
1882 self._credentialsCondition.wait()
1883
1884 return (self._credentialsUserName, self._credentialsPassword)
1885
1886 def _getCredentials(self):
1887 """Get the credentials."""
1888 # FIXME: this is almost the same as
1889 # SimBriefSetupPage._getCredentials
1890 with self._credentialsCondition:
1891 config = self.config
1892
1893 dialog = CredentialsDialog(self, config.pilotID, config.password,
1894 xstr("login_title"),
1895 xstr("button_cancel"),
1896 xstr("button_ok"),
1897 xstr("label_pilotID"),
1898 xstr("login_pilotID_tooltip"),
1899 xstr("label_password"),
1900 xstr("login_password_tooltip"),
1901 xstr("login_info"),
1902 config.rememberPassword,
1903 xstr("remember_password"),
1904 xstr("login_remember_tooltip"))
1905 response = dialog.run()
1906
1907 if response==RESPONSETYPE_OK:
1908 self._credentialsUserName = dialog.userName
1909 self._credentialsPassword = dialog.password
1910 rememberPassword = dialog.rememberPassword
1911
1912 config.pilotID = self._credentialsUserName
1913
1914 config.password = \
1915 self._credentialsPassword if rememberPassword else ""
1916 config.rememberPassword = rememberPassword
1917
1918 config.save()
1919 else:
1920 self._credentialsUserName = None
1921 self._credentialsPassword = None
1922
1923 self._credentialsAvailable = True
1924 self._credentialsCondition.notify()
1925
1926 def _updateDone(self):
1927 """Called when the update is done.
1928
1929 It checks if we already know the PID, and if not, asks the user whether
1930 to register."""
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.