source: src/mlx/gui/flight.py@ 1077:e230d26ca700

python3
Last change on this file since 1077:e230d26ca700 was 1076:5a7bb93c337d, checked in by István Váradi <ivaradi@…>, 21 months ago

SimBrief handling is reworked to use the user's browser (re #362)

File size: 243.7 KB
Line 
1# -*- encoding: utf-8 -*-
2
3from mlx.gui.common import *
4import mlx.gui.cef as cef
5from mlx.gui.flightlist import ColumnDescriptor, FlightList, PendingFlightsWindow
6
7import mlx.const as const
8import mlx.fs as fs
9import mlx.acft as acft
10from mlx.flight import Flight
11from mlx.checks import PayloadChecker
12from mlx.gates import lhbpGates
13import mlx.util as util
14from mlx.pirep import PIREP
15from mlx.i18n import xstr, getLanguage
16from mlx.sound import startSound
17from mlx.rpc import BookedFlight
18import mlx.web as web
19
20import datetime
21import time
22import os
23import tempfile
24import threading
25import re
26import webbrowser
27import urllib.request, urllib.error, urllib.parse
28from lxml import etree
29from io import StringIO
30import lxml.html
31import certifi
32
33#-----------------------------------------------------------------------------
34
35## @package mlx.gui.flight
36#
37# The flight "wizard".
38#
39# This module implements the main tab of the application, the flight
40# wizard. The wizard consists of \ref Page "pages", that come one after the
41# other. As some pages might be skipped, the pages dynamically store the index
42# of the previous page so that going back to it is simpler. The \ref
43# Page.activate "activate" function is called before a page is first shown
44# during a flight. This function should initialize the page's controls and fill
45# it with initial data. When a page is left for the first time, its \ref
46# Page.finalize "finalize" function is called. It should set those controls
47# insensitive, that will not be available if the user comes back to this page.
48#
49# Each page has a title at the top displayed in inverted colors and a big
50# font. There is a help text below it centered, that shortly describes what is
51# expected on the page. There can be two help texts: one shown when the page is
52# first displayed during a flight, another shown when the user goes back to the
53# page. The main content area is below this, also centered. Finally, there are
54# some buttons at the bottom on the right. As some buttons occur very
55# frequently, there are functions to add them (\ref Page.addCancelFlightButton
56# "addCancelFlightButton", \ref Page.addPreviousButton "addPreviousButton" and
57# \ref Page.addNextButton "addNextButton".
58#
59# The \ref Wizard class is the main class to collect the pages. It also stores
60# some data passed from one page to another and provides properties to access
61# data items set via the wizard pages.
62
63#-----------------------------------------------------------------------------
64
65comboModel = Gtk.ListStore(GObject.TYPE_STRING)
66comboModel.append(("N/A",))
67comboModel.append(("VECTORS",))
68
69#-----------------------------------------------------------------------------
70
71class Page(Gtk.Alignment):
72 """A page in the flight wizard."""
73 def __init__(self, wizard, id, title, help, completedHelp = None):
74 """Construct the page."""
75 super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
76 xscale = 1.0, yscale = 1.0)
77 self.set_padding(padding_top = 4, padding_bottom = 4,
78 padding_left = 12, padding_right = 12)
79
80 frame = Gtk.Frame()
81 self.add(frame)
82
83 self._vbox = Gtk.VBox()
84 self._vbox.set_homogeneous(False)
85 frame.add(self._vbox)
86
87 eventBox = Gtk.EventBox()
88
89 alignment = Gtk.Alignment(xalign = 0.0, xscale = 0.0)
90
91 titleLabel = Gtk.Label(title)
92 titleLabel.modify_font(Pango.FontDescription("bold 24"))
93 alignment.set_padding(padding_top = 4, padding_bottom = 4,
94 padding_left = 6, padding_right = 0)
95
96 alignment.add(titleLabel)
97 eventBox.add(alignment)
98
99 self._vbox.pack_start(eventBox, False, False, 0)
100
101 self._titleEventBox = eventBox
102 self._titleLabel = titleLabel
103
104 mainBox = Gtk.VBox()
105
106 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
107 xscale = 1.0, yscale = 1.0)
108 alignment.set_padding(padding_top = 16, padding_bottom = 16,
109 padding_left = 16, padding_right = 16)
110 alignment.add(mainBox)
111 self._vbox.pack_start(alignment, True, True, 0)
112
113 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.0,
114 xscale = 0.0, yscale = 0.0)
115 alignment.set_padding(padding_top = 0, padding_bottom = 16,
116 padding_left = 0, padding_right = 0)
117
118 self._help = help
119 self._completedHelp = completedHelp
120
121 if self._completedHelp is None or \
122 len(help.splitlines())>=len(completedHelp.splitlines()):
123 longerHelp = help
124 else:
125 longerHelp = completedHelp
126
127 self._helpLabel = Gtk.Label(longerHelp)
128 # FIXME: should be a constant in common
129 self._helpLabel.set_justify(Gtk.Justification.CENTER)
130 self._helpLabel.set_use_markup(True)
131 alignment.add(self._helpLabel)
132 mainBox.pack_start(alignment, False, False, 0)
133
134 self._mainAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
135 xscale = 1.0, yscale = 1.0)
136 mainBox.pack_start(self._mainAlignment, True, True, 0)
137
138 buttonAlignment = Gtk.Alignment(xalign = 1.0, xscale=0.0, yscale = 0.0)
139 buttonAlignment.set_padding(padding_top = 4, padding_bottom = 10,
140 padding_left = 16, padding_right = 16)
141
142 self._buttonBox = Gtk.HBox()
143 self._buttonBox.set_homogeneous(False)
144 self._defaultButton = None
145 buttonAlignment.add(self._buttonBox)
146
147 self._vbox.pack_start(buttonAlignment, False, False, 0)
148
149 self._wizard = wizard
150 self._id = id
151 self._nextPageID = None
152
153 self._cancelFlightButton = None
154
155 self._completed = False
156 self._fromPage = None
157
158 @property
159 def id(self):
160 """Get the identifier of the page."""
161 return self._id
162
163 @property
164 def nextPageID(self):
165 """Get the identifier of the next page, if set."""
166 return self._nextPageID
167
168 @nextPageID.setter
169 def nextPageID(self, nextPageID):
170 """Set the identifier of the next page."""
171 self._nextPageID = nextPageID
172
173 def setMainWidget(self, widget):
174 """Set the given widget as the main one."""
175 self._mainAlignment.add(widget)
176
177 def addButton(self, label, default = False, sensitive = True,
178 tooltip = None, clicked = None, padding = 4,
179 clickedArg = None):
180 """Add a button with the given label.
181
182 Return the button object created."""
183 button = Gtk.Button(label)
184 self._buttonBox.pack_start(button, False, False, padding)
185 button.set_use_underline(True)
186 if default:
187 button.set_can_default(True)
188 self._defaultButton = button
189 button.set_sensitive(sensitive)
190 if tooltip is not None:
191 button.set_tooltip_text(tooltip)
192 if clicked is not None:
193 if clickedArg is None:
194 button.connect("clicked", clicked)
195 else:
196 button.connect("clicked", clicked, clickedArg)
197 return button
198
199 def addCancelFlightButton(self):
200 """Add the 'Cancel flight' button to the page."""
201 self._cancelFlightButton = \
202 self.addButton(xstr("button_cancelFlight"),
203 sensitive = True,
204 tooltip = xstr("button_cancelFlight_tooltip"),
205 clicked = self._cancelFlight,
206 padding = 16)
207 return self._cancelFlightButton
208
209 def addPreviousButton(self, sensitive = True, clicked = None):
210 """Add the 'Next' button to the page."""
211 return self.addButton(xstr("button_previous"),
212 sensitive = sensitive,
213 tooltip = xstr("button_previous_tooltip"),
214 clicked = clicked)
215
216 def addNextButton(self, default = True, sensitive = True,
217 clicked = None):
218 """Add the 'Next' button to the page."""
219 return self.addButton(xstr("button_next"),
220 default = default,
221 sensitive = sensitive,
222 tooltip = xstr("button_next_tooltip"),
223 clicked = clicked)
224
225 def setStyle(self):
226 """Set the styles of some of the items on the page."""
227 context = self.get_style_context()
228 color = context.get_background_color(Gtk.StateFlags.SELECTED)
229 self._titleEventBox.modify_bg(0, color.to_color())
230 color = context.get_color(Gtk.StateFlags.SELECTED)
231 self._titleLabel.modify_fg(0, color.to_color())
232
233 def initialize(self):
234 """Initialize the page.
235
236 It sets up the primary help, and calls the activate() function."""
237 self._helpLabel.set_markup(self._help)
238 self._helpLabel.set_sensitive(True)
239 self.activate()
240
241 def activate(self):
242 """Called when this page becomes active.
243
244 This default implementation does nothing."""
245 pass
246
247 def prepareShow(self):
248 """Prepare the page for showing, either initially on when later
249 returned to."""
250 pass
251
252 def setHelp(self, help):
253 """Set the help string."""
254 self._help = help
255 if not self._completed:
256 self._helpLabel.set_markup(self._help)
257 self._helpLabel.set_sensitive(True)
258
259 def complete(self):
260 """Called when the page is completed.
261
262 It greys out/changes the help text and then calls finalize()."""
263 self.finalize()
264 if self._completedHelp is None:
265 self._helpLabel.set_sensitive(False)
266 else:
267 self._helpLabel.set_markup(self._completedHelp)
268 self._completed = True
269
270 def finalize(self):
271 """Called when the page is finalized."""
272 pass
273
274 def grabDefault(self):
275 """If the page has a default button, make it the default one."""
276 if self._defaultButton is not None:
277 self._defaultButton.grab_default()
278
279 def prepareHide(self):
280 """Prepare the page for hiding, either initially on when later
281 the user returned to it and then navigates away from it."""
282 pass
283
284 def reset(self):
285 """Reset the page if the wizard is reset."""
286 self._completed = False
287 self._fromPage = None
288 if self._cancelFlightButton is not None:
289 self._cancelFlightButton.set_sensitive(True)
290
291 def goBack(self):
292 """Go to the page we were invoked from."""
293 assert self._fromPage is not None
294
295 self._wizard.setCurrentPage(self._fromPage, finalize = False)
296
297 def flightEnded(self):
298 """Called when the flight has ended.
299
300 This default implementation disables the cancel flight button."""
301 if self._cancelFlightButton is not None:
302 self._cancelFlightButton.set_sensitive(False)
303
304 def _cancelFlight(self, button):
305 """Called when the Cancel flight button is clicked."""
306 self._wizard.gui.cancelFlight()
307
308#-----------------------------------------------------------------------------
309
310class LoginPage(Page):
311 """The login page."""
312 def __init__(self, wizard):
313 """Construct the login page."""
314 super(LoginPage, self).__init__(wizard, "login",
315 xstr("login"), xstr("loginHelp"))
316
317 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
318 xscale = 0.0, yscale = 0.0)
319
320 table = Gtk.Table(3, 2)
321 table.set_row_spacings(4)
322 table.set_col_spacings(32)
323 alignment.add(table)
324 self.setMainWidget(alignment)
325
326 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
327 xscale = 0.0, yscale = 0.0)
328 label = Gtk.Label(xstr("label_pilotID"))
329 label.set_use_underline(True)
330 labelAlignment.add(label)
331 table.attach(labelAlignment, 0, 1, 0, 1)
332
333 self._pilotID = Gtk.Entry()
334 self._pilotID.connect("changed", self._pilotIDChanged)
335 self._pilotID.set_tooltip_text(xstr("login_pilotID_tooltip"))
336 table.attach(self._pilotID, 1, 2, 0, 1)
337 label.set_mnemonic_widget(self._pilotID)
338
339 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
340 xscale = 0.0, yscale = 0.0)
341 label = Gtk.Label(xstr("label_password"))
342 label.set_use_underline(True)
343 labelAlignment.add(label)
344 table.attach(labelAlignment, 0, 1, 1, 2)
345
346 self._password = Gtk.Entry()
347 self._password.set_visibility(False)
348 self._password.connect("changed", self._setControls)
349 self._password.set_tooltip_text(xstr("login_password_tooltip"))
350 table.attach(self._password, 1, 2, 1, 2)
351 label.set_mnemonic_widget(self._password)
352
353 self._rememberButton = Gtk.CheckButton(xstr("remember_password"))
354 self._rememberButton.set_use_underline(True)
355 self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
356 table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
357
358 self.addButton(xstr("button_login_register"),
359 clicked = self._registerClicked,
360 tooltip = xstr("button_login_register_tooltip"))
361
362 self.addButton(xstr("button_offline"),
363 clicked = self._offlineClicked,
364 tooltip = xstr("button_offline_tooltip"))
365
366 self._loginButton = self.addButton(xstr("button_login"), default = True)
367 self._loginButton.connect("clicked", self._loginClicked)
368 self._loginButton.set_tooltip_text(xstr("login_button_tooltip"))
369
370
371 @property
372 def pilotID(self):
373 """Get the pilot ID, if given."""
374 return self._pilotID.get_text()
375
376 def activate(self):
377 """Activate the page."""
378 config = self._wizard.gui.config
379 self._pilotID.set_text(config.pilotID)
380 self._password.set_text(config.password)
381 self._rememberButton.set_active(config.rememberPassword)
382 self._setControls(None)
383
384 def _pilotIDChanged(self, entry):
385 """Called when the pilot ID has changed.
386
387 It sets the text to upper-case and calls _setControls to update other
388 stuff."""
389 entry.set_text(entry.get_text().upper())
390 self._setControls(entry)
391
392 def _setControls(self, entry = None):
393 """Set the sensitivity of the various controls.
394
395 The login button is sensitive only if both the pilot ID and the
396 password fields contain values.
397
398 The password field is sensitive only, if the entrance exam checkbox is
399 not selected.
400
401 The remember password checkbox is sensitive only, if the password field
402 contains text.
403
404 The entrance exam checkbox is sensitive only, if the pilot ID is not
405 empty."""
406 pilotID = self._pilotID.get_text()
407 password = self._password.get_text()
408 self._rememberButton.set_sensitive(password!="")
409 self._loginButton.set_sensitive(pilotID!="" and password!="")
410
411 def _registerClicked(self, button):
412 """Called when the Register button was clicked."""
413 self._wizard.jumpPage("register")
414
415 def _offlineClicked(self, button):
416 """Called when the offline button was clicked."""
417 print("mlx.flight.LoginPage: offline flight selected")
418 self._wizard.nextPage()
419
420 def _loginClicked(self, button):
421 """Called when the login button was clicked."""
422 print("mlx.flight.LoginPage: logging in")
423 self._wizard.login(self._handleLoginResult,
424 self._pilotID.get_text(),
425 self._password.get_text())
426
427 def _handleLoginResult(self, returned, result):
428 """Handle the login result."""
429 self._loginButton.set_sensitive(True)
430 if returned and result.loggedIn:
431 config = self._wizard.gui.config
432
433 config.pilotID = self._pilotID.get_text()
434
435 rememberPassword = self._rememberButton.get_active()
436 config.password = result.password if rememberPassword else ""
437
438 config.rememberPassword = rememberPassword
439
440 config.save()
441 if result.rank=="STU":
442 self._wizard.jumpPage("student")
443 else:
444 self._wizard.nextPage()
445
446#-----------------------------------------------------------------------------
447
448class FlightSelectionPage(Page):
449 """The page to select the flight."""
450 def __init__(self, wizard):
451 """Construct the flight selection page."""
452 help = xstr("flightsel_help")
453 completedHelp = xstr("flightsel_chelp")
454 super(FlightSelectionPage, self).__init__(wizard, "flightsel",
455 xstr("flightsel_title"),
456 help, completedHelp = completedHelp)
457
458 mainBox = Gtk.HBox()
459 mainBox.set_homogeneous(False)
460
461 leftVBox = Gtk.VBox()
462
463 alignment = Gtk.Alignment(xscale = 1.0)
464 alignment.set_size_request(100, 0)
465
466 leftVBox.pack_start(alignment, False, False, 0)
467
468 mainBox.pack_start(leftVBox, True, True, 0)
469
470 self._flightList = FlightList(popupMenuProducer =
471 self._createListPopupMenu,
472 widthRequest = 400)
473 self._flightList.connect("row-activated", self._rowActivated)
474 self._flightList.connect("selection-changed", self._selectionChanged)
475
476 mainBox.pack_start(self._flightList, False, False, 8)
477
478 flightButtonBox = Gtk.VBox()
479
480 alignment = Gtk.Alignment(xscale = 1.0)
481 alignment.set_size_request(100, 0)
482 flightButtonBox.pack_start(alignment, False, False, 0)
483
484 flightButtonWidthAlignment = Gtk.Alignment(xscale=1.0, yscale=0.0,
485 xalign=0.0, yalign=0.0)
486 flightButtonWidthAlignment.set_padding(padding_top = 0,
487 padding_bottom = 0,
488 padding_left = 8,
489 padding_right = 0)
490 flightButtonWidthBox = Gtk.VBox()
491
492 self._saveButton = Gtk.Button(xstr("flightsel_save"))
493 self._saveButton.set_use_underline(True)
494 self._saveButton.set_sensitive(False)
495 self._saveButton.set_tooltip_text(xstr("flightsel_save_tooltip"))
496 self._saveButton.connect("clicked", self._saveClicked)
497
498 flightButtonWidthBox.pack_start(self._saveButton, True, True, 4)
499
500 self._printButton = Gtk.Button(xstr("flightsel_print"))
501 self._printButton.set_use_underline(True)
502 self._printButton.set_sensitive(False)
503 self._printButton.set_tooltip_text(xstr("flightsel_print_tooltip"))
504 self._printButton.connect("clicked", self._printClicked)
505
506 flightButtonWidthBox.pack_start(self._printButton, True, True, 4)
507
508 self._deleteButton = Gtk.Button(xstr("flightsel_delete"))
509 self._deleteButton.set_use_underline(True)
510 self._deleteButton.set_sensitive(False)
511 self._deleteButton.set_tooltip_text(xstr("flightsel_delete_tooltip"))
512 self._deleteButton.connect("clicked", self._deleteClicked)
513
514 flightButtonWidthBox.pack_start(self._deleteButton, True, True, 4)
515
516 flightButtonWidthAlignment.add(flightButtonWidthBox)
517 flightButtonBox.pack_start(flightButtonWidthAlignment, False, False, 0)
518
519 mainBox.pack_start(flightButtonBox, True, True, 0)
520
521 self.setMainWidget(mainBox)
522
523 self._bookButton = self.addButton(xstr("flightsel_book"),
524 sensitive = True,
525 clicked = self._bookClicked,
526 tooltip = xstr("flightsel_book_tooltip"))
527
528 self._pendingButton = self.addButton(xstr("flightsel_pending"),
529 sensitive = False,
530 clicked = self._pendingClicked,
531 tooltip = xstr("flightsel_pending_tooltip"))
532
533 self._saveDialog = None
534
535 self._refreshButton = self.addButton(xstr("flightsel_refresh"),
536 sensitive = True,
537 clicked = self._refreshClicked,
538 tooltip = xstr("flightsel_refresh_tooltip"))
539
540 self._loadButton = self.addButton(xstr("flightsel_load"),
541 sensitive = True,
542 tooltip = xstr("flightsel_load_tooltip"))
543 self._loadButton.connect("clicked", self._loadButtonClicked)
544 self._loadDialog = None
545
546 self._button = self.addNextButton(sensitive = False,
547 clicked = self._forwardClicked)
548
549 self._flights = []
550
551 self._pendingFlightsWindow = PendingFlightsWindow(self._wizard)
552 self._pendingFlightsWindowShown = False
553 self._pendingFlightsWindow.connect("delete-event",
554 self._deletePendingFlightsWindow)
555
556 self._printSettings = None
557
558 def activate(self):
559 """Fill the flight list."""
560 self._setupHelp()
561 self._flightList.set_sensitive(True)
562 self._loadButton.set_sensitive(True)
563 self._refreshButton.set_sensitive(self._wizard.loggedIn)
564 self._buildFlights()
565
566 def finalize(self):
567 """Finalize the page."""
568 self._flightList.set_sensitive(False)
569 self._loadButton.set_sensitive(False)
570 self._refreshButton.set_sensitive(False)
571
572 def _setupHelp(self):
573 """Setup the help string"""
574 help = ""
575
576 if self._wizard.loggedIn:
577 numReported = len(self._wizard.loginResult.reportedFlights)
578 numRejected = len(self._wizard.loginResult.rejectedFlights)
579 if numReported==0 and numRejected==0:
580 help = xstr("flightsel_prehelp_nopending")
581 elif numReported>0 and numRejected==0:
582 help = xstr("flightsel_prehelp_rep_0rej") % (numReported,)
583 elif numReported==0 and numRejected>0:
584 help = xstr("flightsel_prehelp_0rep_rej") % (numRejected,)
585 else:
586 help = xstr("flightsel_prehelp_rep_rej") % \
587 (numReported, numRejected)
588
589 help += xstr("flightsel_help")
590
591 self.setHelp(help)
592
593 def _buildFlights(self):
594 """Rebuild the flights from the login result."""
595 self._flights = []
596 self._flightList.clear()
597 self._pendingFlightsWindow.clear()
598 loginResult = self._wizard.loginResult
599 if self._wizard.loggedIn:
600 for flight in loginResult.flights:
601 self.addFlight(flight)
602 for flight in loginResult.reportedFlights:
603 self._pendingFlightsWindow.addReportedFlight(flight)
604 for flight in loginResult.rejectedFlights:
605 self._pendingFlightsWindow.addRejectedFlight(flight)
606
607 self._updatePendingButton()
608
609 def addFlight(self, flight):
610 """Add the given file to the list of flights."""
611 self._flights.append(flight)
612 self._flightList.addFlight(flight)
613
614 def _reflyFlight(self, flight):
615 """Refly the given flight."""
616 self.addFlight(flight)
617 self._updatePending()
618
619 def _updatePending(self):
620 """Update the stuff depending on the set of pending flights."""
621 self._setupHelp()
622 self._updatePendingButton()
623
624 def _bookClicked(self, button):
625 """Called when the Book flights button is clicked."""
626 self._wizard.gui.showTimetable()
627
628 def _pendingClicked(self, button):
629 """Called when the Pending flights button is clicked."""
630 self._pendingFlightsWindow.show_all()
631 self._pendingFlightsWindowShown = True
632 self._updateNextButton()
633
634 def _deletePendingFlightsWindow(self, window, event):
635 """Called when the pending flights window is closed."""
636 self._pendingFlightsWindow.hide()
637 self._pendingFlightsWindowShown = False
638 self._updateNextButton()
639 return True
640
641 def _saveClicked(self, button):
642 """Called when the Save flight button is clicked."""
643 self._saveSelected()
644
645 def _saveSelected(self):
646 """Save the selected flight."""
647 flight = self._getSelectedFlight()
648 date = flight.departureTime.date()
649 name = "%04d-%02d-%02d %s %s-%s.vaflight" % \
650 (date.year, date.month, date.day, flight.callsign,
651 flight.departureICAO, flight.arrivalICAO)
652
653 dialog = self._getSaveDialog()
654 dialog.set_current_name(name)
655 dialog.show_all()
656 response = dialog.run()
657 dialog.hide()
658
659 if response==Gtk.ResponseType.OK:
660 fileName = dialog.get_filename()
661 print("Saving", fileName)
662 try:
663 with open(fileName, "wt") as f:
664 flight.writeIntoFile(f)
665 except Exception as e:
666 print("Failed to save flight:", util.utf2unicode(str(e)))
667 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
668 type = Gtk.MessageType.ERROR,
669 message_format =
670 xstr("flightsel_save_failed"))
671 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
672 dialog.set_title(WINDOW_TITLE_BASE)
673 secondary = xstr("flightsel_save_failed_sec")
674 dialog.format_secondary_markup(secondary)
675 dialog.run()
676 dialog.hide()
677
678 def _printClicked(self, button):
679 """Called when the Print briefing button is clicked."""
680 self._printSelected()
681
682 def _printSelected(self):
683 """Print the briefing for the selected flight."""
684 wizard = self._wizard
685 flight = self._getSelectedFlight()
686
687 printOperation = Gtk.PrintOperation()
688 if self._printSettings is not None:
689 printOperation.set_print_settings(self._printSettings)
690
691 printOperation.set_n_pages(1)
692 printOperation.set_show_progress(True)
693 printOperation.connect("draw_page", self._drawBriefing)
694
695 name = "MAVA Briefing %s %s %s" % (wizard.loginResult.pilotID,
696 flight.callsign,
697 flight.departureTime.strftime("%Y-%m-%d %H:%M"))
698 printOperation.set_job_name(name)
699 printOperation.set_export_filename(name)
700 printOperation.set_use_full_page(False)
701
702 result = printOperation.run(Gtk.PrintOperationAction.PRINT_DIALOG,
703 wizard.gui.mainWindow)
704
705 if result == Gtk.PrintOperationResult.APPLY:
706 self._printSettings = printOperation.get_print_settings()
707 elif result == Gtk.PrintOperationResult.ERROR:
708 errorDialog(xstr("flightsel_print_failed",
709 wizard.gui.mainWindow,
710 secondary = printOperation.get_error()))
711
712 def _drawBriefing(self, printOperation, context, pageNumber):
713 """Draw the briefing."""
714 wizard = self._wizard
715 loginResult = wizard.loginResult
716 flight=self._getSelectedFlight()
717
718 print("DPI", context.get_dpi_x(), context.get_dpi_y())
719
720 logoSurface = cairo.ImageSurface.create_from_png(
721 os.path.join(wizard.gui.programDirectory, "mavalogo.png"))
722
723 scale = context.get_dpi_x() / 72.0
724
725 cr = context.get_cairo_context()
726
727 cr.set_antialias(cairo.ANTIALIAS_GRAY)
728
729 pcr = context.create_pango_context()
730
731 delimiter = "*" * 70
732
733 layout = Pango.Layout(pcr)
734
735 font = Pango.FontDescription("Courier")
736 font.set_size(int(12 * Pango.SCALE))
737 font.set_weight(Pango.Weight.THIN)
738 layout.set_font_description(font)
739
740 fontBold = Pango.FontDescription("Courier")
741 fontBold.set_size(int(12 * Pango.SCALE))
742 fontBold.set_weight(Pango.Weight.BOLD)
743
744 layout.set_text(delimiter)
745 (_ink, logical) = layout.get_extents()
746 width = logical.width / Pango.SCALE
747
748 x0 = (context.get_width() - width)/2.0
749
750 logoWidth = width / 3
751 logoScale = logoSurface.get_width() / logoWidth
752
753 y = 25 * scale
754
755 source = cr.get_source()
756 logoSurface.set_device_scale(logoScale, logoScale)
757 cr.set_source_surface(logoSurface, x0, y)
758 cr.rectangle(x0, y, logoSurface.get_width() / logoScale,
759 logoSurface.get_height() / logoScale)
760 cr.fill()
761 cr.set_source(source)
762
763 y += logoSurface.get_height() / logoScale
764 y += 12 * scale
765
766 cr.move_to(x0, y)
767 cr.set_line_width(0.1 * scale)
768 PangoCairo.layout_path(cr, layout)
769 cr.stroke_preserve()
770 cr.fill()
771
772 y += logical.height / Pango.SCALE
773 y += 4 * scale
774
775 layout.set_text("F L I G H T B R I E F I N G S H E E T")
776 (_ink, logical) = layout.get_extents()
777 width = logical.width / Pango.SCALE
778
779 cr.move_to((context.get_width() - width)/2.0, y)
780 cr.set_line_width(0.1 * scale)
781 PangoCairo.layout_path(cr, layout)
782 cr.stroke_preserve()
783 cr.fill()
784
785 y += logical.height / Pango.SCALE
786 y += 4 * scale
787
788 layout.set_text(delimiter)
789 (_ink, logical) = layout.get_extents()
790 width = logical.width / Pango.SCALE
791
792 x2 = x0 + width * 0.4
793
794 cr.move_to(x0, y)
795 cr.set_line_width(0.1 * scale)
796 PangoCairo.layout_path(cr, layout)
797 cr.stroke_preserve()
798 cr.fill()
799
800 y += logical.height / Pango.SCALE
801 y += 5 * scale
802
803 layout.set_text("AIRCRAFT: ")
804 layout.set_font_description(fontBold)
805 (_ink, logical) = layout.get_extents()
806 x1 = x0 + logical.width / Pango.SCALE
807
808 layout.set_text("FLIGHT:")
809 layout.set_font_description(fontBold)
810
811 cr.move_to(x0, y)
812 cr.set_line_width(0.1 * scale)
813 PangoCairo.layout_path(cr, layout)
814 cr.stroke_preserve()
815 cr.fill()
816
817 layout.set_text("%s %s-%s" % (flight.callsign,
818 flight.departureICAO,
819 flight.arrivalICAO))
820
821 layout.set_font_description(font)
822
823 cr.move_to(x1, y)
824 cr.set_line_width(0.1 * scale)
825 PangoCairo.layout_path(cr, layout)
826 cr.stroke_preserve()
827 cr.fill()
828
829 layout.set_text("DATE:")
830 layout.set_font_description(fontBold)
831 (_ink, logical) = layout.get_extents()
832 x3 = x2 + logical.width / Pango.SCALE
833
834 cr.move_to(x2, y)
835 cr.set_line_width(0.1 * scale)
836 PangoCairo.layout_path(cr, layout)
837 cr.stroke_preserve()
838 cr.fill()
839
840 layout.set_text(" %s" % (flight.date,))
841 layout.set_font_description(font)
842
843 cr.move_to(x3, y)
844 cr.set_line_width(0.1 * scale)
845 PangoCairo.layout_path(cr, layout)
846 cr.stroke_preserve()
847 cr.fill()
848
849 layout.set_text(" %02d:%02d" % (flight.departureTime.hour,
850 flight.departureTime.minute))
851 (_ink, logical) = layout.get_extents()
852 x5 = x0 + width - logical.width / Pango.SCALE
853 cr.move_to(x5, y)
854 cr.set_line_width(0.1 * scale)
855 PangoCairo.layout_path(cr, layout)
856 cr.stroke_preserve()
857 cr.fill()
858
859 layout.set_text("ETD (UTC):")
860 layout.set_font_description(fontBold)
861 (_ink, logical) = layout.get_extents()
862 x4 = x5 - logical.width / Pango.SCALE
863 cr.move_to(x4, y)
864 cr.set_line_width(0.1 * scale)
865 PangoCairo.layout_path(cr, layout)
866 cr.stroke_preserve()
867 cr.fill()
868
869 y += logical.height / Pango.SCALE
870 y += 4 * scale
871
872 layout.set_text("AIRCRAFT:")
873 layout.set_font_description(fontBold)
874 cr.move_to(x0, y)
875 cr.set_line_width(0.1 * scale)
876 PangoCairo.layout_path(cr, layout)
877 cr.stroke_preserve()
878 cr.fill()
879
880 layout.set_text(aircraftNames[flight.aircraftType].upper())
881 layout.set_font_description(font)
882 cr.move_to(x1, y)
883 cr.set_line_width(0.1 * scale)
884 PangoCairo.layout_path(cr, layout)
885 cr.stroke_preserve()
886 cr.fill()
887
888 layout.set_text(" %02d:%02d" % (flight.arrivalTime.hour,
889 flight.arrivalTime.minute))
890 cr.move_to(x5, y)
891 cr.set_line_width(0.1 * scale)
892 PangoCairo.layout_path(cr, layout)
893 cr.stroke_preserve()
894 cr.fill()
895
896 layout.set_text("ETA (UTC):")
897 layout.set_font_description(fontBold)
898 cr.move_to(x4, y)
899 cr.set_line_width(0.1 * scale)
900 PangoCairo.layout_path(cr, layout)
901 cr.stroke_preserve()
902 cr.fill()
903
904 y += logical.height / Pango.SCALE
905 y += 4 * scale
906
907 layout.set_text("CREW:")
908 layout.set_font_description(fontBold)
909 cr.move_to(x0, y)
910 cr.set_line_width(0.1 * scale)
911 PangoCairo.layout_path(cr, layout)
912 cr.stroke_preserve()
913 cr.fill()
914
915 layout.set_text("%d + %d" % (flight.numCockpitCrew,
916 flight.numCabinCrew))
917 layout.set_font_description(font)
918 cr.move_to(x1, y)
919 cr.set_line_width(0.1 * scale)
920 PangoCairo.layout_path(cr, layout)
921 cr.stroke_preserve()
922 cr.fill()
923
924 layout.set_text("DOW:")
925 layout.set_font_description(fontBold)
926 cr.move_to(x4, y)
927 cr.set_line_width(0.1 * scale)
928 PangoCairo.layout_path(cr, layout)
929 cr.stroke_preserve()
930 cr.fill()
931
932 layout.set_text("%d KG" % (flight.dow,))
933 layout.set_font_description(font)
934 (_ink, logical) = layout.get_extents()
935 x5 = x0 + width - logical.width / Pango.SCALE
936 cr.move_to(x5, y)
937 cr.set_line_width(0.1 * scale)
938 PangoCairo.layout_path(cr, layout)
939 cr.stroke_preserve()
940 cr.fill()
941
942 y += logical.height / Pango.SCALE
943 y += logical.height / Pango.SCALE
944 y += 4 * scale
945
946 layout.set_text("PASSENGERS")
947 layout.set_font_description(fontBold)
948 cr.move_to(x0, y)
949 cr.set_line_width(0.1 * scale)
950 PangoCairo.layout_path(cr, layout)
951 cr.stroke_preserve()
952 cr.fill()
953
954 layout.set_text("CARGO HOLDS")
955 layout.set_font_description(fontBold)
956 cr.move_to(x2, y)
957 cr.set_line_width(0.1 * scale)
958 PangoCairo.layout_path(cr, layout)
959 cr.stroke_preserve()
960 cr.fill()
961
962 layout.set_text("TOTAL")
963 layout.set_font_description(fontBold)
964 cr.move_to(x4, y)
965 cr.set_line_width(0.1 * scale)
966 PangoCairo.layout_path(cr, layout)
967 cr.stroke_preserve()
968 cr.fill()
969
970 y += logical.height / Pango.SCALE
971 y += 4 * scale
972
973 layout.set_text("CHILDREN:")
974 layout.set_font_description(font)
975 (_ink, logical) = layout.get_extents()
976 x1 = x0 + logical.width / Pango.SCALE
977
978 layout.set_text("CARGO:")
979 layout.set_font_description(font)
980 (_ink, logical) = layout.get_extents()
981 x3 = x2 + logical.width / Pango.SCALE
982
983 layout.set_text("ADULTS:")
984 layout.set_font_description(font)
985 cr.move_to(x0, y)
986 cr.set_line_width(0.1 * scale)
987 PangoCairo.layout_path(cr, layout)
988 cr.stroke_preserve()
989 cr.fill()
990
991 layout.set_text(" %3d" % (flight.numPassengers,))
992 layout.set_font_description(font)
993 cr.move_to(x1, y)
994 cr.set_line_width(0.1 * scale)
995 PangoCairo.layout_path(cr, layout)
996 cr.stroke_preserve()
997 cr.fill()
998
999 layout.set_text("BAGS:")
1000 layout.set_font_description(font)
1001 cr.move_to(x2, y)
1002 cr.set_line_width(0.1 * scale)
1003 PangoCairo.layout_path(cr, layout)
1004 cr.stroke_preserve()
1005 cr.fill()
1006
1007 layout.set_text(" %5d KG" % (flight.bagWeight,))
1008 layout.set_font_description(font)
1009 cr.move_to(x3, y)
1010 cr.set_line_width(0.1 * scale)
1011 PangoCairo.layout_path(cr, layout)
1012 cr.stroke_preserve()
1013 cr.fill()
1014
1015 y += logical.height / Pango.SCALE
1016 y += 4 * scale
1017
1018 layout.set_text("CHILDREN:")
1019 layout.set_font_description(font)
1020 cr.move_to(x0, y)
1021 cr.set_line_width(0.1 * scale)
1022 PangoCairo.layout_path(cr, layout)
1023 cr.stroke_preserve()
1024 cr.fill()
1025
1026 layout.set_text(" %3d" % (flight.numChildren,))
1027 layout.set_font_description(font)
1028 cr.move_to(x1, y)
1029 cr.set_line_width(0.1 * scale)
1030 PangoCairo.layout_path(cr, layout)
1031 cr.stroke_preserve()
1032 cr.fill()
1033
1034 layout.set_text("MAIL:")
1035 layout.set_font_description(font)
1036 cr.move_to(x2, y)
1037 cr.set_line_width(0.1 * scale)
1038 PangoCairo.layout_path(cr, layout)
1039 cr.stroke_preserve()
1040 cr.fill()
1041
1042 layout.set_text(" %5d KG" % (flight.mailWeight,))
1043 layout.set_font_description(font)
1044 cr.move_to(x3, y)
1045 cr.set_line_width(0.1 * scale)
1046 PangoCairo.layout_path(cr, layout)
1047 cr.stroke_preserve()
1048 cr.fill()
1049
1050 layout.set_text("PAYLOAD:")
1051 layout.set_font_description(font)
1052 cr.move_to(x4, y)
1053 cr.set_line_width(0.1 * scale)
1054 PangoCairo.layout_path(cr, layout)
1055 cr.stroke_preserve()
1056 cr.fill()
1057
1058 layout.set_text("%5d KG" % (flight.payload,))
1059 layout.set_font_description(font)
1060 (_ink, logical) = layout.get_extents()
1061 x5 = x0 + width - logical.width / Pango.SCALE
1062 cr.move_to(x5, y)
1063 cr.set_line_width(0.1 * scale)
1064 PangoCairo.layout_path(cr, layout)
1065 cr.stroke_preserve()
1066 cr.fill()
1067
1068 y += logical.height / Pango.SCALE
1069 y += 4 * scale
1070
1071 layout.set_text("INFANTS:")
1072 layout.set_font_description(font)
1073 cr.move_to(x0, y)
1074 cr.set_line_width(0.1 * scale)
1075 PangoCairo.layout_path(cr, layout)
1076 cr.stroke_preserve()
1077 cr.fill()
1078
1079 layout.set_text(" %3d" % (flight.numInfants,))
1080 layout.set_font_description(font)
1081 cr.move_to(x1, y)
1082 cr.set_line_width(0.1 * scale)
1083 PangoCairo.layout_path(cr, layout)
1084 cr.stroke_preserve()
1085 cr.fill()
1086
1087 layout.set_text("CARGO:")
1088 layout.set_font_description(font)
1089 cr.move_to(x2, y)
1090 cr.set_line_width(0.1 * scale)
1091 PangoCairo.layout_path(cr, layout)
1092 cr.stroke_preserve()
1093 cr.fill()
1094
1095 layout.set_text(" %5d KG" % (flight.cargoWeight,))
1096 layout.set_font_description(font)
1097 cr.move_to(x3, y)
1098 cr.set_line_width(0.1 * scale)
1099 PangoCairo.layout_path(cr, layout)
1100 cr.stroke_preserve()
1101 cr.fill()
1102
1103 layout.set_text("ZFW:")
1104 layout.set_font_description(font)
1105 cr.move_to(x4, y)
1106 cr.set_line_width(0.1 * scale)
1107 PangoCairo.layout_path(cr, layout)
1108 cr.stroke_preserve()
1109 cr.fill()
1110
1111 layout.set_text("%5d KG" % (flight.dow + flight.payload,))
1112 layout.set_font_description(font)
1113 (_ink, logical) = layout.get_extents()
1114 x5 = x0 + width - logical.width / Pango.SCALE
1115 cr.move_to(x5, y)
1116 cr.set_line_width(0.1 * scale)
1117 PangoCairo.layout_path(cr, layout)
1118 cr.stroke_preserve()
1119 cr.fill()
1120
1121 y += logical.height / Pango.SCALE
1122 y += logical.height / Pango.SCALE
1123 y += 4 * scale
1124
1125 layout.set_text("ROUTE:")
1126 layout.set_font_description(fontBold)
1127 (_ink, logical) = layout.get_extents()
1128 x1 = x0 + logical.width / Pango.SCALE
1129 cr.move_to(x0, y)
1130 cr.set_line_width(0.1 * scale)
1131 PangoCairo.layout_path(cr, layout)
1132 cr.stroke_preserve()
1133 cr.fill()
1134
1135 routeWords = flight.route.split(" ")
1136 layout.set_font_description(font)
1137
1138 startWord = 0
1139 textWidth = width - (x1 - x0)
1140 while startWord<len(routeWords):
1141 endWord = startWord + 1
1142 while endWord<=len(routeWords):
1143 layout.set_text(" %s" % (" ".join(routeWords[startWord:endWord]),))
1144 (_ink, logical) = layout.get_extents()
1145 if textWidth<logical.width / Pango.SCALE:
1146 endWord -= 1
1147 if endWord<=startWord:
1148 endWord = startWord + 1
1149 break
1150 endWord += 1
1151
1152 layout.set_text(" %s" % (" ".join(routeWords[startWord:endWord]),))
1153 cr.move_to(x1, y)
1154 cr.set_line_width(0.1 * scale)
1155 PangoCairo.layout_path(cr, layout)
1156 cr.stroke_preserve()
1157 cr.fill()
1158
1159 startWord = endWord
1160
1161 y += logical.height / Pango.SCALE
1162 y += 4 * scale
1163
1164 y += logical.height / Pango.SCALE
1165
1166 layout.set_text(delimiter)
1167 layout.set_font_description(font)
1168 cr.move_to(x0, y)
1169 cr.set_line_width(0.1 * scale)
1170 PangoCairo.layout_path(cr, layout)
1171 cr.stroke_preserve()
1172 cr.fill()
1173
1174 def _deleteClicked(self, button):
1175 """Called when the Delete flight button is clicked."""
1176 self._deleteSelected()
1177
1178 def _deleteSelected(self):
1179 """Delete the selected flight."""
1180 if askYesNo(xstr("flightsel_delete_confirm"),
1181 parent = self._wizard.gui.mainWindow):
1182 flight = self._getSelectedFlight()
1183 gui = self._wizard.gui
1184 gui.beginBusy(xstr("flightsel_delete_busy"))
1185
1186 gui.webHandler.deleteFlights(self._deleteResultCallback,
1187 [flight.id])
1188
1189 def _deleteResultCallback(self, returned, result):
1190 """Called when the deletion result is available."""
1191 GObject.idle_add(self._handleDeleteResult, returned, result)
1192
1193 def _handleDeleteResult(self, returned, result):
1194 """Handle the delete result."""
1195 gui = self._wizard.gui
1196 gui.endBusy()
1197
1198 if returned:
1199 indexes = self._flightList.selectedIndexes
1200
1201 flights = [self._flights[index] for index in indexes]
1202
1203 self._flightList.removeFlights(indexes)
1204 for index in indexes[::-1]:
1205 del self._flights[index]
1206 else:
1207 communicationErrorDialog()
1208
1209
1210 def _refreshClicked(self, button):
1211 """Called when the refresh button is clicked."""
1212 self._wizard.reloadFlights(self._refreshCallback)
1213
1214 def _refreshCallback(self, returned, result):
1215 """Callback for the refresh."""
1216 if returned:
1217 self._setupHelp()
1218 if result.loggedIn:
1219 self._buildFlights()
1220
1221 def _selectionChanged(self, flightList, indexes):
1222 """Called when the selection is changed."""
1223 self._saveButton.set_sensitive(len(indexes)==1)
1224 self._printButton.set_sensitive(len(indexes)==1)
1225 self._deleteButton.set_sensitive(len(indexes)==1)
1226 self._updateNextButton()
1227
1228 def _updatePendingButton(self):
1229 """Update the senstivity of the Pending button."""
1230 self._pendingButton.set_sensitive(self._pendingFlightsWindow.hasFlights)
1231
1232 def _updateNextButton(self):
1233 """Update the sensitivity of the Next button."""
1234 sensitive = len(self._flightList.selectedIndexes)==1 and \
1235 len(self._flights)>0 and \
1236 not self._pendingFlightsWindowShown
1237 if sensitive:
1238 flight = self._getSelectedFlight()
1239 flightDate = datetime.datetime.strptime(flight.date, "%Y-%m-%d").date()
1240 tomorrow = datetime.date.today() + datetime.timedelta(days=1)
1241 print(flightDate, tomorrow)
1242 sensitive = flightDate <= tomorrow
1243 self._button.set_sensitive(sensitive)
1244
1245 def _loadButtonClicked(self, loadButton):
1246 """Called when the load a flight button is clicked."""
1247 dialog = self._getLoadDialog()
1248 dialog.show_all()
1249 response = dialog.run()
1250 dialog.hide()
1251
1252 if response==Gtk.ResponseType.OK:
1253 fileName = dialog.get_filename()
1254 print("Loading", fileName)
1255 bookedFlight = web.BookedFlight()
1256 try:
1257 with open(fileName, "rt") as f:
1258 bookedFlight.readFromFile(f, self._wizard._loginResult.fleet)
1259 self.addFlight(bookedFlight)
1260 except Exception as e:
1261 print("Failed to load flight:", util.utf2unicode(str(e)))
1262 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1263 type = Gtk.MessageType.ERROR,
1264 message_format =
1265 xstr("flightsel_load_failed"))
1266 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
1267 dialog.set_title(WINDOW_TITLE_BASE)
1268 secondary = xstr("flightsel_load_failed_sec")
1269 dialog.format_secondary_markup(secondary)
1270 dialog.run()
1271 dialog.hide()
1272
1273 def _forwardClicked(self, button):
1274 """Called when the forward button was clicked."""
1275 if self._completed:
1276 self._wizard.jumpPage(self._nextID, finalize = False)
1277 else:
1278 self._flightSelected()
1279
1280 def _rowActivated(self, flightList, index):
1281 """Called when a row is activated."""
1282 if not self._completed:
1283 self._flightSelected()
1284
1285 def _flightSelected(self):
1286 """Called when a flight has been selected."""
1287 flight = self._getSelectedFlight()
1288 self._wizard._bookedFlight = flight
1289 self._wizard.gui.enableFlightInfo(flight.aircraftType)
1290
1291 self._updateDepartureGate()
1292
1293 def _getSelectedFlight(self):
1294 """Get the currently selected flight."""
1295 indexes = self._flightList.selectedIndexes
1296 assert(len(indexes)==1)
1297 return self._flights[indexes[0]]
1298
1299 def _updateDepartureGate(self):
1300 """Update the departure gate for the booked flight."""
1301 flight = self._wizard._bookedFlight
1302 if self._wizard.gui.config.onlineGateSystem and \
1303 self._wizard.loggedIn and not self._wizard.entranceExam:
1304 if flight.departureICAO=="LHBP":
1305 self._wizard.getFleet(self._fleetRetrieved)
1306 else:
1307 self._wizard.updatePlane(self._planeUpdated,
1308 flight.tailNumber,
1309 const.PLANE_AWAY)
1310 else:
1311 self._nextID = "connect"
1312 self._wizard.jumpPage("connect")
1313
1314 def _fleetRetrieved(self, fleet):
1315 """Called when the fleet has been retrieved."""
1316 if fleet is None:
1317 self._nextID = "connect"
1318 self._wizard.jumpPage("connect")
1319 else:
1320 plane = fleet[self._wizard._bookedFlight.tailNumber]
1321 if plane is None:
1322 self._nextID = "connect"
1323 self._wizard.jumpPage("connect")
1324 elif plane.gateNumber is not None and \
1325 not fleet.isGateConflicting(plane):
1326 self._wizard._departureGate = plane.gateNumber
1327 self._nextID = "connect"
1328 self._wizard.jumpPage("connect")
1329 else:
1330 self._nextID = "gatesel"
1331 self._wizard.jumpPage("gatesel")
1332
1333 def _planeUpdated(self, success):
1334 """Callback for the plane updating."""
1335 self._nextID = "connect"
1336 self._wizard.jumpPage("connect")
1337
1338 def _getSaveDialog(self):
1339 """Get the dialog to load a flight file."""
1340 if self._saveDialog is not None:
1341 return self._saveDialog
1342
1343 gui = self._wizard.gui
1344 dialog = Gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1345 xstr("flightsel_save_title"),
1346 action = Gtk.FileChooserAction.SAVE,
1347 buttons = (Gtk.STOCK_CANCEL,
1348 Gtk.ResponseType.CANCEL,
1349 Gtk.STOCK_OK, Gtk.ResponseType.OK),
1350 parent = gui.mainWindow)
1351 dialog.set_modal(True)
1352 dialog.set_do_overwrite_confirmation(True)
1353
1354 filter = Gtk.FileFilter()
1355 filter.set_name(xstr("flightsel_filter_flights"))
1356 filter.add_pattern("*.vaflight")
1357 dialog.add_filter(filter)
1358
1359 filter = Gtk.FileFilter()
1360 filter.set_name(xstr("file_filter_all"))
1361 filter.add_pattern("*.*")
1362 dialog.add_filter(filter)
1363
1364 self._saveDialog = dialog
1365
1366 return dialog
1367
1368 def _getLoadDialog(self):
1369 """Get the dialog to load a flight file."""
1370 if self._loadDialog is not None:
1371 return self._loadDialog
1372
1373 gui = self._wizard.gui
1374 dialog = Gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
1375 xstr("flightsel_load_title"),
1376 action = Gtk.FileChooserAction.OPEN,
1377 buttons = (Gtk.STOCK_CANCEL,
1378 Gtk.ResponseType.CANCEL,
1379 Gtk.STOCK_OK, Gtk.ResponseType.OK),
1380 parent = gui.mainWindow)
1381 dialog.set_modal(True)
1382
1383 filter = Gtk.FileFilter()
1384 filter.set_name(xstr("flightsel_filter_flights"))
1385 filter.add_pattern("*.vaflight")
1386 dialog.add_filter(filter)
1387
1388 filter = Gtk.FileFilter()
1389 filter.set_name(xstr("file_filter_all"))
1390 filter.add_pattern("*.*")
1391 dialog.add_filter(filter)
1392
1393 self._loadDialog = dialog
1394
1395 return dialog
1396
1397 def _createListPopupMenu(self):
1398 """Get the flight list popup menu."""
1399 menu = Gtk.Menu()
1400
1401 menuItem = Gtk.MenuItem()
1402 menuItem.set_label(xstr("flightsel_popup_select"))
1403 menuItem.set_use_underline(True)
1404 menuItem.connect("activate", self._popupSelect)
1405 menuItem.show()
1406
1407 menu.append(menuItem)
1408
1409 menuItem = Gtk.MenuItem()
1410 menuItem.set_label(xstr("flightsel_popup_save"))
1411 menuItem.set_use_underline(True)
1412 menuItem.connect("activate", self._popupSave)
1413 menuItem.show()
1414
1415 menu.append(menuItem)
1416
1417 menuItem = Gtk.MenuItem()
1418 menuItem.set_label(xstr("flightsel_popup_print"))
1419 menuItem.set_use_underline(True)
1420 menuItem.connect("activate", self._popupPrint)
1421 menuItem.show()
1422
1423 menu.append(menuItem)
1424
1425 menuItem = Gtk.MenuItem()
1426 menuItem.set_label(xstr("flightsel_popup_delete"))
1427 menuItem.set_use_underline(True)
1428 menuItem.connect("activate", self._popupDelete)
1429 menuItem.show()
1430
1431 menu.append(menuItem)
1432
1433 return menu
1434
1435 def _popupSelect(self, menuItem):
1436 """Called when the Select menu item is activated in the popup menu."""
1437 if not self._completed:
1438 self._flightSelected()
1439
1440 def _popupSave(self, menuItem):
1441 """Called when the Save menu item is activated in the popup menu."""
1442 if not self._completed:
1443 self._saveSelected()
1444
1445 def _popupPrint(self, menuItem):
1446 """Called when the Print briefing menu item is activated in the popup menu."""
1447 if not self._completed:
1448 self._printSelected()
1449
1450 def _popupDelete(self, menuItem):
1451 """Called when the Delete menu item is activated in the popup menu."""
1452 if not self._completed:
1453 self._deleteSelected()
1454
1455#-----------------------------------------------------------------------------
1456
1457class GateSelectionPage(Page):
1458 """Page to select a free gate at LHBP.
1459 This page should be displayed only if we have fleet information!."""
1460 def __init__(self, wizard):
1461 """Construct the gate selection page."""
1462 super(GateSelectionPage, self).__init__(wizard, "gatesel",
1463 xstr("gatesel_title"),
1464 xstr("gatesel_help"))
1465
1466 self._listStore = Gtk.ListStore(str)
1467 self._gateList = Gtk.TreeView(self._listStore)
1468 column = Gtk.TreeViewColumn(None, Gtk.CellRendererText(),
1469 text = 0)
1470 column.set_expand(True)
1471 self._gateList.append_column(column)
1472 self._gateList.set_headers_visible(False)
1473 self._gateList.connect("row-activated", self._rowActivated)
1474
1475 gateSelection = self._gateList.get_selection()
1476 gateSelection.connect("changed", self._selectionChanged)
1477
1478 scrolledWindow = Gtk.ScrolledWindow()
1479 scrolledWindow.add(self._gateList)
1480 scrolledWindow.set_size_request(50, -1)
1481 scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
1482 Gtk.PolicyType.AUTOMATIC)
1483 scrolledWindow.set_shadow_type(Gtk.ShadowType.IN)
1484
1485 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
1486 alignment.add(scrolledWindow)
1487
1488 self.setMainWidget(alignment)
1489
1490 self.addCancelFlightButton()
1491
1492 self.addPreviousButton(clicked = self._backClicked)
1493
1494 self._button = self.addNextButton(sensitive = False,
1495 clicked = self._forwardClicked)
1496
1497 def activate(self):
1498 """Fill the gate list."""
1499 self._listStore.clear()
1500 self._gateList.set_sensitive(True)
1501 occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
1502 for gate in lhbpGates.gates:
1503 if gate.isAvailable(lhbpGates, occupiedGateNumbers):
1504 self._listStore.append([gate.number])
1505
1506 def finalize(self):
1507 """Finalize the page."""
1508 self._gateList.set_sensitive(False)
1509
1510 def _selectionChanged(self, selection):
1511 """Called when the selection is changed."""
1512 self._button.set_sensitive(selection.count_selected_rows()==1)
1513
1514 def _backClicked(self, button):
1515 """Called when the Back button is pressed."""
1516 self.goBack()
1517
1518 def _forwardClicked(self, button):
1519 """Called when the forward button is clicked."""
1520 if not self._completed:
1521 self._gateSelected()
1522 else:
1523 self._wizard.jumpPage("connect")
1524
1525 def _rowActivated(self, flightList, path, column):
1526 """Called when a row is activated."""
1527 if not self._completed:
1528 self._gateSelected()
1529
1530 def _gateSelected(self):
1531 """Called when a gate has been selected."""
1532 selection = self._gateList.get_selection()
1533 (listStore, iter) = selection.get_selected()
1534 (gateNumber,) = listStore.get(iter, 0)
1535
1536 self._wizard._departureGate = gateNumber
1537
1538 self._wizard.updatePlane(self._planeUpdated,
1539 self._wizard._bookedFlight.tailNumber,
1540 const.PLANE_HOME, gateNumber)
1541
1542 def _planeUpdated(self, success):
1543 """Callback for the plane updating call."""
1544 if success is None or success:
1545 self._wizard.jumpPage("connect")
1546 else:
1547 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
1548 type = Gtk.MessageType.ERROR,
1549 message_format = xstr("gatesel_conflict"))
1550 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
1551 dialog.set_title(WINDOW_TITLE_BASE)
1552 dialog.format_secondary_markup(xstr("gatesel_conflict_sec"))
1553 dialog.run()
1554 dialog.hide()
1555
1556 self._wizard.getFleet(self._fleetRetrieved)
1557
1558 def _fleetRetrieved(self, fleet):
1559 """Called when the fleet has been retrieved."""
1560 if fleet is None:
1561 self._wizard.jumpPage("connect")
1562 else:
1563 self.activate()
1564
1565#-----------------------------------------------------------------------------
1566
1567class RegisterPage(Page):
1568 """A page to enter the registration data."""
1569
1570 # The minimal year of birth
1571 _minYearOfBirth = 1900
1572
1573 # The maximal year of birth
1574 _maxYearOfBirth = datetime.date.today().year
1575
1576 # The regular expression to check the e-mail address with
1577 _emailAddressRE = re.compile("[^@]+@[^@]+\.[^@]+")
1578
1579 def __init__(self, wizard):
1580 """Construct the registration page."""
1581 super(RegisterPage, self).__init__(wizard, "register",
1582 xstr("register_title"),
1583 xstr("register_help"))
1584
1585 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
1586 xscale = 0.0, yscale = 0.0)
1587
1588 table = Gtk.Table(12, 4)
1589 table.set_row_spacings(4)
1590 table.set_col_spacings(24)
1591 alignment.add(table)
1592 self.setMainWidget(alignment)
1593
1594 row = 0
1595
1596 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1597 xscale = 0.0, yscale = 0.0)
1598 label = Gtk.Label(xstr("register_name1"))
1599 label.set_use_underline(True)
1600 labelAlignment.add(label)
1601 table.attach(labelAlignment, 0, 1, row, row+1)
1602
1603 self._name1 = Gtk.Entry()
1604 self._name1.connect("changed", self._updateButtons)
1605 self._name1.set_tooltip_text(xstr("register_name1_tooltip"))
1606 self._name1.set_width_chars(15)
1607 table.attach(self._name1, 1, 2, row, row+1)
1608 label.set_mnemonic_widget(self._name1)
1609
1610 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1611 xscale = 0.0, yscale = 0.0)
1612 label = Gtk.Label(xstr("register_name2"))
1613 label.set_use_underline(True)
1614 labelAlignment.add(label)
1615 table.attach(labelAlignment, 2, 3, row, row+1)
1616
1617 self._name2 = Gtk.Entry()
1618 self._name2.connect("changed", self._updateButtons)
1619 self._name2.set_tooltip_text(xstr("register_name2_tooltip"))
1620 self._name2.set_width_chars(15)
1621 table.attach(self._name2, 3, 4, row, row+1)
1622 label.set_mnemonic_widget(self._name2)
1623
1624 row += 1
1625
1626 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1627 xscale = 0.0, yscale = 0.0)
1628 label = Gtk.Label(xstr("register_year_of_birth"))
1629 label.set_use_underline(True)
1630 labelAlignment.add(label)
1631 table.attach(labelAlignment, 0, 1, row, row+1)
1632
1633 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
1634 xscale = 0.0, yscale = 0.0)
1635
1636 self._yearOfBirth = Gtk.SpinButton()
1637 self._yearOfBirth.set_increments(step = 1, page = 10)
1638 self._yearOfBirth.set_range(min = RegisterPage._minYearOfBirth,
1639 max = RegisterPage._maxYearOfBirth)
1640 self._yearOfBirth.set_numeric(True)
1641 self._yearOfBirth.set_tooltip_text(xstr("register_year_of_birth_tooltip"))
1642 self._yearOfBirth.set_width_chars(5)
1643 self._yearOfBirth.connect("changed", self._updateButtons)
1644 self._yearOfBirth.connect("value-changed", self._updateButtons)
1645 alignment.add(self._yearOfBirth)
1646 table.attach(alignment, 1, 2, row, row+1)
1647 label.set_mnemonic_widget(self._yearOfBirth)
1648
1649 row += 1
1650
1651 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1652 xscale = 0.0, yscale = 0.0)
1653 label = Gtk.Label(xstr("register_email"))
1654 label.set_use_underline(True)
1655 labelAlignment.add(label)
1656 table.attach(labelAlignment, 0, 1, row, row+1)
1657
1658 self._emailAddress = Gtk.Entry()
1659 self._emailAddress.connect("changed", self._updateButtons)
1660 self._emailAddress.set_tooltip_text(xstr("register_email_tooltip"))
1661 table.attach(self._emailAddress, 1, 2, row, row+1)
1662 label.set_mnemonic_widget(self._emailAddress)
1663
1664 self._emailAddressPublic = Gtk.CheckButton(xstr("register_email_public"))
1665 self._emailAddressPublic.set_use_underline(True)
1666 self._emailAddressPublic.set_tooltip_text(xstr("register_email_public_tooltip"))
1667 table.attach(self._emailAddressPublic, 2, 4, row, row+1)
1668
1669 row += 1
1670
1671 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1672 xscale = 0.0, yscale = 0.0)
1673 label = Gtk.Label(xstr("register_vatsim_id"))
1674 label.set_use_underline(True)
1675 labelAlignment.add(label)
1676 table.attach(labelAlignment, 0, 1, row, row+1)
1677
1678 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
1679 xscale = 0.0, yscale = 0.0)
1680 self._vatsimID = IntegerEntry()
1681 self._vatsimID.connect("changed", self._updateButtons)
1682 self._vatsimID.set_tooltip_text(xstr("register_vatsim_id_tooltip"))
1683 self._vatsimID.set_width_chars(7)
1684 alignment.add(self._vatsimID)
1685 table.attach(alignment, 1, 2, row, row+1)
1686 label.set_mnemonic_widget(self._vatsimID)
1687
1688 labelAlignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
1689 xscale = 0.0, yscale = 0.0)
1690 label = Gtk.Label(xstr("register_ivao_id"))
1691 label.set_use_underline(True)
1692 labelAlignment.add(label)
1693 table.attach(labelAlignment, 2, 3, row, row+1)
1694
1695 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
1696 xscale = 0.0, yscale = 0.0)
1697 self._ivaoID = IntegerEntry()
1698 self._ivaoID.connect("changed", self._updateButtons)
1699 self._ivaoID.set_tooltip_text(xstr("register_ivao_id_tooltip"))
1700 self._ivaoID.set_width_chars(7)
1701 alignment.add(self._ivaoID)
1702 table.attach(alignment, 3, 4, row, row+1)
1703 label.set_mnemonic_widget(self._ivaoID)
1704
1705 row += 1
1706
1707 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1708 xscale = 0.0, yscale = 0.0)
1709 label = Gtk.Label(xstr("register_phone_num"))
1710 label.set_use_underline(True)
1711 label.set_use_markup(True)
1712 labelAlignment.add(label)
1713 table.attach(labelAlignment, 0, 1, row, row+1)
1714
1715 self._phoneNumber = Gtk.Entry()
1716 self._phoneNumber.set_tooltip_text(xstr("register_phone_num_tooltip"))
1717 table.attach(self._phoneNumber, 1, 2, row, row+1)
1718 label.set_mnemonic_widget(self._phoneNumber)
1719
1720 row += 1
1721
1722 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1723 xscale = 0.0, yscale = 0.0)
1724 label = Gtk.Label(xstr("register_nationality"))
1725 label.set_use_underline(True)
1726 label.set_use_markup(True)
1727 labelAlignment.add(label)
1728 table.attach(labelAlignment, 0, 1, row, row+1)
1729
1730
1731 self._nationality = Gtk.Entry()
1732 self._nationality.set_tooltip_text(xstr("register_nationality_tooltip"))
1733 table.attach(self._nationality, 1, 2, row, row+1)
1734 label.set_mnemonic_widget(self._nationality)
1735
1736 placeholder = Gtk.Label()
1737 placeholder.set_text(xstr("register_password_mismatch"))
1738 placeholder.set_use_markup(True)
1739 placeholder.set_child_visible(False)
1740 placeholder.hide()
1741 table.attach(placeholder, 2, 4, row, row+1)
1742
1743 row += 1
1744
1745 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1746 xscale = 0.0, yscale = 0.0)
1747 label = Gtk.Label(xstr("register_password"))
1748 label.set_use_underline(True)
1749 labelAlignment.add(label)
1750 table.attach(labelAlignment, 0, 1, row, row+1)
1751
1752 self._password = Gtk.Entry()
1753 self._password.set_visibility(False)
1754 self._password.connect("changed", self._updateButtons)
1755 self._password.set_tooltip_text(xstr("register_password_tooltip"))
1756 table.attach(self._password, 1, 2, row, row+1)
1757 label.set_mnemonic_widget(self._password)
1758
1759 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
1760 xscale = 0.0, yscale = 0.0)
1761 self._passwordStatus = Gtk.Label()
1762 alignment.add(self._passwordStatus)
1763 table.attach(alignment, 2, 4, row, row+1)
1764
1765 row += 1
1766
1767 labelAlignment = Gtk.Alignment(xalign = 1.0, yalign = 0.5,
1768 xscale = 0.0, yscale = 0.0)
1769 label = Gtk.Label(xstr("register_password2"))
1770 label.set_use_underline(True)
1771 labelAlignment.add(label)
1772 table.attach(labelAlignment, 0, 1, row, row+1)
1773
1774 self._password2 = Gtk.Entry()
1775 self._password2.set_visibility(False)
1776 self._password2.connect("changed", self._updateButtons)
1777 self._password2.set_tooltip_text(xstr("register_password2_tooltip"))
1778 table.attach(self._password2, 1, 2, row, row+1)
1779 label.set_mnemonic_widget(self._password2)
1780
1781 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
1782 xscale = 0.0, yscale = 0.0)
1783 self._password2Status = Gtk.Label()
1784 alignment.add(self._password2Status)
1785 table.attach(alignment, 2, 4, row, row+1)
1786
1787 row += 1
1788
1789 self._rememberButton = Gtk.CheckButton(xstr("remember_password"))
1790 self._rememberButton.set_use_underline(True)
1791 self._rememberButton.set_tooltip_text(xstr("login_remember_tooltip"))
1792 table.attach(self._rememberButton, 1, 3, row, row+1)
1793
1794 cancelButton = \
1795 self.addButton(xstr("button_cancel"))
1796 cancelButton.connect("clicked", self._cancelClicked)
1797
1798 self._registerButton = \
1799 self.addButton(xstr("button_register"), default = True,
1800 tooltip = xstr("button_register_tooltip"))
1801 self._registerButton.connect("clicked", self._registerClicked)
1802
1803 self._updateButtons()
1804
1805 @property
1806 def name1(self):
1807 """Get the first name component entered."""
1808 return self._name1.get_text()
1809
1810 @property
1811 def name2(self):
1812 """Get the second name component entered."""
1813 return self._name2.get_text()
1814
1815 @property
1816 def yearOfBirth(self):
1817 """Get the year of birth."""
1818 yearOfBirthText = self._yearOfBirth.get_text()
1819 return int(yearOfBirthText) if yearOfBirthText else 0
1820
1821 @property
1822 def emailAddress(self):
1823 """Get the e-mail address."""
1824 return self._emailAddress.get_text()
1825
1826 @property
1827 def emailAddressPublic(self):
1828 """Get the whether the e-mail address is public."""
1829 return self._emailAddressPublic.get_active()
1830
1831 @property
1832 def vatsimID(self):
1833 """Get the VATSIM ID."""
1834 return self._vatsimID.get_int()
1835
1836 @property
1837 def ivaoID(self):
1838 """Get the IVAO ID."""
1839 return self._ivaoID.get_int()
1840
1841 @property
1842 def phoneNumber(self):
1843 """Get the phone number."""
1844 return self._phoneNumber.get_text()
1845
1846 @property
1847 def nationality(self):
1848 """Get the nationality."""
1849 return self._nationality.get_text()
1850
1851 @property
1852 def password(self):
1853 """Get the password."""
1854 return self._password.get_text()
1855
1856 @property
1857 def rememberPassword(self):
1858 """Get whether the password should be remembered."""
1859 return self._rememberButton.get_active()
1860
1861 def activate(self):
1862 """Setup the route from the booked flight."""
1863 self._yearOfBirth.set_value(0)
1864 self._yearOfBirth.set_text("")
1865 self._updateButtons()
1866
1867 def _updateButtons(self, widget = None):
1868 """Update the sensitive state of the buttons"""
1869 yearOfBirth = self.yearOfBirth
1870
1871 emailAddress = self.emailAddress
1872 emailAddressMatch = RegisterPage._emailAddressRE.match(emailAddress)
1873
1874 vatsimID = self.vatsimID
1875 ivaoID = self.ivaoID
1876
1877 password = self.password
1878 password2 = self._password2.get_text()
1879 if not password:
1880 self._passwordStatus.set_text("")
1881 elif len(password)<5:
1882 self._passwordStatus.set_text(xstr("register_password_too_short"))
1883 else:
1884 self._passwordStatus.set_text(xstr("register_password_ok"))
1885 self._passwordStatus.set_use_markup(True)
1886
1887 if len(password)<5 or not password2:
1888 self._password2Status.set_text("")
1889 elif password!=password2:
1890 self._password2Status.set_text(xstr("register_password_mismatch"))
1891 else:
1892 self._password2Status.set_text(xstr("register_password_ok"))
1893 self._password2Status.set_use_markup(True)
1894
1895 sensitive = \
1896 len(self.name1)>0 and len(self.name2)>0 and \
1897 yearOfBirth>=RegisterPage._minYearOfBirth and \
1898 yearOfBirth<=RegisterPage._maxYearOfBirth and \
1899 emailAddressMatch is not None and \
1900 (vatsimID>=800000 or ivaoID>=100000) and \
1901 len(password)>=5 and password==password2
1902
1903 self._registerButton.set_sensitive(sensitive)
1904
1905 def _cancelClicked(self, button):
1906 """Called when the Cancel button is clicked."""
1907 self.goBack()
1908
1909 def _registerClicked(self, button):
1910 """Called when the Register button is clicked."""
1911 nameOrder = xstr("register_nameorder")
1912
1913 if nameOrder=="eastern":
1914 surName = self.name1
1915 firstName = self.name2
1916 else:
1917 surName = self.name2
1918 firstName = self.name1
1919
1920 nationality = self.nationality.lower()
1921
1922 if getLanguage().lower()=="hu" or nationality.find("hung")!=-1 or \
1923 nationality.find("magyar")!=-1:
1924 requestedNameOrder = "eastern"
1925 else:
1926 requestedNameOrder = "western"
1927
1928 registrationData = web.Registration(surName, firstName,
1929 requestedNameOrder,
1930 self.yearOfBirth,
1931 self.emailAddress,
1932 self.emailAddressPublic,
1933 self.vatsimID, self.ivaoID,
1934 self.phoneNumber, self.nationality,
1935 self.password)
1936 print("Registering with data:")
1937 print(" name:", self.name1, self.name2, registrationData.firstName, registrationData.surName, requestedNameOrder)
1938 print(" yearOfBirth:", self.yearOfBirth, registrationData.yearOfBirth)
1939 print(" emailAddress:", self.emailAddress, registrationData.emailAddress)
1940 print(" emailAddressPublic:", self.emailAddressPublic, registrationData.emailAddressPublic)
1941 print(" vatsimID:", self.vatsimID, registrationData.vatsimID)
1942 print(" ivaoID:", self.ivaoID, registrationData.ivaoID)
1943 print(" phoneNumber:", self.phoneNumber, registrationData.phoneNumber)
1944 print(" nationality:", self.nationality, registrationData.nationality)
1945
1946 gui = self._wizard.gui
1947 gui.beginBusy(xstr("register_busy"))
1948 gui.webHandler.register(self._registerResultCallback, registrationData)
1949
1950 def _registerResultCallback(self, returned, result):
1951 """Called when the registration result is available."""
1952 GObject.idle_add(self._handleRegisterResult, returned, result)
1953
1954 def _handleRegisterResult(self, returned, result):
1955 """Handle the registration result."""
1956 gui = self._wizard.gui
1957
1958 gui.endBusy()
1959
1960 print("Registration result:")
1961 print(" returned:", returned)
1962 if returned:
1963 print(" registered:", result.registered)
1964 if result.registered:
1965 print(" pilotID", result.pilotID)
1966 print(" loggedIn", result.loggedIn)
1967 print(" emailAlreadyRegistered:", result.emailAlreadyRegistered)
1968 print(" invalidData:", result.invalidData)
1969
1970 registrationOK = returned and result.registered
1971
1972 message = xstr("register_ok") if registrationOK \
1973 else xstr("register_failed")
1974 secondaryMessage = None
1975 if registrationOK:
1976 if result.loggedIn:
1977 secondaryMessage = xstr("register_info") % (result.pilotID,)
1978 else:
1979 secondaryMessage = xstr("register_nologin") % (result.pilotID,)
1980 messageType = Gtk.MessageType.INFO
1981
1982 config = gui.config
1983 config.pilotID = result.pilotID
1984 config.rememberPassword = self.rememberPassword
1985 if config.rememberPassword:
1986 config.password = self.password
1987 else:
1988 config.password = ""
1989
1990 config.save()
1991 elif returned and result.emailAlreadyRegistered:
1992 secondaryMessage = xstr("register_email_already")
1993 messageType = Gtk.MessageType.ERROR
1994 elif returned and result.invalidData:
1995 secondaryMessage = xstr("register_invalid_data")
1996 messageType = Gtk.MessageType.ERROR
1997 else:
1998 secondaryMessage = xstr("register_error")
1999 messageType = Gtk.MessageType.ERROR
2000
2001 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
2002 type = messageType,
2003 message_format = message)
2004 dialog.set_title(WINDOW_TITLE_BASE + " - " +
2005 xstr("register_result_title"))
2006 dialog.format_secondary_markup(secondaryMessage)
2007
2008 dialog.add_button(xstr("button_ok"), 0)
2009
2010 dialog.run()
2011 dialog.hide()
2012
2013 if registrationOK:
2014 if result.loggedIn:
2015 self._wizard._loginResult = result
2016 self._wizard.nextPage()
2017 else:
2018 self._wizard.jumpPage("login")
2019
2020#-----------------------------------------------------------------------------
2021
2022class StudentPage(Page):
2023 """A page displayed to students after logging in."""
2024 _entryExamStatusQueryInterval = 60*1000
2025
2026 def __init__(self, wizard):
2027 """Construct the student page."""
2028 super(StudentPage, self).__init__(wizard, "student",
2029 xstr("student_title"),
2030 xstr("student_help"))
2031
2032
2033 self._getEntryExamStatusCancelled = False
2034
2035 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
2036 xscale = 0.5, yscale = 0.0)
2037
2038 table = Gtk.Table(6, 4)
2039 table.set_row_spacings(4)
2040 table.set_col_spacings(0)
2041 table.set_homogeneous(False)
2042 alignment.add(table)
2043 self.setMainWidget(alignment)
2044
2045 row = 0
2046
2047 labelAlignment = Gtk.Alignment(xalign=0.0, yalign = 0.5,
2048 xscale=0.0, yscale = 0.0)
2049 label = Gtk.Label(xstr("student_entry_exam_status"))
2050 label.set_alignment(0.0, 0.5)
2051 labelAlignment.add(label)
2052 labelAlignment.resize_children()
2053 table.attach(labelAlignment, 0, 1, row, row + 1,
2054 xoptions = Gtk.AttachOptions.FILL)
2055
2056 alignment = Gtk.Alignment(xalign=0.0, yalign = 0.5,
2057 xscale=1.0, yscale = 0.0)
2058 self._entryExamStatus = Gtk.Label()
2059 self._entryExamStatus.set_use_markup(True)
2060 self._entryExamStatus.set_alignment(0.0, 0.5)
2061 alignment.add(self._entryExamStatus)
2062 alignment.resize_children()
2063 table.attach(alignment, 1, 4, row, row + 1)
2064
2065 row += 1
2066
2067 buttonAlignment = Gtk.Alignment(xalign=0.0, xscale=1.0)
2068 button = self._entryExamButton = Gtk.Button(xstr("student_entry_exam"))
2069 button.set_use_underline(True)
2070 button.connect("clicked", self._entryExamClicked)
2071 button.set_tooltip_text(xstr("student_entry_exam_tooltip"))
2072
2073 buttonAlignment.add(button)
2074 table.attach(buttonAlignment, 0, 4, row, row + 1,
2075 xoptions = Gtk.AttachOptions.FILL,
2076 ypadding = 4)
2077
2078 row += 3
2079
2080 labelAlignment = Gtk.Alignment(xalign=0.0, yalign = 0.5,
2081 xscale=0.0, yscale = 0.0)
2082 label = Gtk.Label(xstr("student_check_flight_status"))
2083 labelAlignment.add(label)
2084 table.attach(labelAlignment, 0, 1, row, row + 1,
2085 xoptions = Gtk.AttachOptions.FILL)
2086
2087 alignment = Gtk.Alignment(xalign=0.0, yalign = 0.5,
2088 xscale=1.0, yscale = 0.0)
2089 self._checkFlightStatus = Gtk.Label()
2090 self._checkFlightStatus.set_use_markup(True)
2091 self._checkFlightStatus.set_alignment(0.0, 0.5)
2092 alignment.add(self._checkFlightStatus)
2093 table.attach(alignment, 1, 4, row, row + 1)
2094
2095 row += 1
2096
2097 alignment = Gtk.Alignment(xalign=0.0, xscale=1.0)
2098
2099 hbox = Gtk.HBox()
2100 hbox.set_homogeneous(False)
2101 hbox.set_spacing(0)
2102
2103 aircraftTypesModel = Gtk.ListStore(str, int)
2104 for aircraftType in web.BookedFlight.checkFlightTypes:
2105 aircraftTypesModel.append([aircraftNames[aircraftType],
2106 aircraftType])
2107
2108 aircraftTypeAlignment = Gtk.Alignment(xalign = 0.0, xscale = 1.0)
2109
2110 self._aircraftType = Gtk.ComboBox(model = aircraftTypesModel)
2111 renderer = Gtk.CellRendererText()
2112 self._aircraftType.pack_start(renderer, True)
2113 self._aircraftType.add_attribute(renderer, "text", 0)
2114 self._aircraftType.set_tooltip_text(xstr("student_check_flight_type_tooltip"))
2115 self._aircraftType.set_active(0)
2116
2117 aircraftTypeAlignment.add(self._aircraftType)
2118
2119 hbox.pack_start(aircraftTypeAlignment, False, False, 0)
2120
2121 buttonAlignment = Gtk.Alignment(xalign=0.0, xscale=1.0)
2122 button = self._checkFlightButton = Gtk.Button(xstr("student_check_flight"))
2123 button.set_use_underline(True)
2124 button.connect("clicked", self._checkFlightClicked)
2125 button.set_tooltip_text(xstr("student_check_flight_tooltip"))
2126
2127 hbox.pack_start(button, True, True, 0)
2128
2129 alignment.add(hbox)
2130 table.attach(alignment, 0, 4, row, row + 1,
2131 xoptions = Gtk.AttachOptions.FILL)
2132
2133 @property
2134 def aircraftType(self):
2135 """Get the type of the aircraft used to perform the check flight."""
2136 index = self._aircraftType.get_active()
2137 return self._aircraftType.get_model()[index][1]
2138
2139 def activate(self):
2140 """Activate the student page."""
2141 print("StudentPage.activate")
2142 self._getEntryExamStatusCancelled = False
2143
2144 loginResult = self._wizard.loginResult
2145 self._entryExamLink = loginResult.entryExamLink
2146
2147 self._updateEntryExamStatus(loginResult.entryExamPassed)
2148 self._getEntryExamStatus()
2149
2150 # FIXME: call with real value
2151 self._updateCheckFlightStatus(self._wizard.loginResult.checkFlightStatus)
2152
2153 def finalize(self):
2154 """Finalize the page."""
2155 print("StudentPage.finalize")
2156 self._getEntryExamStatusCancelled = True
2157
2158 def _entryExamClicked(self, button):
2159 """Called when the entry exam button is clicked."""
2160 webbrowser.open(self._entryExamLink)
2161
2162 def _getEntryExamStatus(self):
2163 """Initiate the query of the entry exam status after the interval."""
2164 if not self._getEntryExamStatusCancelled:
2165 GObject.timeout_add(StudentPage._entryExamStatusQueryInterval,
2166 lambda: self._wizard.gui.webHandler. \
2167 getEntryExamStatus(self._entryExamStatusCallback))
2168
2169 def _entryExamStatusCallback(self, returned, result):
2170 """Called when the entry exam status is available."""
2171 GObject.idle_add(self._handleEntryExamStatus, returned, result)
2172
2173 def _handleEntryExamStatus(self, returned, result):
2174 """Called when the entry exam status is availabe."""
2175 print("_handleEntryExamStatus", returned, result)
2176 if returned and not self._getEntryExamStatusCancelled:
2177 self._entryExamLink = result.entryExamLink
2178 self._updateEntryExamStatus(result.entryExamPassed)
2179 if result.madeFO:
2180 self._madeFO()
2181 else:
2182 self._getEntryExamStatus()
2183
2184 def _updateEntryExamStatus(self, passed):
2185 """Update the entry exam status display and button."""
2186 self._entryExamStatus.set_text(xstr("student_entry_exam_passed")
2187 if passed else
2188 xstr("student_entry_exam_not_passed"))
2189 self._entryExamStatus.set_use_markup(True)
2190 self._entryExamButton.set_sensitive(not passed)
2191 self._wizard._loginResult.entryExamPassed = passed
2192
2193 def _checkFlightClicked(self, button):
2194 """Called when the check flight button is clicked."""
2195 aircraftType = self.aircraftType
2196 self._wizard._bookedFlight = \
2197 web.BookedFlight.forCheckFlight(aircraftType)
2198 self._wizard.gui.enableFlightInfo(aircraftType)
2199 self._wizard.jumpPage("connect")
2200
2201 def _updateCheckFlightStatus(self, passed):
2202 """Update the status of the check flight."""
2203 self._aircraftType.set_sensitive(not passed)
2204 self._checkFlightStatus.set_text(xstr("student_check_flight_passed")
2205 if passed else
2206 xstr("student_check_flight_not_passed"))
2207 self._checkFlightStatus.set_use_markup(True)
2208 self._checkFlightButton.set_sensitive(not passed)
2209
2210 def _madeFO(self):
2211 """Handle the event when the pilot has become a first officer."""
2212 wizard = self._wizard
2213 loginResult = wizard.loginResult
2214 loginResult.rank = "FO"
2215
2216 gui = wizard.gui
2217
2218 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
2219 type = Gtk.MessageType.INFO,
2220 message_format = xstr("student_fo"))
2221
2222 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
2223 dialog.set_title(WINDOW_TITLE_BASE)
2224 secondary = xstr("student_fo_secondary")
2225 dialog.format_secondary_markup(secondary)
2226 dialog.run()
2227 dialog.hide()
2228
2229 gui.reset()
2230
2231#-----------------------------------------------------------------------------
2232
2233class ConnectPage(Page):
2234 """Page which displays the departure airport and gate (if at LHBP)."""
2235 def __init__(self, wizard):
2236 """Construct the connect page."""
2237 help = "Load the aircraft below into the simulator and park it\n" \
2238 "at the given airport, at the gate below, if present.\n\n" \
2239 "Then press the Connect button to connect to the simulator."
2240 completedHelp = "The basic data of your flight can be read below."
2241 super(ConnectPage, self).__init__(wizard, "connect",
2242 xstr("connect_title"),
2243 xstr("connect_help"),
2244 completedHelp = xstr("connect_chelp"))
2245
2246 self._selectSimulator = os.name=="nt" or "FORCE_SELECT_SIM" in os.environ
2247
2248 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
2249 xscale = 0.0, yscale = 0.0)
2250
2251 table = Gtk.Table(7 if self._selectSimulator else 5, 2)
2252 table.set_row_spacings(4)
2253 table.set_col_spacings(16)
2254 table.set_homogeneous(True)
2255 alignment.add(table)
2256 self.setMainWidget(alignment)
2257
2258 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
2259 label = Gtk.Label(xstr("connect_flightno"))
2260 labelAlignment.add(label)
2261 table.attach(labelAlignment, 0, 1, 0, 1)
2262
2263 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
2264 self._flightNumber = Gtk.Label()
2265 self._flightNumber.set_width_chars(9)
2266 self._flightNumber.set_alignment(0.0, 0.5)
2267 labelAlignment.add(self._flightNumber)
2268 table.attach(labelAlignment, 1, 2, 0, 1)
2269
2270 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
2271 label = Gtk.Label(xstr("connect_acft"))
2272 labelAlignment.add(label)
2273 table.attach(labelAlignment, 0, 1, 1, 2)
2274
2275 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
2276 self._aircraft = Gtk.Label()
2277 self._aircraft.set_width_chars(25)
2278 self._aircraft.set_alignment(0.0, 0.5)
2279 labelAlignment.add(self._aircraft)
2280 table.attach(labelAlignment, 1, 2, 1, 2)
2281
2282 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
2283 label = Gtk.Label(xstr("connect_tailno"))
2284 labelAlignment.add(label)
2285 table.attach(labelAlignment, 0, 1, 2, 3)
2286
2287 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
2288 self._tailNumber = Gtk.Label()
2289 self._tailNumber.set_width_chars(10)
2290 self._tailNumber.set_alignment(0.0, 0.5)
2291 labelAlignment.add(self._tailNumber)
2292 table.attach(labelAlignment, 1, 2, 2, 3)
2293
2294 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
2295 label = Gtk.Label(xstr("connect_airport"))
2296 labelAlignment.add(label)
2297 table.attach(labelAlignment, 0, 1, 3, 4)
2298
2299 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
2300 self._departureICAO = Gtk.Label()
2301 self._departureICAO.set_width_chars(6)
2302 self._departureICAO.set_alignment(0.0, 0.5)
2303 labelAlignment.add(self._departureICAO)
2304 table.attach(labelAlignment, 1, 2, 3, 4)
2305
2306 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
2307 label = Gtk.Label(xstr("connect_gate"))
2308 labelAlignment.add(label)
2309 table.attach(labelAlignment, 0, 1, 4, 5)
2310
2311 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
2312 self._departureGate = Gtk.Label()
2313 self._departureGate.set_width_chars(5)
2314 self._departureGate.set_alignment(0.0, 0.5)
2315 labelAlignment.add(self._departureGate)
2316 table.attach(labelAlignment, 1, 2, 4, 5)
2317
2318 if self._selectSimulator:
2319 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0, yalign=0.5)
2320 label = Gtk.Label(xstr("connect_sim"))
2321 labelAlignment.add(label)
2322 table.attach(labelAlignment, 0, 1, 5, 7)
2323
2324 selectAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0, yalign=0.5)
2325
2326 selectBox = Gtk.HBox()
2327 self._selectMSFS = \
2328 Gtk.RadioButton.new_with_mnemonic_from_widget(None,
2329 xstr("connect_sim_msfs"))
2330
2331 selectBox.pack_start(self._selectMSFS, False, False, 0);
2332
2333 self._selectXPlane = \
2334 Gtk.RadioButton.new_with_mnemonic_from_widget(self._selectMSFS,
2335 xstr("connect_sim_xplane"))
2336
2337 selectBox.pack_start(self._selectXPlane, False, False, 8);
2338
2339 selectAlignment.add(selectBox)
2340 table.attach(selectAlignment, 1, 2, 5, 7)
2341
2342
2343 self.addCancelFlightButton()
2344
2345 self._previousButton = \
2346 self.addPreviousButton(clicked = self._backClicked)
2347
2348 self._button = self.addButton(xstr("button_connect"), default = True,
2349 tooltip = xstr("button_connect_tooltip"))
2350 self._clickedID = self._button.connect("clicked", self._connectClicked)
2351
2352 def activate(self):
2353 """Setup the departure information."""
2354 self._button.set_label(xstr("button_connect"))
2355 self._button.set_use_underline(True)
2356 self._button.set_tooltip_text(xstr("button_connect_tooltip"))
2357 self._button.disconnect(self._clickedID)
2358 self._clickedID = self._button.connect("clicked", self._connectClicked)
2359
2360 bookedFlight = self._wizard._bookedFlight
2361
2362 self._flightNumber.set_markup("<b>" + bookedFlight.callsign + "</b>")
2363
2364 aircraftType = aircraftNames[bookedFlight.aircraftType]
2365 self._aircraft.set_markup("<b>" + aircraftType + "</b>")
2366
2367 self._tailNumber.set_markup("<b>" + bookedFlight.tailNumber + "</b>")
2368
2369 icao = bookedFlight.departureICAO
2370 self._departureICAO.set_markup("<b>" + icao + "</b>")
2371 gate = self._wizard._departureGate
2372 if gate!="-":
2373 gate = "<b>" + gate + "</b>"
2374 self._departureGate.set_markup(gate)
2375
2376 if self._selectSimulator:
2377 config = self._wizard.gui.config
2378 self._selectMSFS.set_active(config.defaultMSFS)
2379 self._selectXPlane.set_active(not config.defaultMSFS)
2380
2381 self._previousButton.set_sensitive(not self._wizard.entranceExam)
2382
2383 def finalize(self):
2384 """Finalize the page."""
2385 self._button.set_label(xstr("button_next"))
2386 self._button.set_use_underline(True)
2387 self._button.set_tooltip_text(xstr("button_next_tooltip"))
2388 self._button.disconnect(self._clickedID)
2389 self._clickedID = self._button.connect("clicked", self._forwardClicked)
2390
2391 def _backClicked(self, button):
2392 """Called when the Back button is pressed."""
2393 self.goBack()
2394
2395 def _connectClicked(self, button):
2396 """Called when the Connect button is pressed."""
2397 if self._selectSimulator:
2398 simulatorType = const.SIM_MSFS9 if self._selectMSFS.get_active() \
2399 else const.SIM_XPLANE10
2400 else:
2401 simulatorType = const.SIM_MSFS9 if os.name=="nt" \
2402 else const.SIM_XPLANE10
2403
2404 config = self._wizard.gui.config
2405 config.defaultMSFS = simulatorType == const.SIM_MSFS9
2406 config.save()
2407
2408 self._wizard._connectSimulator(simulatorType)
2409
2410 def _forwardClicked(self, button):
2411 """Called when the Forward button is pressed."""
2412 self._wizard.nextPage()
2413
2414#-----------------------------------------------------------------------------
2415
2416class PayloadPage(Page):
2417 """Page to allow setting up the payload."""
2418 def __init__(self, wizard):
2419 """Construct the page."""
2420 super(PayloadPage, self).__init__(wizard, "payload",
2421 xstr("payload_title"),
2422 xstr("payload_help") + "\n\n \n \n",
2423 completedHelp = xstr("payload_chelp"))
2424
2425 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
2426 xscale = 0.0, yscale = 0.0)
2427
2428 table = Gtk.Table(7, 3)
2429 table.set_row_spacings(4)
2430 table.set_col_spacings(16)
2431 table.set_homogeneous(False)
2432 alignment.add(table)
2433 self.setMainWidget(alignment)
2434
2435 self._crewLabel = label = Gtk.Label(xstr("payload_crew"))
2436 label.set_use_underline(True)
2437 label.set_alignment(0.0, 0.5)
2438 table.attach(label, 0, 1, 0, 1)
2439
2440 self._numCockpitCrew = Gtk.Label()
2441
2442 self._numCabinCrew = IntegerEntry(defaultValue = 0)
2443 self._numCabinCrew.set_width_chars(6)
2444 self._numCabinCrew.connect("integer-changed", self._weightChanged)
2445 self._numCabinCrew.set_tooltip_text(xstr("payload_crew_tooltip"))
2446
2447 crewBox = Gtk.HBox()
2448 crewBox.pack_start(self._numCockpitCrew, False, False, 0)
2449 crewBox.pack_start(Gtk.Label("+"), False, False, 4)
2450 crewBox.pack_start(self._numCabinCrew, False, False, 0)
2451 crewBox.set_halign(Gtk.Align.END)
2452
2453 table.attach(crewBox, 1, 2, 0, 1)
2454 label.set_mnemonic_widget(self._numCabinCrew)
2455
2456 label = Gtk.Label(xstr("payload_crew_info"))
2457 label.set_halign(Gtk.Align.START)
2458 table.attach(label, 2, 3, 0, 1)
2459
2460 self._paxLabel = label = Gtk.Label(xstr("payload_pax"))
2461 label.set_use_underline(True)
2462 label.set_alignment(0.0, 0.5)
2463 table.attach(label, 0, 1, 1, 2)
2464
2465 self._numPassengers = IntegerEntry(defaultValue = 0)
2466 self._numPassengers.set_width_chars(6)
2467 self._numPassengers.connect("integer-changed", self._weightChanged)
2468 self._numPassengers.set_tooltip_text(xstr("payload_pax_tooltip"))
2469
2470 self._numChildren = IntegerEntry(defaultValue = 0)
2471 self._numChildren.set_width_chars(6)
2472 self._numChildren.connect("integer-changed", self._weightChanged)
2473 self._numChildren.set_tooltip_text(xstr("payload_pax_children_tooltip"))
2474
2475 self._numInfants = IntegerEntry(defaultValue = 0)
2476 self._numInfants.set_width_chars(6)
2477 self._numInfants.connect("integer-changed", self._weightChanged)
2478 self._numInfants.set_tooltip_text(xstr("payload_pax_infants_tooltip"))
2479
2480 paxBox = Gtk.HBox()
2481 paxBox.pack_start(self._numPassengers, False, False, 0)
2482 paxBox.pack_start(Gtk.Label("+"), False, False, 4)
2483 paxBox.pack_start(self._numChildren, False, False, 0)
2484 paxBox.pack_start(Gtk.Label("+"), False, False, 4)
2485 paxBox.pack_start(self._numInfants, False, False, 0)
2486
2487 table.attach(paxBox, 1, 2, 1, 2)
2488 label.set_mnemonic_widget(self._numPassengers)
2489
2490 label = Gtk.Label(xstr("payload_pax_info"))
2491 label.set_halign(Gtk.Align.START)
2492 table.attach(label, 2, 3, 1, 2)
2493
2494 label = Gtk.Label(xstr("payload_bag"))
2495 label.set_use_underline(True)
2496 label.set_alignment(0.0, 0.5)
2497 table.attach(label, 0, 1, 2, 3)
2498
2499 self._bagWeight = IntegerEntry(defaultValue = 0)
2500 self._bagWeight.set_width_chars(6)
2501 self._bagWeight.connect("integer-changed", self._weightChanged)
2502 self._bagWeight.set_tooltip_text(xstr("payload_bag_tooltip"))
2503 self._bagWeight.set_hexpand(False)
2504 self._bagWeight.set_halign(Gtk.Align.END)
2505 table.attach(self._bagWeight, 1, 2, 2, 3)
2506 label.set_mnemonic_widget(self._bagWeight)
2507
2508 label = Gtk.Label("kg")
2509 label.set_halign(Gtk.Align.START)
2510 table.attach(label, 2, 3, 2, 3)
2511
2512 label = Gtk.Label(xstr("payload_cargo"))
2513 label.set_use_underline(True)
2514 label.set_alignment(0.0, 0.5)
2515 table.attach(label, 0, 1, 3, 4)
2516
2517 self._cargoWeight = IntegerEntry(defaultValue = 0)
2518 self._cargoWeight.set_width_chars(6)
2519 self._cargoWeight.connect("integer-changed", self._weightChanged)
2520 self._cargoWeight.set_tooltip_text(xstr("payload_cargo_tooltip"))
2521 self._cargoWeight.set_hexpand(False)
2522 self._cargoWeight.set_halign(Gtk.Align.END)
2523 table.attach(self._cargoWeight, 1, 2, 3, 4)
2524 label.set_mnemonic_widget(self._cargoWeight)
2525
2526 label = Gtk.Label("kg")
2527 label.set_halign(Gtk.Align.START)
2528 table.attach(label, 2, 3, 3, 4)
2529
2530 label = Gtk.Label(xstr("payload_mail"))
2531 label.set_use_underline(True)
2532 label.set_alignment(0.0, 0.5)
2533 table.attach(label, 0, 1, 4, 5)
2534
2535 self._mailWeight = IntegerEntry(defaultValue = 0)
2536 self._mailWeight.set_width_chars(6)
2537 self._mailWeight.connect("integer-changed", self._weightChanged)
2538 self._mailWeight.set_tooltip_text(xstr("payload_mail_tooltip"))
2539 self._mailWeight.set_hexpand(False)
2540 self._mailWeight.set_halign(Gtk.Align.END)
2541 table.attach(self._mailWeight, 1, 2, 4, 5)
2542 label.set_mnemonic_widget(self._mailWeight)
2543
2544 label = Gtk.Label("kg")
2545 label.set_halign(Gtk.Align.START)
2546 table.attach(label, 2, 3, 4, 5)
2547
2548 label = Gtk.Label("<b>" + xstr("payload_zfw") + "</b>")
2549 label.set_alignment(0.0, 0.5)
2550 label.set_use_markup(True)
2551 table.attach(label, 0, 1, 5, 6)
2552
2553 self._calculatedZFW = Gtk.Label()
2554 self._calculatedZFW.set_width_chars(6)
2555 self._calculatedZFW.set_alignment(1.0, 0.5)
2556 table.attach(self._calculatedZFW, 1, 2, 5, 6)
2557
2558 label = Gtk.Label("kg")
2559 label.set_halign(Gtk.Align.START)
2560 table.attach(label, 2, 3, 5, 6)
2561
2562 self._zfwButton = Gtk.Button(xstr("payload_fszfw"))
2563 self._zfwButton.set_use_underline(True)
2564 self._zfwButton.connect("clicked", self._zfwRequested)
2565 self._zfwButton.set_tooltip_text(xstr("payload_fszfw_tooltip"))
2566 table.attach(self._zfwButton, 0, 1, 6, 7)
2567
2568 self._simulatorZFW = Gtk.Label("-")
2569 self._simulatorZFW.set_width_chars(6)
2570 self._simulatorZFW.set_alignment(1.0, 0.5)
2571 table.attach(self._simulatorZFW, 1, 2, 6, 7)
2572 self._simulatorZFWValue = None
2573
2574 label = Gtk.Label("kg")
2575 label.set_halign(Gtk.Align.START)
2576 table.attach(label, 2, 3, 6, 7)
2577
2578 self.addCancelFlightButton()
2579 self._backButton = self.addPreviousButton(clicked = self._backClicked)
2580 self._button = self.addNextButton(clicked = self._forwardClicked)
2581
2582 @property
2583 def numCockpitCrew(self):
2584 """The number of the cockpit crew members on the flight."""
2585 return self._wizard._bookedFlight.numCockpitCrew
2586
2587 @property
2588 def numCabinCrew(self):
2589 """The number of the cabin members on the flight."""
2590 return self._numCabinCrew.get_int()
2591
2592 @property
2593 def numPassengers(self):
2594 """The number of the adult passengers on the flight."""
2595 return self._numPassengers.get_int()
2596
2597 @property
2598 def numChildren(self):
2599 """The number of the child passengers on the flight."""
2600 return self._numChildren.get_int()
2601
2602 @property
2603 def numInfants(self):
2604 """The number of the infant passengers on the flight."""
2605 return self._numInfants.get_int()
2606
2607 @property
2608 def bagWeight(self):
2609 """Get the bag weight entered."""
2610 return self._bagWeight.get_int()
2611
2612 @property
2613 def cargoWeight(self):
2614 """Get the cargo weight entered."""
2615 return self._cargoWeight.get_int()
2616
2617 @property
2618 def mailWeight(self):
2619 """Get the bag weight entered."""
2620 return self._mailWeight.get_int()
2621
2622 def activate(self):
2623 """Setup the information."""
2624 bookedFlight = self._wizard._bookedFlight
2625
2626 self._numCockpitCrew.set_markup("<b>" +
2627 str(bookedFlight.numCockpitCrew) +
2628 "</b>")
2629 self._numCabinCrew.set_int(bookedFlight.numCabinCrew)
2630 self._numCabinCrew.set_sensitive(True)
2631
2632
2633 self._numPassengers.set_int(bookedFlight.numPassengers)
2634 self._numPassengers.set_sensitive(True)
2635
2636 self._numChildren.set_int(bookedFlight.numChildren)
2637 self._numChildren.set_sensitive(True)
2638
2639 self._numInfants.set_int(bookedFlight.numInfants)
2640 self._numInfants.set_sensitive(True)
2641
2642 self._bagWeight.set_int(bookedFlight.bagWeight)
2643 self._bagWeight.set_sensitive(True)
2644
2645 if bookedFlight.flightType==const.FLIGHTTYPE_CHARTER:
2646 self._cargoWeight.set_int(0)
2647 self._cargoWeight.set_sensitive(False)
2648 self._mailWeight.set_int(0)
2649 self._mailWeight.set_sensitive(False)
2650 else:
2651 self._cargoWeight.set_int(bookedFlight.cargoWeight)
2652 self._cargoWeight.set_sensitive(True)
2653 self._mailWeight.set_int(bookedFlight.mailWeight)
2654 self._mailWeight.set_sensitive(True)
2655
2656 self._simulatorZFW.set_text("-")
2657 self._simulatorZFWValue = None
2658 self._zfwButton.set_sensitive(True)
2659 self._updateCalculatedZFW()
2660
2661 def finalize(self):
2662 """Finalize the payload page."""
2663 self._numCabinCrew.set_sensitive(False)
2664 self._numPassengers.set_sensitive(False)
2665 self._numChildren.set_sensitive(False)
2666 self._numInfants.set_sensitive(False)
2667 self._bagWeight.set_sensitive(False)
2668 self._cargoWeight.set_sensitive(False)
2669 self._mailWeight.set_sensitive(False)
2670 self._wizard.gui.initializeWeightHelp()
2671
2672 def calculateZFW(self):
2673 """Calculate the ZFW value."""
2674 bookedFlight = self._wizard._bookedFlight
2675
2676 zfw = bookedFlight.dow
2677 zfw += (self._numCabinCrew.get_int() - bookedFlight.dowNumCabinCrew) * \
2678 const.WEIGHT_CABIN_CREW
2679 zfw += self._numPassengers.get_int() * \
2680 (const.WEIGHT_PASSENGER_CHARTER
2681 if bookedFlight.flightType==const.FLIGHTTYPE_CHARTER
2682 else const.WEIGHT_PASSENGER)
2683 zfw += self._numChildren.get_int() * const.WEIGHT_CHILD
2684 zfw += self._numInfants.get_int() * const.WEIGHT_INFANT
2685 zfw += self._bagWeight.get_int()
2686 zfw += self._cargoWeight.get_int()
2687 zfw += self._mailWeight.get_int()
2688 return zfw
2689
2690 def _updateCalculatedZFW(self):
2691 """Update the calculated ZFW"""
2692 zfw = self.calculateZFW()
2693
2694 markupBegin = "<b>"
2695 markupEnd = "</b>"
2696 if self._simulatorZFWValue is not None and \
2697 PayloadChecker.isZFWFaulty(self._simulatorZFWValue, zfw):
2698 markupBegin += '<span foreground="red">'
2699 markupEnd = "</span>" + markupEnd
2700 self._calculatedZFW.set_markup(markupBegin + str(zfw) + markupEnd)
2701
2702 def _weightChanged(self, entry, weight):
2703 """Called when one of the weight values or humanm counts has
2704 changed."""
2705 bookedFlight = self._wizard._bookedFlight
2706 numPassengers = \
2707 self._numPassengers.get_int() + \
2708 self._numChildren.get_int() + \
2709 self._numInfants.get_int()
2710 minCabinCrew = 0 if numPassengers==0 else \
2711 (bookedFlight.maxPassengers // 50) + 1
2712
2713 extraHelp = []
2714 enoughCrew = self._numCabinCrew.get_int() >= minCabinCrew
2715 if enoughCrew:
2716 self._crewLabel.set_text(xstr("payload_crew"))
2717 self.setHelp(xstr("payload_help"))
2718 else:
2719 self._crewLabel.set_markup("<b><span foreground=\"red\">" +
2720 xstr("payload_crew") +
2721 "</span></b>")
2722 extraHelp.append(xstr("payload_help_few_crew") % (minCabinCrew,))
2723 self._crewLabel.set_use_underline(True)
2724
2725 tooManyPassengers = numPassengers>bookedFlight.maxPassengers
2726 if tooManyPassengers:
2727 self._paxLabel.set_markup("<b><span foreground=\"red\">" +
2728 xstr("payload_pax") +
2729 "</span></b>")
2730 extraHelp.append(xstr("payload_help_many_pax") % (bookedFlight.maxPassengers,))
2731 else:
2732 self._paxLabel.set_text(xstr("payload_pax"))
2733 self._paxLabel.set_use_underline(True)
2734
2735 hlp = xstr("payload_help") + "\n\n"
2736 for eh in extraHelp:
2737 hlp += "<b><span foreground=\"red\">" + eh + "</span></b>\n"
2738 hlp += " \n" * (2-len(extraHelp))
2739 self.setHelp(hlp)
2740
2741 self._button.set_sensitive(enoughCrew and not tooManyPassengers)
2742
2743
2744 self._updateCalculatedZFW()
2745
2746 def _zfwRequested(self, button):
2747 """Called when the ZFW is requested from the simulator."""
2748 self._zfwButton.set_sensitive(False)
2749 self._backButton.set_sensitive(False)
2750 self._button.set_sensitive(False)
2751 gui = self._wizard.gui
2752 gui.beginBusy(xstr("payload_zfw_busy"))
2753 gui.simulator.requestZFW(self._handleZFW)
2754
2755 def _handleZFW(self, zfw):
2756 """Called when the ZFW value is retrieved."""
2757 GObject.idle_add(self._processZFW, zfw)
2758
2759 def _processZFW(self, zfw):
2760 """Process the given ZFW value received from the simulator."""
2761 self._wizard.gui.endBusy()
2762 self._zfwButton.set_sensitive(True)
2763 self._backButton.set_sensitive(True)
2764 self._button.set_sensitive(True)
2765 self._simulatorZFWValue = zfw
2766 self._simulatorZFW.set_text("%.0f" % (zfw,))
2767 self._updateCalculatedZFW()
2768
2769 def _forwardClicked(self, button):
2770 """Called when the forward button is clicked."""
2771 self._wizard.nextPage()
2772
2773 def _backClicked(self, button):
2774 """Called when the Back button is pressed."""
2775 self.goBack()
2776
2777#-----------------------------------------------------------------------------
2778
2779class TimePage(Page):
2780 """Page displaying the departure and arrival times and allows querying the
2781 current time from the flight simulator."""
2782 def __init__(self, wizard):
2783 super(TimePage, self).__init__(wizard, "time",
2784 xstr("time_title"),
2785 xstr("time_help"),
2786 completedHelp = xstr("time_chelp"))
2787
2788 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
2789 xscale = 0.0, yscale = 0.0)
2790
2791 table = Gtk.Table(3, 2)
2792 table.set_row_spacings(4)
2793 table.set_col_spacings(16)
2794 table.set_homogeneous(False)
2795 alignment.add(table)
2796 self.setMainWidget(alignment)
2797
2798 label = Gtk.Label(xstr("time_departure"))
2799 label.set_alignment(0.0, 0.5)
2800 table.attach(label, 0, 1, 0, 1)
2801
2802 self._departure = Gtk.Label()
2803 self._departure.set_alignment(0.0, 0.5)
2804 table.attach(self._departure, 1, 2, 0, 1)
2805
2806 label = Gtk.Label(xstr("time_arrival"))
2807 label.set_alignment(0.0, 0.5)
2808 table.attach(label, 0, 1, 1, 2)
2809
2810 self._arrival = Gtk.Label()
2811 self._arrival.set_alignment(0.0, 0.5)
2812 table.attach(self._arrival, 1, 2, 1, 2)
2813
2814 self._timeButton = Gtk.Button(xstr("time_fs"))
2815 self._timeButton.set_use_underline(True)
2816 self._timeButton.set_tooltip_text(xstr("time_fs_tooltip"))
2817 self._timeButton.connect("clicked", self._timeRequested)
2818 table.attach(self._timeButton, 0, 1, 2, 3)
2819
2820 self._simulatorTime = Gtk.Label("-")
2821 self._simulatorTime.set_alignment(0.0, 0.5)
2822 table.attach(self._simulatorTime, 1, 2, 2, 3)
2823
2824 self.addCancelFlightButton()
2825
2826 self._backButton = self.addPreviousButton(clicked = self._backClicked)
2827 self._button = self.addNextButton(clicked = self._forwardClicked)
2828
2829 def activate(self):
2830 """Activate the page."""
2831 self._timeButton.set_sensitive(True)
2832 bookedFlight = self._wizard._bookedFlight
2833 self._departure.set_text(str(bookedFlight.departureTime.time()))
2834 self._arrival.set_text(str(bookedFlight.arrivalTime.time()))
2835 self._simulatorTime.set_text("-")
2836
2837 def _timeRequested(self, button):
2838 """Request the time from the simulator."""
2839 self._timeButton.set_sensitive(False)
2840 self._backButton.set_sensitive(False)
2841 self._button.set_sensitive(False)
2842 self._wizard.gui.beginBusy(xstr("time_busy"))
2843 self._wizard.gui.simulator.requestTime(self._handleTime)
2844
2845 def _handleTime(self, timestamp):
2846 """Handle the result of a time retrieval."""
2847 GObject.idle_add(self._processTime, timestamp)
2848
2849 def _processTime(self, timestamp):
2850 """Process the given time."""
2851 self._wizard.gui.endBusy()
2852 self._timeButton.set_sensitive(True)
2853 self._backButton.set_sensitive(True)
2854 self._button.set_sensitive(True)
2855 tm = time.gmtime(timestamp)
2856 t = datetime.time(tm.tm_hour, tm.tm_min, tm.tm_sec)
2857 self._simulatorTime.set_text(str(t))
2858
2859 ts = tm.tm_hour * 3600 + tm.tm_min * 60 + tm.tm_sec
2860 dt = self._wizard._bookedFlight.departureTime.time()
2861 dts = dt.hour * 3600 + dt.minute * 60 + dt.second
2862 diff = dts-ts
2863
2864 markupBegin = ""
2865 markupEnd = ""
2866 if diff < 0:
2867 markupBegin = '<b><span foreground="red">'
2868 markupEnd = '</span></b>'
2869 elif diff < 3*60 or diff > 30*60:
2870 markupBegin = '<b><span foreground="orange">'
2871 markupEnd = '</span></b>'
2872
2873 self._departure.set_markup(markupBegin + str(dt) + markupEnd)
2874
2875 def _backClicked(self, button):
2876 """Called when the Back button is pressed."""
2877 self.goBack()
2878
2879 def _forwardClicked(self, button):
2880 """Called when the forward button is clicked."""
2881 if not self._completed:
2882 gui = self._wizard.gui
2883 gui.beginBusy(xstr("fuel_get_busy"))
2884
2885 gui.simulator.getFuel(self._handleFuel)
2886 else:
2887 self._wizard.nextPage()
2888
2889 def _handleFuel(self, fuelData):
2890 """Callback for the fuel query operation."""
2891 GObject.idle_add(self._processFuel, fuelData)
2892
2893 def _processFuel(self, fuelData):
2894 """Process the given fuel data."""
2895 self._wizard.gui.endBusy()
2896 self._wizard._fuelData = fuelData
2897 self._wizard.nextPage()
2898
2899#-----------------------------------------------------------------------------
2900
2901class RoutePage(Page):
2902 """The page containing the route and the flight level."""
2903 def __init__(self, wizard):
2904 """Construct the page."""
2905 super(RoutePage, self).__init__(wizard, "route",
2906 xstr("route_title"),
2907 xstr("route_help"),
2908 completedHelp = xstr("route_chelp"))
2909
2910 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
2911 xscale = 0.0, yscale = 0.0)
2912
2913 mainBox = Gtk.VBox()
2914 alignment.add(mainBox)
2915 self.setMainWidget(alignment)
2916
2917 levelBox = Gtk.HBox()
2918
2919 label = Gtk.Label(xstr("route_level"))
2920 label.set_use_underline(True)
2921 levelBox.pack_start(label, True, True, 0)
2922
2923 self._cruiseLevel = Gtk.SpinButton()
2924 self._cruiseLevel.set_increments(step = 10, page = 100)
2925 self._cruiseLevel.set_range(min = 0, max = 500)
2926 self._cruiseLevel.set_tooltip_text(xstr("route_level_tooltip"))
2927 self._cruiseLevel.set_numeric(True)
2928 self._cruiseLevel.connect("changed", self._cruiseLevelChanged)
2929 self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
2930 label.set_mnemonic_widget(self._cruiseLevel)
2931
2932 levelBox.pack_start(self._cruiseLevel, False, False, 8)
2933
2934 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
2935 xscale = 0.0, yscale = 0.0)
2936 alignment.add(levelBox)
2937
2938 mainBox.pack_start(alignment, False, False, 0)
2939
2940
2941 routeBox = Gtk.VBox()
2942
2943 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
2944 xscale = 0.0, yscale = 0.0)
2945 label = Gtk.Label(xstr("route_route"))
2946 label.set_use_underline(True)
2947 alignment.add(label)
2948 routeBox.pack_start(alignment, True, True, 0)
2949
2950 routeWindow = Gtk.ScrolledWindow()
2951 routeWindow.set_size_request(400, 80)
2952 routeWindow.set_shadow_type(Gtk.ShadowType.IN)
2953 routeWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
2954 Gtk.PolicyType.AUTOMATIC)
2955
2956 self._uppercasingRoute = False
2957
2958 self._route = Gtk.TextView()
2959 self._route.set_tooltip_text(xstr("route_route_tooltip"))
2960 self._route.set_wrap_mode(Gtk.WrapMode.WORD)
2961 self._route.get_buffer().connect("changed", self._routeChanged)
2962 self._route.get_buffer().connect_after("insert-text", self._routeInserted)
2963 routeWindow.add(self._route)
2964
2965 label.set_mnemonic_widget(self._route)
2966 routeBox.pack_start(routeWindow, True, True, 0)
2967
2968 mainBox.pack_start(routeBox, True, True, 8)
2969
2970 alternateBox = Gtk.HBox()
2971
2972 label = Gtk.Label(xstr("route_altn"))
2973 label.set_use_underline(True)
2974 alternateBox.pack_start(label, True, True, 0)
2975
2976 self._alternate = Gtk.Entry()
2977 self._alternate.set_width_chars(6)
2978 self._alternate.connect("changed", self._alternateChanged)
2979 self._alternate.set_tooltip_text(xstr("route_altn_tooltip"))
2980 label.set_mnemonic_widget(self._alternate)
2981
2982 alternateBox.pack_start(self._alternate, False, False, 8)
2983
2984 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.5,
2985 xscale = 0.0, yscale = 0.0)
2986 alignment.add(alternateBox)
2987
2988 mainBox.pack_start(alignment, False, False, 0)
2989
2990 self.addCancelFlightButton()
2991
2992 self._backButton = self.addPreviousButton(clicked = self._backClicked)
2993 self._button = self.addNextButton(clicked = self._forwardClicked)
2994
2995 @property
2996 def filedCruiseLevel(self):
2997 """Get the filed cruise level."""
2998 return self._cruiseLevel.get_value_as_int()
2999
3000 @property
3001 def route(self):
3002 """Get the route."""
3003 return self._getRoute()
3004
3005 @property
3006 def alternate(self):
3007 """Get the ICAO code of the alternate airport."""
3008 return self._alternate.get_text()
3009
3010 def activate(self):
3011 """Setup the route from the booked flight."""
3012 self._cruiseLevel.set_value(0)
3013 self._cruiseLevel.set_text("")
3014 self._route.get_buffer().set_text(self._wizard._bookedFlight.route)
3015 self._alternate.set_text("")
3016 self._updateForwardButton()
3017
3018 def _getRoute(self):
3019 """Get the text of the route."""
3020 buffer = self._route.get_buffer()
3021 return buffer.get_text(buffer.get_start_iter(),
3022 buffer.get_end_iter(), True)
3023
3024 def _updateForwardButton(self):
3025 """Update the sensitivity of the forward button."""
3026 cruiseLevelText = self._cruiseLevel.get_text()
3027 cruiseLevel = int(cruiseLevelText) if cruiseLevelText else 0
3028 alternate = self._alternate.get_text()
3029 self._button.set_sensitive(cruiseLevel>=50 and self._getRoute()!="" and
3030 (len(alternate)==4 or self._wizard.entranceExam))
3031
3032 def _cruiseLevelChanged(self, *arg):
3033 """Called when the cruise level has changed."""
3034 self._updateForwardButton()
3035
3036 def _routeChanged(self, textBuffer):
3037 """Called when the route has changed."""
3038 if not self._uppercasingRoute:
3039 self._updateForwardButton()
3040
3041 def _routeInserted(self, textBuffer, iter, text, length):
3042 """Called when new characters are inserted into the route.
3043
3044 It uppercases all characters."""
3045 if not self._uppercasingRoute:
3046 self._uppercasingRoute = True
3047
3048 iter1 = iter.copy()
3049 iter1.backward_chars(length)
3050 textBuffer.delete(iter, iter1)
3051
3052 textBuffer.insert(iter, text.upper())
3053
3054 self._uppercasingRoute = False
3055
3056 def _alternateChanged(self, entry):
3057 """Called when the alternate airport has changed."""
3058 entry.set_text(entry.get_text().upper())
3059 self._updateForwardButton()
3060
3061 def _backClicked(self, button):
3062 """Called when the Back button is pressed."""
3063 self.goBack()
3064
3065 def _forwardClicked(self, button):
3066 """Called when the Forward button is clicked."""
3067 if self._wizard.gui.flight.aircraft.simBriefData is None:
3068 self._wizard.usingSimBrief = False
3069 if self._wizard.gui.config.useSimBrief and \
3070 self._wizard.usingSimBrief is not False:
3071 self._wizard.jumpPage("simbrief_setup")
3072 else:
3073 self._wizard.usingSimBrief = False
3074 self._wizard.jumpPage("fuel")
3075
3076#-----------------------------------------------------------------------------
3077
3078class SimBriefCredentialsDialog(Gtk.Dialog):
3079 """A dialog window to ask for SimBrief credentials."""
3080 def __init__(self, gui, userName, password, rememberPassword):
3081 """Construct the dialog."""
3082 super(SimBriefCredentialsDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
3083 xstr("simbrief_credentials_title"),
3084 gui.mainWindow,
3085 Gtk.DialogFlags.MODAL)
3086 self.add_button(xstr("button_cancel"), Gtk.ResponseType.CANCEL)
3087 self.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
3088
3089 contentArea = self.get_content_area()
3090
3091 contentAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3092 xscale = 0.0, yscale = 0.0)
3093 contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
3094 padding_left = 8, padding_right = 8)
3095
3096 contentArea.pack_start(contentAlignment, False, False, 0)
3097
3098 contentVBox = Gtk.VBox()
3099 contentAlignment.add(contentVBox)
3100
3101 label = Gtk.Label(xstr("simbrief_login_failed"))
3102 label.set_alignment(0.0, 0.0)
3103
3104 contentVBox.pack_start(label, False, False, 0)
3105
3106 tableAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3107 xscale = 0.0, yscale = 0.0)
3108 tableAlignment.set_padding(padding_top = 24, padding_bottom = 0,
3109 padding_left = 0, padding_right = 0)
3110
3111 table = Gtk.Table(3, 2)
3112 table.set_row_spacings(4)
3113 table.set_col_spacings(16)
3114 table.set_homogeneous(False)
3115
3116 tableAlignment.add(table)
3117 contentVBox.pack_start(tableAlignment, True, True, 0)
3118
3119 label = Gtk.Label(xstr("simbrief_username"))
3120 label.set_use_underline(True)
3121 label.set_alignment(0.0, 0.5)
3122 table.attach(label, 0, 1, 0, 1)
3123
3124 self._userName = Gtk.Entry()
3125 self._userName.set_width_chars(16)
3126 #self._userName.connect("changed",
3127 # lambda button: self._updateForwardButton())
3128 self._userName.set_tooltip_text(xstr("simbrief_username_tooltip"))
3129 self._userName.set_text(userName)
3130 table.attach(self._userName, 1, 2, 0, 1)
3131 label.set_mnemonic_widget(self._userName)
3132
3133 label = Gtk.Label(xstr("simbrief_password"))
3134 label.set_use_underline(True)
3135 label.set_alignment(0.0, 0.5)
3136 table.attach(label, 0, 1, 1, 2)
3137
3138 self._password = Gtk.Entry()
3139 self._password.set_visibility(False)
3140 #self._password.connect("changed",
3141 # lambda button: self._updateForwardButton())
3142 self._password.set_tooltip_text(xstr("simbrief_password_tooltip"))
3143 self._password.set_text(password)
3144 table.attach(self._password, 1, 2, 1, 2)
3145 label.set_mnemonic_widget(self._password)
3146
3147 self._rememberButton = Gtk.CheckButton(xstr("simbrief_remember_password"))
3148 self._rememberButton.set_use_underline(True)
3149 self._rememberButton.set_tooltip_text(xstr("simbrief_remember_tooltip"))
3150 self._rememberButton.set_active(rememberPassword)
3151 table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
3152
3153 @property
3154 def userName(self):
3155 """Get the user name entered."""
3156 return self._userName.get_text()
3157
3158 @property
3159 def password(self):
3160 """Get the password entered."""
3161 return self._password.get_text()
3162
3163 @property
3164 def rememberPassword(self):
3165 """Get whether the password is to be remembered."""
3166 return self._rememberButton.get_active()
3167
3168 def run(self):
3169 """Run the dialog."""
3170 self.show_all()
3171
3172 response = super(SimBriefCredentialsDialog, self).run()
3173
3174 self.hide()
3175
3176 return response
3177
3178#-----------------------------------------------------------------------------
3179
3180class SimBriefSetupPage(Page):
3181 """Page for setting up some parameters for SimBrief."""
3182 monthNum2Name = [
3183 "JAN",
3184 "FEB",
3185 "MAR",
3186 "APR",
3187 "MAY",
3188 "JUN",
3189 "JUL",
3190 "AUG",
3191 "SEP",
3192 "OCT",
3193 "NOV",
3194 "DEC"
3195 ]
3196
3197 progress2Message = {
3198 cef.SIMBRIEF_PROGRESS_SEARCHING_BROWSER: "simbrief_progress_searching_browser",
3199 cef.SIMBRIEF_PROGRESS_LOADING_FORM: "simbrief_progress_loading_form",
3200 cef.SIMBRIEF_PROGRESS_FILLING_FORM: "simbrief_progress_filling_form",
3201 cef.SIMBRIEF_PROGRESS_WAITING_LOGIN: "simbrief_progress_waiting_login",
3202 cef.SIMBRIEF_PROGRESS_LOGGING_IN: "simbrief_progress_logging_in",
3203 cef.SIMBRIEF_PROGRESS_WAITING_RESULT: "simbrief_progress_waiting_result",
3204 cef.SIMBRIEF_PROGRESS_RETRIEVING_BRIEFING: "simbrief_progress_retrieving_briefing"
3205 }
3206
3207 result2Message = {
3208 cef.SIMBRIEF_RESULT_ERROR_OTHER: "simbrief_result_error_other",
3209 cef.SIMBRIEF_RESULT_ERROR_NO_FORM: "simbrief_result_error_no_form",
3210 cef.SIMBRIEF_RESULT_ERROR_NO_POPUP: "simbrief_result_error_no_popup",
3211 cef.SIMBRIEF_RESULT_ERROR_LOGIN_FAILED: "simbrief_result_error_login_failed"
3212 }
3213
3214 _resultQueryInterval = 2000
3215
3216 _waitTimeout = 2 * 60.0
3217
3218 @staticmethod
3219 def getHTMLFilePath():
3220 """Get the path of the HTML file to contain the generated flight
3221 plan."""
3222 if os.name=="nt":
3223 return os.path.join(tempfile.gettempdir(),
3224 "mlx_simbrief" +
3225 (".secondary" if secondaryInstallation else "") +
3226 ".html")
3227 else:
3228 import pwd
3229 return os.path.join(tempfile.gettempdir(),
3230 "mlx_simbrief." + pwd.getpwuid(os.getuid())[0] + "" +
3231 (".secondary" if secondaryInstallation else "") +
3232 ".html")
3233
3234 def __init__(self, wizard):
3235 """Construct the setup page."""
3236
3237 super(SimBriefSetupPage, self).__init__(wizard, "simbrief_setup",
3238 xstr("simbrief_setup_title"),
3239 xstr("simbrief_setup_help"),
3240 xstr("simbrief_setup_chelp"))
3241
3242 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3243 xscale = 0.0, yscale = 0.0)
3244
3245 table = Gtk.Table(6, 3)
3246 table.set_row_spacings(4)
3247 table.set_col_spacings(16)
3248 table.set_homogeneous(False)
3249 alignment.add(table)
3250 self.setMainWidget(alignment)
3251
3252
3253 row = 0
3254
3255 # label = Gtk.Label(xstr("simbrief_username"))
3256 # label.set_use_underline(True)
3257 # label.set_alignment(0.0, 0.5)
3258 # table.attach(label, 0, 1, row, row+1)
3259
3260 # self._userName = Gtk.Entry()
3261 # self._userName.set_width_chars(16)
3262 # self._userName.connect("changed",
3263 # lambda button: self._updateForwardButton())
3264 # self._userName.set_tooltip_text(xstr("simbrief_username_tooltip"))
3265 # table.attach(self._userName, 1, 2, row, row+1)
3266 # label.set_mnemonic_widget(self._userName)
3267 # row += 1
3268
3269 # label = Gtk.Label(xstr("simbrief_password"))
3270 # label.set_use_underline(True)
3271 # label.set_alignment(0.0, 0.5)
3272 # table.attach(label, 0, 1, row, row+1)
3273
3274 # self._password = Gtk.Entry()
3275 # self._password.set_visibility(False)
3276 # self._password.connect("changed",
3277 # lambda button: self._updateForwardButton())
3278 # self._password.set_tooltip_text(xstr("simbrief_password_tooltip"))
3279 # table.attach(self._password, 1, 2, row, row+1)
3280 # label.set_mnemonic_widget(self._password)
3281 # row += 1
3282
3283 # self._rememberButton = Gtk.CheckButton(xstr("simbrief_remember_password"))
3284 # self._rememberButton.set_use_underline(True)
3285 # self._rememberButton.set_tooltip_text(xstr("simbrief_remember_tooltip"))
3286 # table.attach(self._rememberButton, 1, 2, row, row+1, ypadding = 8)
3287 # row += 1
3288
3289 label = Gtk.Label(xstr("simbrief_extra_fuel"))
3290 label.set_use_underline(True)
3291 label.set_alignment(0.0, 0.5)
3292 table.attach(label, 0, 1, row, row+1)
3293
3294 self._extraFuel = IntegerEntry(defaultValue = 0)
3295 self._extraFuel.set_width_chars(6)
3296 self._extraFuel.set_tooltip_text(xstr("simbrief_extra_fuel_tooltip"))
3297 table.attach(self._extraFuel, 1, 2, row, row+1)
3298 label.set_mnemonic_widget(self._extraFuel)
3299
3300 table.attach(Gtk.Label("kg"), 2, 3, row, row+1)
3301 row += 1
3302
3303 label = Gtk.Label(xstr("simbrief_takeoff_runway"))
3304 label.set_use_underline(True)
3305 label.set_alignment(0.0, 0.5)
3306 table.attach(label, 0, 1, row, row+1)
3307
3308 self._takeoffRunway = Gtk.Entry()
3309 self._takeoffRunway.set_width_chars(10)
3310 self._takeoffRunway.set_tooltip_text(xstr("simbrief_takeoff_runway_tooltip"))
3311 self._takeoffRunway.connect("changed", self._upperChanged)
3312 table.attach(self._takeoffRunway, 1, 2, row, row+1)
3313 label.set_mnemonic_widget(self._takeoffRunway)
3314 row += 1
3315
3316 label = Gtk.Label(xstr("simbrief_landing_runway"))
3317 label.set_use_underline(True)
3318 label.set_alignment(0.0, 0.5)
3319 table.attach(label, 0, 1, row, row+1)
3320
3321 self._landingRunway = Gtk.Entry()
3322 self._landingRunway.set_width_chars(10)
3323 self._landingRunway.set_tooltip_text(xstr("simbrief_landing_runway_tooltip"))
3324 self._landingRunway.connect("changed", self._upperChanged)
3325 table.attach(self._landingRunway, 1, 2, row, row+1)
3326 label.set_mnemonic_widget(self._landingRunway)
3327 row += 1
3328
3329 label = Gtk.Label(xstr("simbrief_climb_profile"))
3330 label.set_use_underline(True)
3331 label.set_alignment(0.0, 0.5)
3332 table.attach(label, 0, 1, row, row+1)
3333
3334 self._climbProfile = Gtk.ComboBox()
3335 renderer = Gtk.CellRendererText()
3336 self._climbProfile.pack_start(renderer, True)
3337 self._climbProfile.add_attribute(renderer, "text", 0)
3338 self._climbProfile.set_tooltip_text(xstr("simbrief_climb_profile_tooltip"))
3339 table.attach(self._climbProfile, 1, 2, row, row+1)
3340 label.set_mnemonic_widget(self._climbProfile)
3341 row += 1
3342
3343 label = Gtk.Label(xstr("simbrief_cruise_profile"))
3344 label.set_use_underline(True)
3345 label.set_alignment(0.0, 0.5)
3346 table.attach(label, 0, 1, row, row+1)
3347
3348 self._cruiseProfile = Gtk.ComboBox()
3349 renderer = Gtk.CellRendererText()
3350 self._cruiseProfile.pack_start(renderer, True)
3351 self._cruiseProfile.add_attribute(renderer, "text", 0)
3352 self._cruiseProfile.set_tooltip_text(xstr("simbrief_cruise_profile_tooltip"))
3353 table.attach(self._cruiseProfile, 1, 2, row, row+1)
3354 label.set_mnemonic_widget(self._cruiseProfile)
3355 row += 1
3356
3357 label = Gtk.Label(xstr("simbrief_descent_profile"))
3358 label.set_use_underline(True)
3359 label.set_alignment(0.0, 0.5)
3360 table.attach(label, 0, 1, row, row+1)
3361
3362 self._descentProfile = Gtk.ComboBox()
3363 renderer = Gtk.CellRendererText()
3364 self._descentProfile.pack_start(renderer, True)
3365 self._descentProfile.add_attribute(renderer, "text", 0)
3366 self._descentProfile.set_tooltip_text(xstr("simbrief_descent_profile_tooltip"))
3367 table.attach(self._descentProfile, 1, 2, row, row+1)
3368 label.set_mnemonic_widget(self._descentProfile)
3369
3370 self.addCancelFlightButton()
3371
3372 self._backButton = self.addPreviousButton(clicked = self._backClicked)
3373 self._button = self.addNextButton(clicked = self._forwardClicked)
3374
3375 self._resultTimestamp = None
3376
3377 def activate(self):
3378 """Activate the SimBrief setup page"""
3379 config = self._wizard.gui.config
3380
3381 # self._userName.set_text(config.simBriefUserName)
3382 # self._userName.set_sensitive(True)
3383
3384 # self._password.set_text(config.simBriefPassword)
3385 # self._password.set_sensitive(True)
3386
3387 # self._rememberButton.set_active(config.rememberSimBriefPassword)
3388 # self._rememberButton.set_sensitive(True)
3389
3390 self._extraFuel.set_int(0)
3391 self._extraFuel.set_sensitive(True)
3392
3393 self._takeoffRunway.set_text("")
3394 self._takeoffRunway.set_sensitive(True)
3395
3396 self._landingRunway.set_text("")
3397 self._landingRunway.set_sensitive(True)
3398
3399 simBriefData = self._wizard.gui.flight.aircraft.simBriefData
3400 for (control, profiles) in [(self._climbProfile,
3401 simBriefData.climbProfiles),
3402 (self._cruiseProfile,
3403 simBriefData.cruiseProfiles),
3404 (self._descentProfile,
3405 simBriefData.descentProfiles)]:
3406 model = Gtk.ListStore(str)
3407 for profile in profiles:
3408 model.append([profile])
3409 control.set_model(model)
3410 control.set_sensitive(True)
3411
3412 self._climbProfile.set_active(0)
3413 self._cruiseProfile.set_active(0)
3414 self._descentProfile.set_active(0)
3415
3416 self._resultTimestamp = None
3417 self._waitEnd = None
3418
3419 self._updateForwardButton()
3420
3421 def _updateForwardButton(self):
3422 """Update the sensitivity of the forward button."""
3423 self._button.set_sensitive(True)
3424 # self._button.set_sensitive(len(self._userName.get_text())>0 and
3425 # len(self._password.get_text())>0)
3426
3427 def _backClicked(self, button):
3428 """Called when the Back button is pressed."""
3429 self.goBack()
3430
3431 def _forwardClicked(self, button):
3432 if self._completed:
3433 self._wizard.nextPage()
3434 else:
3435 # config = self._wizard.gui.config
3436
3437 # config.simBriefUserName = self._userName.get_text()
3438
3439 # rememberPassword = self._rememberButton.get_active()
3440 # config.simBriefPassword = \
3441 # self._password.get_text() if rememberPassword else ""
3442 # config.rememberSimBriefPassword = rememberPassword
3443
3444 # config.save()
3445
3446 self._wizard.gui.simulator.requestTime(self._handleTime)
3447
3448 def _handleTime(self, simulatorNow):
3449 """Handle the result of a time retrieval."""
3450 GObject.idle_add(self._processTime, simulatorNow)
3451
3452 def _processTime(self, simulatorNow):
3453 """Process the given time."""
3454 plan = self._getPlan(simulatorNow)
3455 print("plan:", plan)
3456
3457 takeoffRunway = self._takeoffRunway.get_text()
3458 if takeoffRunway:
3459 self._wizard.takeoffRunway = takeoffRunway
3460
3461 landingRunway = self._landingRunway.get_text()
3462 if landingRunway:
3463 self._wizard.landingRunway = landingRunway
3464
3465 # self._userName.set_sensitive(False)
3466 # self._password.set_sensitive(False)
3467 # self._rememberButton.set_sensitive(False)
3468 self._extraFuel.set_sensitive(False)
3469 self._takeoffRunway.set_sensitive(False)
3470 self._landingRunway.set_sensitive(False)
3471
3472 self._climbProfile.set_sensitive(False)
3473 self._cruiseProfile.set_sensitive(False)
3474 self._descentProfile.set_sensitive(False)
3475
3476 self._wizard.gui.beginBusy(xstr("simbrief_calling"))
3477
3478 url = MAVA_BASE_URL + "/simbrief_form.php?" + \
3479 urllib.parse.urlencode(plan)
3480 print("url:", url)
3481
3482 # GObject.idle_add(self._resultsAvailable,
3483 # { "orig_metar": "LDZA...",
3484 # "dest_metar": "LHBP..." })
3485 # return
3486
3487 webbrowser.open(url = url, new = 1)
3488
3489 # cef.callSimBrief(plan,
3490 # self._getCredentials,
3491 # self._simBriefProgress,
3492 # SimBriefSetupPage.getHTMLFilePath())
3493
3494 startSound(const.SOUND_NOTAM)
3495
3496 self._waitEnd = time.time() + SimBriefSetupPage._waitTimeout
3497 GObject.timeout_add(SimBriefSetupPage._resultQueryInterval,
3498 lambda: self._wizard.gui.webHandler. \
3499 getSimBriefResult(self._resultCallback,
3500 self._resultTimestamp))
3501
3502 # def _getCredentials(self, count):
3503 # """Get the credentials.
3504
3505 # If count is 0, the user name and password entered into the setup page
3506 # are returned. Otherwise a dialog box is displayed informing the user of
3507 # invalid credentials and requesting another set of them."""
3508 # print("_getCredentials", count)
3509 # if count==0:
3510 # return (self._userName.get_text(), self._password.get_text())
3511 # else:
3512 # gui = self._wizard.gui
3513 # config = gui.config
3514
3515 # dialog = SimBriefCredentialsDialog(gui,
3516 # config.simBriefUserName,
3517 # config.simBriefPassword,
3518 # config.rememberSimBriefPassword)
3519 # response = dialog.run()
3520
3521 # if response==Gtk.ResponseType.OK:
3522 # userName = dialog.userName
3523 # self._userName.set_text(userName)
3524 # password = dialog.password
3525 # self._password.set_text(password)
3526 # rememberPassword = dialog.rememberPassword
3527
3528 # config.simBriefUserName = userName
3529
3530 # config.simBriefPassword = \
3531 # password if rememberPassword else ""
3532 # config.rememberSimBriefPassword = rememberPassword
3533
3534 # config.save()
3535
3536 # return (userName, password)
3537 # else:
3538 # return (None, None)
3539
3540 # def _simBriefProgress(self, progress, result, flightInfo):
3541 # """The real SimBrief progress handler."""
3542 # print("_simBriefProgress", progress, result, flightInfo)
3543 # if result==cef.SIMBRIEF_RESULT_NONE:
3544 # message = SimBriefSetupPage.progress2Message.get(progress,
3545 # "simbrief_progress_unknown")
3546 # self._wizard.gui.updateBusyState(xstr(message))
3547 # else:
3548 # self._wizard.gui.endBusy()
3549
3550 # if result==cef.SIMBRIEF_RESULT_OK:
3551 # self._wizard.departureMETARChanged(flightInfo["orig_metar"],
3552 # self)
3553 # self._wizard.arrivalMETARChanged(flightInfo["dest_metar"], self)
3554 # self._wizard.nextPage()
3555 # else:
3556 # message = SimBriefSetupPage.result2Message.get(result,
3557 # "simbrief_result_unknown")
3558 # dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
3559 # type = Gtk.MessageType.ERROR,
3560 # message_format =
3561 # xstr(message) + "\n"+
3562 # xstr("simbrief_cancelled"))
3563
3564 # dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
3565 # dialog.set_title(WINDOW_TITLE_BASE)
3566 # secondary = xstr("flightsel_save_failed_sec")
3567 # dialog.format_secondary_markup(secondary)
3568 # dialog.run()
3569 # dialog.hide()
3570
3571 # self._wizard.usingSimBrief = False
3572 # self._wizard.jumpPage("fuel", fromPageShift = 1)
3573
3574 def _getPlan(self, simulatorNow):
3575 """Get the flight plan data for SimBrief."""
3576 print("timestamp:", simulatorNow)
3577
3578 self._resultTimestamp = now = int(time.time())
3579
3580 plan = {
3581 "airline": "MAH",
3582 "selcal": "XXXX",
3583 "fuelfactor": "P000",
3584 "contpct": "0.05",
3585 "resvrule": "45",
3586 "taxiout": "10",
3587 "taxiin": "10",
3588 "civalue": "AUTO",
3589 "lang": "hun" if getLanguage().lower()=="hu" else "eng",
3590 "sessionID": self._wizard._loginResult.sessionID,
3591 "timestamp": now
3592 }
3593
3594 wizard = self._wizard
3595 gui = wizard.gui
3596
3597 loginResult = wizard.loginResult
3598 plan["cpt"] = loginResult.pilotName
3599 plan["pid"] = loginResult.pilotID
3600
3601 bookedFlight = wizard.bookedFlight
3602 plan["fltnum"] = wizard.bookedFlight.callsign[2:]
3603 plan["type"] = const.icaoCodes[bookedFlight.aircraftType]
3604 plan["orig"] = bookedFlight.departureICAO
3605 plan["dest"] = bookedFlight.arrivalICAO
3606 plan["reg"] = bookedFlight.tailNumber
3607 plan["pax"] = str(bookedFlight.numPassengers)
3608
3609 departureTime = bookedFlight.departureTime
3610 print("departureTime", departureTime)
3611
3612 tm = time.gmtime(simulatorNow)
3613 diffMinutes = (departureTime.hour - tm.tm_hour)*60 + \
3614 departureTime.minute - tm.tm_min
3615 while diffMinutes<0:
3616 diffMinutes += 24*60
3617
3618 realDepartureTime = datetime.datetime.utcnow()
3619 realDepartureTime += datetime.timedelta(minutes = diffMinutes)
3620
3621 plan["date"] = "%02d%s%02d" % (realDepartureTime.day,
3622 SimBriefSetupPage.monthNum2Name[realDepartureTime.month-1],
3623 realDepartureTime.year%100)
3624 plan["deph"] = str(realDepartureTime.hour)
3625 plan["depm"] = str(realDepartureTime.minute)
3626
3627 arrivalTime = bookedFlight.arrivalTime
3628 blockMinutes = (arrivalTime.hour - departureTime.hour)*60 + \
3629 arrivalTime.minute - departureTime.minute
3630 while blockMinutes<0:
3631 blockMinutes += 24*60
3632 plan["steh"] = str(blockMinutes//60)
3633 plan["stem"] = str(blockMinutes%60)
3634
3635 plan["manualzfw"] = str(wizard.zfw / 1000.0)
3636 plan["cargo"] = str((wizard.bagWeight + wizard.cargoWeight + wizard.mailWeight)/1000.0)
3637
3638 plan["route"] = wizard.route
3639 plan["fl"] = str(wizard.filedCruiseAltitude)
3640 plan["altn"] = wizard.alternate
3641
3642 plan["addedfuel"] = str(self._extraFuel.get_int() / 1000.0)
3643 plan["origrwy"] = self._takeoffRunway.get_text()
3644 plan["destrwy"] = self._landingRunway.get_text()
3645
3646 for (key, control) in [("climb", self._climbProfile),
3647 ("cruise", self._cruiseProfile),
3648 ("descent", self._descentProfile)]:
3649 model = control.get_model()
3650 active = control.get_active_iter()
3651 value = model.get_value(active, 0)
3652 plan[key] = value
3653
3654 return plan
3655
3656 def _resultCallback(self, returned, result):
3657 """Callback for the SimBrief result query."""
3658 GObject.idle_add(self._handleResult, returned, result)
3659
3660 def _handleResult(self, returned, result):
3661 """Handle an RPC query result."""
3662 print("_handleResult", returned, result)
3663 if result.result and result.result.get("result"):
3664 link = result.result.get("result")
3665 if link=="<failed>":
3666 self._wizard.gui.endBusy()
3667
3668 self._finishWithError()
3669 else:
3670 link ="https://www.simbrief.com/ofp/flightplans/xml/" + link + ".xml"
3671
3672 thread = threading.Thread(target = self._getResults, args = (link,))
3673 thread.daemon = True
3674 thread.start()
3675
3676 elif time.time() >= self._waitEnd:
3677 self._wizard.gui.endBusy()
3678
3679 self._finishWithError()
3680 else:
3681 GObject.timeout_add(SimBriefSetupPage._resultQueryInterval,
3682 lambda: self._wizard.gui.webHandler. \
3683 getSimBriefResult(self._resultCallback,
3684 self._resultTimestamp))
3685
3686 def _upperChanged(self, entry, arg = None):
3687 """Called when the value of some entry widget has changed and the value
3688 should be converted to uppercase."""
3689 entry.set_text(entry.get_text().upper())
3690
3691 def _getResults(self, link):
3692 """Get the result from the given link."""
3693 ## Holds analysis data to be used
3694 flightInfo = {}
3695 try:
3696 availableInfo = {}
3697
3698 # Obtaining the xml
3699 response = urllib.request.urlopen(link,
3700 cafile = certifi.where())
3701 content = etree.iterparse(response)
3702
3703 for (action, element) in content:
3704 # Processing tags that occur multiple times
3705 if element.tag == "weather":
3706 weatherElementList = list(element)
3707 for weatherElement in weatherElementList:
3708 flightInfo[weatherElement.tag] = weatherElement.text
3709 else:
3710 availableInfo[element.tag] = element.text
3711
3712 # Processing plan_html
3713 ## Obtaining chart links
3714 imageLinks = []
3715 for imageLinkElement in lxml.html.find_class(availableInfo["plan_html"],
3716 "ofpmaplink"):
3717 for imageLink in imageLinkElement.iterlinks():
3718 if imageLink[1] == 'src':
3719 imageLinks.append(imageLink[2])
3720 flightInfo["image_links"] = imageLinks
3721 print((sorted(availableInfo.keys())))
3722 htmlFilePath = SimBriefSetupPage.getHTMLFilePath()
3723 with open(htmlFilePath, 'w') as f:
3724 f.write(availableInfo["plan_html"])
3725 except Exception as e:
3726 print("_getResults", e)
3727
3728 GObject.idle_add(self._resultsAvailable, flightInfo)
3729
3730 def _resultsAvailable(self, flightInfo):
3731 """Called from the result retrieval thread when the result is
3732 available."""
3733 print("_resultsAvailable")
3734 self._wizard.gui.endBusy()
3735
3736 if flightInfo:
3737 self._wizard.departureMETARChanged(flightInfo["orig_metar"],
3738 self)
3739 self._wizard.arrivalMETARChanged(flightInfo["dest_metar"], self)
3740 self._wizard.nextPage()
3741 else:
3742 self._finishWithError()
3743
3744 def _finishWithError(self):
3745 """Display an error dialog, and when it is accepted, cancel
3746 SimBrief briefing and jump to the fuel page."""
3747 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
3748 type = Gtk.MessageType.ERROR,
3749 message_format =
3750 xstr("simbrief_result_error_other") + "\n"+
3751 xstr("simbrief_cancelled"))
3752
3753 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
3754 dialog.set_title(WINDOW_TITLE_BASE)
3755 secondary = xstr("flightsel_save_failed_sec")
3756 dialog.format_secondary_markup(secondary)
3757 dialog.run()
3758 dialog.hide()
3759
3760 self._wizard.usingSimBrief = False
3761 self._wizard.jumpPage("fuel", fromPageShift = 1)
3762
3763
3764
3765#-----------------------------------------------------------------------------
3766
3767class SimBriefingPage(Page):
3768 """Page to display the SimBrief HTML briefing."""
3769 class BrowserLifeSpanHandler(object):
3770 """The life-span handler of a browser."""
3771 def __init__(self, simBriefingPage):
3772 """Construct the life-span handler for the given page."""
3773 self._simBriefingPage = simBriefingPage
3774
3775 def OnBeforeClose(self, browser):
3776 """Called before closing the browser."""
3777 self._simBriefingPage._invalidateBrowser()
3778
3779 def __init__(self, wizard):
3780 """Construct the setup page."""
3781
3782 super(SimBriefingPage, self).__init__(wizard, "simbrief_result",
3783 xstr("simbrief_result_title"), "")
3784
3785 self._alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3786 xscale = 1.0, yscale = 1.0)
3787
3788 self._container = cef.getContainer()
3789 self._alignment.add(self._container)
3790
3791 self.setMainWidget(self._alignment)
3792
3793 self._browser = None
3794
3795 self.addCancelFlightButton()
3796
3797 self.addPreviousButton(clicked = self._backClicked)
3798
3799 self._button = self.addNextButton(clicked = self._forwardClicked)
3800 self._button.set_label(xstr("briefing_button"))
3801 self._button.set_has_tooltip(False)
3802 self._button.set_use_stock(False)
3803
3804 def prepareShow(self):
3805 """Prepare the page for showing (again)."""
3806 if self._browser is None:
3807 self._startBrowser()
3808 else:
3809 self._browser.Reload()
3810
3811 def prepareHide(self):
3812 """Prepare the page for hiding."""
3813 if os.name!="nt":
3814 self._browser.CloseBrowser(False)
3815
3816 def grabDefault(self):
3817 """If the page has a default button, make it the default one."""
3818 super(SimBriefingPage, self).grabDefault()
3819
3820 if self._browser is None:
3821 self._startBrowser()
3822
3823 def _backClicked(self, button):
3824 """Called when the Back button has been pressed."""
3825 self.goBack()
3826
3827 def _forwardClicked(self, button):
3828 """Called when the Forward button has been pressed."""
3829 if not self._completed:
3830 self._button.set_label(xstr("button_next"))
3831 self._button.set_tooltip_text(xstr("button_next_tooltip"))
3832 self._wizard.usingSimBrief = True
3833 self.complete()
3834
3835 self._wizard.nextPage()
3836
3837 def _startBrowser(self):
3838 """Start the browser.
3839
3840 If a container is needed, create one."""
3841 if self._container is None:
3842 self._container = cef.getContainer()
3843 self._alignment.add(self._container)
3844 else:
3845 self._container.show()
3846
3847 url = "file://" + SimBriefSetupPage.getHTMLFilePath()
3848 self._browser = cef.startInContainer(self._container, url)
3849
3850 lifeSpanHandler = SimBriefingPage.BrowserLifeSpanHandler(self)
3851 self._browser.SetClientHandler(lifeSpanHandler)
3852
3853 def _invalidateBrowser(self):
3854 """Invalidate the browser (and associated stuff)."""
3855 self._container.hide()
3856 self._browser = None
3857
3858#-----------------------------------------------------------------------------
3859
3860class FuelTank(Gtk.VBox):
3861 """Widget for the fuel tank."""
3862 def __init__(self, fuelTank, name, capacity, currentWeight):
3863 """Construct the widget for the tank with the given name."""
3864 super(FuelTank, self).__init__()
3865
3866 self._enabled = True
3867 self.fuelTank = fuelTank
3868 self.capacity = capacity
3869 self.currentWeight = currentWeight
3870 self.expectedWeight = currentWeight
3871
3872 self._label = label = Gtk.Label("<b>" + name + "</b>")
3873 label.set_use_markup(True)
3874 label.set_use_underline(True)
3875 label.set_justify(Gtk.Justification.CENTER)
3876 label.set_alignment(0.5, 1.0)
3877
3878 self._tankFigure = Gtk.EventBox()
3879 self._tankFigure.set_size_request(38, 200)
3880 self._tankFigure.set_visible_window(False)
3881 self._tankFigure.set_tooltip_markup(xstr("fuel_tank_tooltip"))
3882
3883 self._tankFigure.connect("draw", self._drawTankFigure)
3884 self._tankFigure.connect("button_press_event", self._buttonPressed)
3885 self._tankFigure.connect("motion_notify_event", self._motionNotify)
3886
3887 self._tankFigure.add_events(Gdk.EventMask.SCROLL_MASK)
3888 self._tankFigure.connect("scroll-event", self._scrolled)
3889
3890 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3891 xscale = 0.0, yscale = 1.0)
3892 alignment.add(self._tankFigure)
3893
3894 self.pack_start(alignment, True, True, 4)
3895
3896 self._expectedButton = Gtk.SpinButton()
3897 self._expectedButton.set_numeric(True)
3898 self._expectedButton.set_range(0, self.capacity)
3899 self._expectedButton.set_increments(10, 100)
3900 self._expectedButton.set_value(currentWeight)
3901 self._expectedButton.set_alignment(1.0)
3902 self._expectedButton.set_width_chars(5)
3903 self._expectedButton.connect("value-changed", self._expectedChanged)
3904
3905 label.set_mnemonic_widget(self._expectedButton)
3906
3907 @property
3908 def label(self):
3909 """Get the label with the caption."""
3910 return self._label
3911
3912 @property
3913 def expectedButton(self):
3914 """Get the button containing the expected value."""
3915 return self._expectedButton
3916
3917 def setCurrent(self, currentWeight):
3918 """Set the current weight."""
3919 self.currentWeight = currentWeight
3920 self._redraw()
3921
3922 def isCorrect(self):
3923 """Determine if the contents of the fuel tank are as expected"""
3924 return abs(self.expectedWeight - self.currentWeight)<=1
3925
3926 def disable(self):
3927 """Disable the fuel tank."""
3928 self._expectedButton.set_sensitive(False)
3929 self._enabled = False
3930
3931 def _redraw(self):
3932 """Redraw the tank figure."""
3933 self._tankFigure.queue_draw()
3934
3935 def _drawTankFigure(self, tankFigure, eventOrContext):
3936 """Draw the tank figure."""
3937 triangleSize = 5
3938
3939 context = eventOrContext
3940 (xOffset, yOffset) = (0, 0)
3941
3942 width = tankFigure.get_allocated_width()
3943 height = tankFigure.get_allocated_height()
3944
3945 rectangleX0 = triangleSize
3946 rectangleY0 = triangleSize
3947 rectangleX1 = width - 1 - triangleSize
3948 rectangleY1 = height - 1 - triangleSize
3949 rectangleLineWidth = 2.0
3950
3951 context.set_source_rgb(0.0, 0.0, 0.0)
3952 context.set_line_width(rectangleLineWidth)
3953 context.rectangle(xOffset + rectangleX0 + rectangleLineWidth/2,
3954 yOffset + rectangleY0 + rectangleLineWidth/2,
3955 rectangleX1 - rectangleX0 - rectangleLineWidth,
3956 rectangleY1 - rectangleY0 - rectangleLineWidth)
3957 context.stroke()
3958
3959 rectangleInnerLeft = rectangleX0 + rectangleLineWidth
3960 rectangleInnerRight = rectangleX1 - rectangleLineWidth
3961 self._rectangleInnerTop = rectangleInnerTop = rectangleY0 + rectangleLineWidth
3962 self._rectangleInnerBottom = rectangleInnerBottom = rectangleY1 - rectangleLineWidth
3963
3964 rectangleInnerWidth = rectangleInnerRight - rectangleInnerLeft
3965 rectangleInnerHeight = rectangleInnerBottom - rectangleInnerTop
3966
3967 context.set_source_rgb(1.0, 0.9, 0.6)
3968 currentHeight = self.currentWeight * rectangleInnerHeight / self.capacity
3969 currentX = rectangleInnerTop + rectangleInnerHeight - currentHeight
3970 context.rectangle(xOffset + rectangleInnerLeft,
3971 yOffset + rectangleInnerTop +
3972 rectangleInnerHeight - currentHeight,
3973 rectangleInnerWidth, currentHeight)
3974 context.fill()
3975
3976 expectedHeight = self.expectedWeight * rectangleInnerHeight / self.capacity
3977 expectedY = rectangleInnerTop + rectangleInnerHeight - expectedHeight
3978
3979 context.set_line_width(1.5)
3980 context.set_source_rgb(0.0, 0.85, 0.85)
3981 context.move_to(xOffset + rectangleX0, yOffset + expectedY)
3982 context.line_to(xOffset + rectangleX1, yOffset + expectedY)
3983 context.stroke()
3984
3985 context.set_line_width(0.0)
3986 context.move_to(xOffset + 0, yOffset + expectedY - triangleSize)
3987 context.line_to(xOffset + 0, yOffset + expectedY + triangleSize)
3988 context.line_to(xOffset + rectangleX0 + 1, yOffset + expectedY)
3989 context.line_to(xOffset + 0, yOffset + expectedY - triangleSize)
3990 context.fill()
3991
3992 context.set_line_width(0.0)
3993 context.move_to(xOffset + width, yOffset + expectedY - triangleSize)
3994 context.line_to(xOffset + width, yOffset + expectedY + triangleSize)
3995 context.line_to(xOffset + rectangleX1 - 1, yOffset + expectedY)
3996 context.line_to(xOffset + width, yOffset + expectedY - triangleSize)
3997 context.fill()
3998
3999 return True
4000
4001 def _setExpectedFromY(self, y):
4002 """Set the expected weight from the given Y-coordinate."""
4003 level = (self._rectangleInnerBottom - y) / \
4004 (self._rectangleInnerBottom - self._rectangleInnerTop)
4005 level = min(1.0, max(0.0, level))
4006 self._expectedButton.set_value(level * self.capacity)
4007
4008 def _buttonPressed(self, tankFigure, event):
4009 """Called when a button is pressed in the figure.
4010
4011 The expected level will be set there."""
4012 if self._enabled and event.button==1:
4013 self._setExpectedFromY(event.y)
4014
4015 def _motionNotify(self, tankFigure, event):
4016 """Called when the mouse pointer moves within the area of a tank figure."""
4017 if self._enabled and event.state==Gdk.ModifierType.BUTTON1_MASK:
4018 self._setExpectedFromY(event.y)
4019
4020 def _scrolled(self, tankFigure, event):
4021 """Called when a scroll event is received."""
4022 if self._enabled:
4023 increment = 1 if event.state==Gdk.ModifierType.CONTROL_MASK \
4024 else 100 if event.state==Gdk.ModifierType.SHIFT_MASK \
4025 else 10 if event.state==0 else 0
4026 if increment!=0:
4027 if event.direction==Gdk.ScrollDirection.DOWN:
4028 increment *= -1
4029 self._expectedButton.spin(Gtk.SpinType.USER_DEFINED, increment)
4030
4031 def _expectedChanged(self, spinButton):
4032 """Called when the expected value has changed."""
4033 self.expectedWeight = spinButton.get_value_as_int()
4034 self._redraw()
4035
4036#-----------------------------------------------------------------------------
4037
4038class FuelPage(Page):
4039 """The page containing the fuel tank filling."""
4040 _pumpStep = 0.02
4041
4042 def __init__(self, wizard):
4043 """Construct the page."""
4044 super(FuelPage, self).__init__(wizard, "fuel",
4045 xstr("fuel_title"),
4046 xstr("fuel_help_pre") +
4047 xstr("fuel_help_post"),
4048 completedHelp = xstr("fuel_chelp"))
4049
4050 self._fuelTanks = []
4051 self._fuelTable = None
4052 self._fuelAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4053 xscale = 0.0, yscale = 1.0)
4054 self.setMainWidget(self._fuelAlignment)
4055
4056 tankData = [(tank, 2500, 3900) for tank in acft.mostFuelTanks]
4057 self._setupTanks(tankData)
4058
4059 self.addCancelFlightButton()
4060
4061 self._backButton = self.addPreviousButton(clicked = self._backClicked)
4062 self._button = self.addNextButton(clicked = self._forwardClicked)
4063
4064 self._pumpIndex = 0
4065
4066 def activate(self):
4067 """Activate the page."""
4068 self._setupTanks(self._wizard._fuelData)
4069
4070 aircraft = self._wizard.gui.flight.aircraft
4071 minLandingFuel = aircraft.minLandingFuel
4072 recommendedLandingFuel = aircraft.recommendedLandingFuel
4073
4074 middleHelp = "" if minLandingFuel is None else \
4075 (xstr("fuel_help_min") % (minLandingFuel,)) \
4076 if recommendedLandingFuel is None else \
4077 (xstr("fuel_help_min_rec") % (minLandingFuel,
4078 recommendedLandingFuel))
4079 self.setHelp(xstr("fuel_help_pre") + middleHelp + xstr("fuel_help_post"))
4080
4081 def finalize(self):
4082 """Finalize the page."""
4083 for fuelTank in self._fuelTanks:
4084 fuelTank.disable()
4085
4086 def _backClicked(self, button):
4087 """Called when the Back button is pressed."""
4088 self.goBack()
4089
4090 def _forwardClicked(self, button):
4091 """Called when the forward button is clicked."""
4092 if not self._completed:
4093 self._pumpIndex = 0
4094 self._wizard.gui.beginBusy(xstr("fuel_pump_busy"))
4095 self._pump()
4096 elif self._wizard.usingSimBrief:
4097 self._wizard.jumpPage("takeoff")
4098 else:
4099 self._wizard.jumpPage("briefing1")
4100
4101 def _setupTanks(self, tankData):
4102 """Setup the tanks for the given data."""
4103 numTanks = len(tankData)
4104 if self._fuelTable is not None:
4105 self._fuelAlignment.remove(self._fuelTable)
4106
4107 self._fuelTanks = []
4108 self._fuelTable = Gtk.Grid()
4109 self._fuelTable.set_column_homogeneous(True)
4110 self._fuelTable.set_row_spacing(4)
4111 self._fuelTable.set_column_spacing(16 if numTanks<5 else 0)
4112 index = 0
4113 for (tank, current, capacity) in tankData:
4114 fuelTank = FuelTank(tank,
4115 xstr("fuel_tank_" +
4116 const.fuelTank2string(tank)),
4117 capacity, current)
4118 self._fuelTanks.append(fuelTank)
4119
4120 alignment = Gtk.Alignment(xalign = 0.5, yalign = 1.0,
4121 xscale = 1.0, yscale = 0.0)
4122 alignment.add(fuelTank.label)
4123 self._fuelTable.attach(alignment,
4124 index*(1 if numTanks<5 else 2), 0,
4125 1 if numTanks<5 else 3, 1)
4126
4127 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4128 xscale = 0.0, yscale = 1.0)
4129 alignment.add(fuelTank)
4130 self._fuelTable.attach(alignment,
4131 index*(1 if numTanks<5 else 2) +
4132 (0 if numTanks<5 else 1), 1, 1, 1)
4133
4134
4135 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4136 xscale = 1.0, yscale = 0.0)
4137 alignment.add(fuelTank.expectedButton)
4138
4139 self._fuelTable.attach(alignment,
4140 index* (1 if numTanks<5 else 2),
4141 2 if (index%2)==0 or numTanks<5 else 3,
4142 1 if numTanks<5 else 3, 1)
4143
4144 index += 1
4145
4146 self._fuelAlignment.add(self._fuelTable)
4147 self.show_all()
4148
4149 def _pump(self):
4150 """Perform one step of pumping.
4151
4152 It is checked, if the current tank's contents are of the right
4153 quantity. If not, it is filled one step further to the desired
4154 contents. Otherwise the next tank is started. If all tanks are are
4155 filled, the next page is selected."""
4156 numTanks = len(self._fuelTanks)
4157
4158 fuelTank = None
4159 while self._pumpIndex < numTanks:
4160 fuelTank = self._fuelTanks[self._pumpIndex]
4161 if fuelTank.isCorrect():
4162 self._pumpIndex += 1
4163 fuelTank = None
4164 else:
4165 break
4166
4167 if fuelTank is None:
4168 self._wizard.gui.endBusy()
4169 if self._wizard.usingSimBrief:
4170 self._wizard.gui.startMonitoring()
4171 self._wizard.jumpPage("takeoff")
4172 else:
4173 bookedFlight = self._wizard._bookedFlight
4174 self._wizard.gui.beginBusy(xstr("route_down_notams"))
4175 self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
4176 bookedFlight.departureICAO,
4177 bookedFlight.arrivalICAO)
4178 startSound(const.SOUND_NOTAM)
4179 else:
4180 currentLevel = fuelTank.currentWeight / fuelTank.capacity
4181 expectedLevel = fuelTank.expectedWeight / fuelTank.capacity
4182 if currentLevel<expectedLevel:
4183 currentLevel += FuelPage._pumpStep
4184 if currentLevel>expectedLevel: currentLevel = expectedLevel
4185 else:
4186 currentLevel -= FuelPage._pumpStep
4187 if currentLevel<expectedLevel: currentLevel = expectedLevel
4188 fuelTank.setCurrent(currentLevel * fuelTank.capacity)
4189 self._wizard.gui.simulator.setFuelLevel([(fuelTank.fuelTank,
4190 currentLevel)])
4191 GObject.timeout_add(50, self._pump)
4192
4193 def _notamsCallback(self, returned, result):
4194 """Callback for the NOTAMs."""
4195 GObject.idle_add(self._handleNOTAMs, returned, result)
4196
4197 def _handleNOTAMs(self, returned, result):
4198 """Handle the NOTAMs."""
4199 if returned:
4200 self._wizard._departureNOTAMs = result.departureNOTAMs
4201 self._wizard._arrivalNOTAMs = result.arrivalNOTAMs
4202 else:
4203 self._wizard._departureNOTAMs = None
4204 self._wizard._arrivalNOTAMs = None
4205
4206 bookedFlight = self._wizard._bookedFlight
4207 self._wizard.gui.beginBusy(xstr("route_down_metars"))
4208 self._wizard.gui.webHandler.getMETARs(self._metarsCallback,
4209 [bookedFlight.departureICAO,
4210 bookedFlight.arrivalICAO])
4211
4212 def _metarsCallback(self, returned, result):
4213 """Callback for the METARs."""
4214 GObject.idle_add(self._handleMETARs, returned, result)
4215
4216 def _handleMETARs(self, returned, result):
4217 """Handle the METARs."""
4218 self._wizard._departureMETAR = None
4219 self._wizard._arrivalMETAR = None
4220 bookedFlight = self._wizard._bookedFlight
4221 if returned:
4222 if bookedFlight.departureICAO in result.metars:
4223 self._wizard._departureMETAR = result.metars[bookedFlight.departureICAO]
4224 if bookedFlight.arrivalICAO in result.metars:
4225 self._wizard._arrivalMETAR = result.metars[bookedFlight.arrivalICAO]
4226
4227 self._wizard.gui.endBusy()
4228 self._backButton.set_sensitive(True)
4229 self._button.set_sensitive(True)
4230 self._wizard.nextPage()
4231
4232#-----------------------------------------------------------------------------
4233
4234class BriefingPage(Page):
4235 """Page for the briefing."""
4236 def __init__(self, wizard, departure):
4237 """Construct the briefing page."""
4238 self._departure = departure
4239
4240 number = 1 if departure else 2
4241
4242 title = xstr("briefing_title") % (number,
4243 xstr("briefing_departure")
4244 if departure
4245 else xstr("briefing_arrival"))
4246 super(BriefingPage, self).__init__(wizard,
4247 "briefing%d" % (number,),
4248 title, xstr("briefing_help"),
4249 completedHelp = xstr("briefing_chelp"))
4250
4251 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4252 xscale = 1.0, yscale = 1.0)
4253
4254 mainBox = Gtk.VBox()
4255 alignment.add(mainBox)
4256 self.setMainWidget(alignment)
4257
4258 self._notamsFrame = Gtk.Frame()
4259 self._notamsFrame.set_label(xstr("briefing_notams_init"))
4260 scrolledWindow = Gtk.ScrolledWindow()
4261 scrolledWindow.set_size_request(-1, 128)
4262 # FIXME: these constants should be in common
4263 scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
4264 Gtk.PolicyType.AUTOMATIC)
4265 self._notams = Gtk.TextView()
4266 self._notams.set_editable(False)
4267 self._notams.set_accepts_tab(False)
4268 self._notams.set_wrap_mode(Gtk.WrapMode.WORD)
4269 scrolledWindow.add(self._notams)
4270 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4271 xscale = 1.0, yscale = 1.0)
4272 alignment.set_padding(padding_top = 4, padding_bottom = 0,
4273 padding_left = 0, padding_right = 0)
4274 alignment.add(scrolledWindow)
4275 self._notamsFrame.add(alignment)
4276 mainBox.pack_start(self._notamsFrame, True, True, 4)
4277
4278 self._metarFrame = Gtk.Frame()
4279 self._metarFrame.set_label(xstr("briefing_metar_init"))
4280 scrolledWindow = Gtk.ScrolledWindow()
4281 scrolledWindow.set_size_request(-1, 32)
4282 scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
4283 Gtk.PolicyType.AUTOMATIC)
4284
4285 self._updatingMETAR = False
4286
4287 self._metar = Gtk.TextView()
4288 self._metar.set_accepts_tab(False)
4289 self._metar.set_wrap_mode(Gtk.WrapMode.WORD)
4290 self._metar.get_buffer().connect("changed", self._metarChanged)
4291 self._metar.get_buffer().connect_after("insert-text", self._metarInserted)
4292 scrolledWindow.add(self._metar)
4293 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4294 xscale = 1.0, yscale = 1.0)
4295 alignment.set_padding(padding_top = 4, padding_bottom = 0,
4296 padding_left = 0, padding_right = 0)
4297 alignment.add(scrolledWindow)
4298 self._metarFrame.add(alignment)
4299 mainBox.pack_start(self._metarFrame, True, True, 4)
4300 self.metarEdited = False
4301
4302 self.addCancelFlightButton()
4303
4304 self.addPreviousButton(clicked = self._backClicked)
4305 self._button = self.addNextButton(clicked = self._forwardClicked)
4306
4307 @property
4308 def metar(self):
4309 """Get the METAR on the page."""
4310 buffer = self._metar.get_buffer()
4311 return buffer.get_text(buffer.get_start_iter(),
4312 buffer.get_end_iter(), True)
4313
4314 def setMETAR(self, metar):
4315 """Set the METAR."""
4316 self._metar.get_buffer().set_text(metar)
4317 self.metarEdited = False
4318
4319 def changeMETAR(self, metar):
4320 """Change the METAR as a result of an edit on one of the other
4321 pages."""
4322 self._updatingMETAR = True
4323 self._metar.get_buffer().set_text(metar)
4324 self._updatingMETAR = False
4325
4326 self._updateButton()
4327 self.metarEdited = True
4328
4329 def activate(self):
4330 """Activate the page."""
4331 if not self._departure:
4332 self._button.set_label(xstr("briefing_button"))
4333 self._button.set_has_tooltip(False)
4334 self._button.set_use_stock(False)
4335
4336 bookedFlight = self._wizard._bookedFlight
4337
4338 icao = bookedFlight.departureICAO if self._departure \
4339 else bookedFlight.arrivalICAO
4340 notams = self._wizard._departureNOTAMs if self._departure \
4341 else self._wizard._arrivalNOTAMs
4342 metar = self._wizard._departureMETAR if self._departure \
4343 else self._wizard._arrivalMETAR
4344
4345 self._notamsFrame.set_label(xstr("briefing_notams_template") % (icao,))
4346 buffer = self._notams.get_buffer()
4347 if notams is None:
4348 buffer.set_text(xstr("briefing_notams_failed"))
4349 elif not notams:
4350 buffer.set_text(xstr("briefing_notams_missing"))
4351 else:
4352 s = ""
4353 for notam in notams:
4354 s += str(notam)
4355 s += "-------------------- * --------------------\n"
4356 buffer.set_text(s)
4357
4358 self._metarFrame.set_label(xstr("briefing_metar_template") % (icao,))
4359 buffer = self._metar.get_buffer()
4360 self._updatingMETAR = True
4361 if metar is None:
4362 buffer.set_text("")
4363 self.setHelp(xstr("briefing_help_nometar"))
4364 else:
4365 buffer.set_text(metar)
4366 self._updatingMETAR = False
4367 self._updateButton()
4368
4369 label = self._metarFrame.get_label_widget()
4370 label.set_use_underline(True)
4371 label.set_mnemonic_widget(self._metar)
4372
4373 self.metarEdited = False
4374
4375 def _backClicked(self, button):
4376 """Called when the Back button is pressed."""
4377 self.goBack()
4378
4379 def _forwardClicked(self, button):
4380 """Called when the forward button is clicked."""
4381 if not self._departure:
4382 if not self._completed:
4383 self._wizard.gui.startMonitoring()
4384 self._button.set_label(xstr("button_next"))
4385 self._button.set_tooltip_text(xstr("button_next_tooltip"))
4386 self.complete()
4387
4388 self._wizard.nextPage()
4389
4390 def _metarChanged(self, buffer):
4391 """Called when the METAR has changed."""
4392 print("BriefingPage.metarChanged", self._updatingMETAR)
4393 if not self._updatingMETAR:
4394 self.metarEdited = True
4395 self._updateButton()
4396 metar = buffer.get_text(buffer.get_start_iter(),
4397 buffer.get_end_iter(), True)
4398 self._wizard.metarChanged(metar, self)
4399
4400 def _metarInserted(self, textBuffer, iter, text, length):
4401 """Called when new characters are inserted into the METAR.
4402
4403 It uppercases all characters."""
4404 print("BriefingPage.metarInserted", self._updatingMETAR)
4405 if not self._updatingMETAR:
4406 self._updatingMETAR = True
4407
4408 iter1 = iter.copy()
4409 iter1.backward_chars(length)
4410 textBuffer.delete(iter, iter1)
4411
4412 textBuffer.insert(iter, text.upper())
4413
4414 self._updatingMETAR = False
4415
4416 def _updateButton(self):
4417 """Update the sensitivity of the Next button based on the contents of
4418 the METAR field."""
4419 buffer = self._metar.get_buffer()
4420 self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
4421 buffer.get_end_iter(),
4422 True)!="")
4423
4424
4425#-----------------------------------------------------------------------------
4426
4427class TakeoffPage(Page):
4428 """Page for entering the takeoff data."""
4429 def __init__(self, wizard):
4430 """Construct the takeoff page."""
4431 super(TakeoffPage, self).__init__(wizard, "takeoff",
4432 xstr("takeoff_title"),
4433 xstr("takeoff_help"),
4434 completedHelp = xstr("takeoff_chelp"))
4435
4436 self._forwardAllowed = False
4437
4438 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4439 xscale = 0.0, yscale = 0.0)
4440
4441 table = Gtk.Table(9, 24)
4442 table.set_row_spacings(4)
4443 table.set_col_spacings(16)
4444 table.set_homogeneous(False)
4445 alignment.add(table)
4446 self.setMainWidget(alignment)
4447
4448 row = 0
4449
4450 label = Gtk.Label(xstr("takeoff_metar"))
4451 label.set_use_underline(True)
4452 label.set_alignment(0.0, 0.5)
4453 table.attach(label, 0, 1, row, row+1)
4454
4455 self._metar = Gtk.Entry()
4456 self._metar.set_width_chars(40)
4457 self._metar.set_tooltip_text(xstr("takeoff_metar_tooltip"))
4458 self._metar.connect("changed", self._metarChanged)
4459 self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
4460 table.attach(self._metar, 1, 24, row, row+1)
4461 label.set_mnemonic_widget(self._metar)
4462
4463 self._updatingMETAR = False
4464
4465 row += 1
4466
4467 label = Gtk.Label(xstr("takeoff_runway"))
4468 label.set_use_underline(True)
4469 label.set_alignment(0.0, 0.5)
4470 table.attach(label, 0, 1, row, row+1)
4471
4472 self._runway = Gtk.Entry()
4473 self._runway.set_width_chars(10)
4474 self._runway.set_tooltip_text(xstr("takeoff_runway_tooltip"))
4475 self._runway.connect("changed", self._upperChanged)
4476 table.attach(self._runway, 1, 3, row, row+1)
4477 label.set_mnemonic_widget(self._runway)
4478
4479 row += 1
4480
4481 label = Gtk.Label(xstr("takeoff_sid"))
4482 label.set_use_underline(True)
4483 label.set_alignment(0.0, 0.5)
4484 table.attach(label, 0, 1, row, row+1)
4485
4486 self._sid = Gtk.ComboBox.new_with_model_and_entry(comboModel)
4487
4488 self._sid.set_entry_text_column(0)
4489 self._sid.get_child().set_width_chars(10)
4490 self._sid.set_tooltip_text(xstr("takeoff_sid_tooltip"))
4491 self._sid.connect("changed", self._upperChangedComboBox)
4492 table.attach(self._sid, 1, 3, row, row+1)
4493 label.set_mnemonic_widget(self._sid)
4494
4495 row += 1
4496
4497 label = Gtk.Label(xstr("takeoff_v1"))
4498 label.set_use_markup(True)
4499 label.set_use_underline(True)
4500 label.set_alignment(0.0, 0.5)
4501 table.attach(label, 0, 1, row, row+1)
4502
4503 self._v1 = IntegerEntry()
4504 self._v1.set_width_chars(4)
4505 self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip_knots"))
4506 self._v1.connect("integer-changed", self._valueChanged)
4507 table.attach(self._v1, 2, 3, row, row+1)
4508 label.set_mnemonic_widget(self._v1)
4509
4510 self._v1Unit = Gtk.Label(xstr("label_knots"))
4511 self._v1Unit.set_alignment(0.0, 0.5)
4512 table.attach(self._v1Unit, 3, 4, row, row+1)
4513
4514 row += 1
4515
4516 label = Gtk.Label(xstr("takeoff_vr"))
4517 label.set_use_markup(True)
4518 label.set_use_underline(True)
4519 label.set_alignment(0.0, 0.5)
4520 table.attach(label, 0, 1, row, row+1)
4521
4522 self._vr = IntegerEntry()
4523 self._vr.set_width_chars(4)
4524 self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip_knots"))
4525 self._vr.connect("integer-changed", self._valueChanged)
4526 table.attach(self._vr, 2, 3, row, row+1)
4527 label.set_mnemonic_widget(self._vr)
4528
4529 self._vrUnit = Gtk.Label(xstr("label_knots"))
4530 self._vrUnit.set_alignment(0.0, 0.5)
4531 table.attach(self._vrUnit, 3, 4, row, row+1)
4532
4533 row += 1
4534
4535 label = Gtk.Label(xstr("takeoff_v2"))
4536 label.set_use_markup(True)
4537 label.set_use_underline(True)
4538 label.set_alignment(0.0, 0.5)
4539 table.attach(label, 0, 1, row, row+1)
4540
4541 self._v2 = IntegerEntry()
4542 self._v2.set_width_chars(4)
4543 self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip_knots"))
4544 self._v2.connect("integer-changed", self._valueChanged)
4545 table.attach(self._v2, 2, 3, row, row+1)
4546 label.set_mnemonic_widget(self._v2)
4547
4548 self._v2Unit = Gtk.Label(xstr("label_knots"))
4549 self._v2Unit.set_alignment(0.0, 0.5)
4550 table.attach(self._v2Unit, 3, 4, row, row+1)
4551
4552 row += 1
4553
4554 self._derateType = acft.DERATE_NONE
4555
4556 self._derateLabel = Gtk.Label()
4557 self._derateLabel.set_use_underline(True)
4558 self._derateLabel.set_markup(xstr("takeoff_derate_tupolev"))
4559 self._derateLabel.set_alignment(0.0, 0.5)
4560 table.attach(self._derateLabel, 0, 1, row, row+1)
4561
4562 self._derate = Gtk.Alignment()
4563 table.attach(self._derate, 2, 4, row, row+1)
4564 self._derateWidget = None
4565 self._derateEntry = None
4566 self._derateUnit = None
4567 self._derateButtons = None
4568
4569 row += 1
4570
4571 self._antiIceOn = Gtk.CheckButton(xstr("takeoff_antiice"))
4572 self._antiIceOn.set_use_underline(True)
4573 self._antiIceOn.set_tooltip_text(xstr("takeoff_antiice_tooltip"))
4574 table.attach(self._antiIceOn, 2, 4, row, row+1)
4575
4576 row += 1
4577
4578 self._rto = Gtk.CheckButton(xstr("takeoff_rto"))
4579 self._rto.set_use_underline(True)
4580 self._rto.set_tooltip_text(xstr("takeoff_rto_tooltip"))
4581 self._rto.connect("toggled", self._rtoToggled)
4582 table.attach(self._rto, 2, 4, row, row+1, ypadding = 8)
4583
4584 self.addCancelFlightButton()
4585
4586 self.addPreviousButton(clicked = self._backClicked)
4587
4588 self._button = self.addNextButton(clicked = self._forwardClicked)
4589
4590 self._active = False
4591
4592 @property
4593 def runway(self):
4594 """Get the runway."""
4595 return self._runway.get_text()
4596
4597 @property
4598 def sid(self):
4599 """Get the SID."""
4600 text = self._sid.get_child().get_text()
4601 return text if self._sid.get_active()!=0 and text and text!="N/A" \
4602 else None
4603
4604 @property
4605 def v1(self):
4606 """Get the v1 speed."""
4607 return self._v1.get_int()
4608
4609 @property
4610 def vr(self):
4611 """Get the vr speed."""
4612 return self._vr.get_int()
4613
4614 @property
4615 def v2(self):
4616 """Get the v2 speed."""
4617 return self._v2.get_int()
4618
4619 @property
4620 def derate(self):
4621 """Get the derate value, if any."""
4622 if self._derateWidget is None:
4623 return None
4624 if self._derateType==acft.DERATE_BOEING:
4625 derate = self._derateEntry.get_text()
4626 return derate if derate else None
4627 elif self._derateType==acft.DERATE_EPR:
4628 derate = self._derateWidget.get_text()
4629 return derate if derate else None
4630 elif self._derateType==acft.DERATE_TUPOLEV:
4631 return acft.DERATE_TUPOLEV_NOMINAL \
4632 if self._derateButtons[0].get_active() \
4633 else acft.DERATE_TUPOLEV_TAKEOFF
4634 elif self._derateType==acft.DERATE_B462:
4635 return self._derateWidget.get_active()
4636 else:
4637 return None
4638
4639 @property
4640 def antiIceOn(self):
4641 """Get whether the anti-ice system has been turned on."""
4642 return self._antiIceOn.get_active()
4643
4644 @antiIceOn.setter
4645 def antiIceOn(self, value):
4646 """Set the anti-ice indicator."""
4647 self._antiIceOn.set_active(value)
4648
4649 @property
4650 def rtoIndicated(self):
4651 """Get whether the pilot has indicated if there was an RTO."""
4652 return self._rto.get_active()
4653
4654 def activate(self):
4655 """Activate the page."""
4656 print("TakeoffPage.activate")
4657
4658 self._updatingMETAR = True
4659 self._metar.get_buffer().set_text(self._wizard.departureMETAR, -1)
4660 self._updatingMETAR = False
4661
4662 if self._wizard.takeoffRunway is None:
4663 self._runway.set_text("")
4664 else:
4665 self._runway.set_text(self._wizard.takeoffRunway)
4666 self._runway.set_sensitive(True)
4667 self._sid.set_active(0)
4668 self._sid.set_sensitive(True)
4669 self._v1.set_int(None)
4670 self._v1.set_sensitive(True)
4671 self._vr.set_int(None)
4672 self._vr.set_sensitive(True)
4673 self._v2.set_int(None)
4674 self._v2.set_sensitive(True)
4675
4676 i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
4677 speedUnit = xstr("label" + i18nSpeedUnit)
4678 self._v1Unit.set_text(speedUnit)
4679 self._vrUnit.set_text(speedUnit)
4680 self._v2Unit.set_text(speedUnit)
4681
4682 self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip" + i18nSpeedUnit))
4683 self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip" + i18nSpeedUnit))
4684 self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip" + i18nSpeedUnit))
4685
4686 self._derateType = self._wizard.gui.flight.aircraft.derateType
4687
4688 self._setupDerateWidget()
4689
4690 self._rto.set_active(False)
4691 self._rto.set_sensitive(False)
4692
4693 self._button.set_sensitive(False)
4694 self._forwardAllowed = False
4695
4696 self._active = True
4697
4698 def allowForward(self):
4699 """Allow going to the next page."""
4700 print("TakeoffPage.allowForward")
4701 self._forwardAllowed = True
4702 self._updateForwardButton()
4703
4704 def reset(self):
4705 """Reset the page if the wizard is reset."""
4706 print("TakeoffPage.reset")
4707
4708 super(TakeoffPage, self).reset()
4709 self._v1.reset()
4710 self._vr.reset()
4711 self._v2.reset()
4712 self._hasDerate = False
4713 self._antiIceOn.set_active(False)
4714 self._active = False
4715
4716 def setRTOEnabled(self, enabled):
4717 """Set the RTO checkbox enabled or disabled."""
4718 if not enabled:
4719 self._rto.set_active(False)
4720 self._rto.set_sensitive(enabled)
4721
4722 def changeMETAR(self, metar):
4723 """Change the METAR as a result of an edit on one of the other
4724 pages."""
4725 if self._active:
4726 print("TakeoffPage.changeMETAR")
4727 self._updatingMETAR = True
4728 self._metar.get_buffer().set_text(metar, -1)
4729 self._updatingMETAR = False
4730
4731 self._updateForwardButton()
4732
4733 def _updateForwardButton(self):
4734 """Update the sensitivity of the forward button based on some conditions."""
4735 sensitive = self._forwardAllowed and \
4736 self._metar.get_text()!="" and \
4737 self._runway.get_text()!="" and \
4738 self.sid is not None and \
4739 self.v1 is not None and \
4740 self.vr is not None and \
4741 self.v2 is not None and \
4742 self.v1 <= self.vr and \
4743 self.vr <= self.v2 and \
4744 (self._derateType==acft.DERATE_NONE or
4745 self.derate is not None)
4746
4747 print("TakeoffPage._updateForwardButton: forwardAllowed:", self._forwardAllowed, ", sensitive:", sensitive)
4748 if self._forwardAllowed:
4749 print(" METAR: ", self._metar.get_text())
4750 print(" runway: ", self._runway.get_text())
4751 print(" SID:", self.sid)
4752 print(" V1:", self.v1)
4753 print(" VR:", self.vr)
4754 print(" V2:", self.v2)
4755 print(" derateType:", self._derateType)
4756 print(" derate:", self.derate)
4757
4758 self._button.set_sensitive(sensitive)
4759
4760 def _valueChanged(self, widget, arg = None):
4761 """Called when the value of some widget has changed."""
4762 print("TakeoffPage._valueChanged")
4763
4764 self._updateForwardButton()
4765
4766 def _upperChanged(self, entry, arg = None):
4767 """Called when the value of some entry widget has changed and the value
4768 should be converted to uppercase."""
4769 print("TakeoffPage._upperChanged")
4770 entry.set_text(entry.get_text().upper())
4771 self._valueChanged(entry, arg)
4772
4773 def _upperChangedComboBox(self, comboBox):
4774 """Called for combo box widgets that must be converted to uppercase."""
4775 entry = comboBox.get_child()
4776 if comboBox.get_active()==-1:
4777 entry.set_text(entry.get_text().upper())
4778 self._valueChanged(entry)
4779
4780 def _derateChanged(self, entry):
4781 """Called when the value of the derate is changed."""
4782 print("TakeoffPage._derateChanged")
4783 self._updateForwardButton()
4784
4785 def _rtoToggled(self, button):
4786 """Called when the RTO check button is toggled."""
4787 self._wizard.rtoToggled(button.get_active())
4788
4789 def _backClicked(self, button):
4790 """Called when the Back button is pressed."""
4791 self.goBack()
4792
4793 def _forwardClicked(self, button):
4794 """Called when the forward button is clicked."""
4795 aircraft = self._wizard.gui.flight.aircraft
4796 aircraft.updateV1R2()
4797 if self.derate is not None:
4798 aircraft.updateDerate()
4799 aircraft.updateTakeoffAntiIce()
4800 self._wizard.nextPage()
4801
4802 def _setupDerateWidget(self):
4803 """Setup the derate widget."""
4804 if self._derateWidget is not None:
4805 self._derate.remove(self._derateWidget)
4806
4807 if self._derateType==acft.DERATE_BOEING:
4808 self._derateLabel.set_text(xstr("takeoff_derate_boeing"))
4809 self._derateLabel.set_use_underline(True)
4810 self._derateLabel.set_sensitive(True)
4811
4812 self._derateEntry = Gtk.Entry()
4813 self._derateEntry.set_width_chars(7)
4814 self._derateEntry.set_tooltip_text(xstr("takeoff_derate_boeing_tooltip"))
4815 self._derateEntry.set_alignment(1.0)
4816 self._derateEntry.connect("changed", self._derateChanged)
4817 self._derateLabel.set_mnemonic_widget(self._derateEntry)
4818
4819 self._derateUnit = Gtk.Label("%")
4820 self._derateUnit.set_alignment(0.0, 0.5)
4821
4822 self._derateWidget = Gtk.Table(3, 1)
4823 self._derateWidget.set_row_spacings(4)
4824 self._derateWidget.set_col_spacings(16)
4825 self._derateWidget.set_homogeneous(False)
4826
4827 self._derateWidget.attach(self._derateEntry, 0, 2, 0, 1)
4828 self._derateWidget.attach(self._derateUnit, 2, 3, 0, 1)
4829
4830 self._derate.add(self._derateWidget)
4831 elif self._derateType==acft.DERATE_EPR:
4832 self._derateLabel.set_text("_EPR:")
4833 self._derateLabel.set_use_underline(True)
4834 self._derateLabel.set_sensitive(True)
4835
4836 self._derateWidget = Gtk.Entry()
4837 self._derateWidget.set_width_chars(7)
4838 self._derateWidget.set_tooltip_text(xstr("takeoff_derate_epr_tooltip"))
4839 self._derateWidget.set_alignment(1.0)
4840 self._derateWidget.connect("changed", self._derateChanged)
4841 self._derateLabel.set_mnemonic_widget(self._derateWidget)
4842
4843 self._derate.add(self._derateWidget)
4844 elif self._derateType==acft.DERATE_TUPOLEV:
4845 self._derateLabel.set_text(xstr("takeoff_derate_tupolev"))
4846 self._derateLabel.set_use_underline(True)
4847 self._derateLabel.set_sensitive(True)
4848
4849 nominal = Gtk.RadioButton.\
4850 new_with_label_from_widget(None,
4851 xstr("takeoff_derate_tupolev_nominal"))
4852 nominal.set_use_underline(True)
4853 nominal.set_tooltip_text(xstr("takeoff_derate_tupolev_nominal_tooltip"))
4854 nominal.connect("toggled", self._derateChanged)
4855
4856 takeoff = Gtk.RadioButton.\
4857 new_with_label_from_widget(nominal,
4858 xstr("takeoff_derate_tupolev_takeoff"))
4859
4860 takeoff.set_use_underline(True)
4861 takeoff.set_tooltip_text(xstr("takeoff_derate_tupolev_takeoff_tooltip"))
4862 takeoff.connect("toggled", self._derateChanged)
4863
4864 self._derateButtons = [nominal, takeoff]
4865
4866 self._derateWidget = Gtk.HBox()
4867 self._derateWidget.pack_start(nominal, False, False, 4)
4868 self._derateWidget.pack_start(takeoff, False, False, 4)
4869
4870 self._derate.add(self._derateWidget)
4871 elif self._derateType==acft.DERATE_B462:
4872 self._derateLabel.set_text("")
4873
4874 self._derateWidget = Gtk.CheckButton(xstr("takeoff_derate_b462"))
4875 self._derateWidget.set_tooltip_text(xstr("takeoff_derate_b462_tooltip"))
4876 self._derateWidget.set_use_underline(True)
4877 self._derate.add(self._derateWidget)
4878 else:
4879 self._derateWidget = None
4880 self._derateLabel.set_text("")
4881 self._derateLabel.set_sensitive(False)
4882
4883 def _metarChanged(self, entry):
4884 """Called when the METAR has changed."""
4885 print("TakeoffPage.metarChanged", self._updatingMETAR)
4886 if not self._updatingMETAR:
4887 self._updateForwardButton()
4888 self._wizard.metarChanged(entry.get_text(), self)
4889
4890 def _metarInserted(self, buffer, position, text, length):
4891 """Called when new characters are inserted into the METAR.
4892
4893 It uppercases all characters."""
4894 print("TakeoffPage.metarInserted", self._updatingMETAR)
4895 if not self._updatingMETAR:
4896 self._updatingMETAR = True
4897
4898 buffer.delete_text(position, length)
4899 buffer.insert_text(position, text.upper(), length)
4900
4901 self._updatingMETAR = False
4902
4903#-----------------------------------------------------------------------------
4904
4905class CruisePage(Page):
4906 """The page containing the flight level that might change during flight."""
4907 def __init__(self, wizard):
4908 """Construct the page."""
4909 super(CruisePage, self).__init__(wizard, "cruise",
4910 xstr("cruise_title"),
4911 xstr("cruise_help"))
4912
4913 self._loggable = False
4914 self._loggedCruiseLevel = 240
4915 self._activated = False
4916
4917 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.0,
4918 xscale = 0.0, yscale = 1.0)
4919
4920 mainBox = Gtk.VBox()
4921 alignment.add(mainBox)
4922 self.setMainWidget(alignment)
4923
4924 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4925 xscale = 0.0, yscale = 0.0)
4926 mainBox.pack_start(alignment, False, False, 16)
4927
4928 levelBox = Gtk.HBox()
4929
4930 label = Gtk.Label(xstr("route_level"))
4931 label.set_use_underline(True)
4932 levelBox.pack_start(label, True, True, 0)
4933
4934 self._cruiseLevel = Gtk.SpinButton()
4935 self._cruiseLevel.set_increments(step = 10, page = 100)
4936 self._cruiseLevel.set_range(min = 50, max = 500)
4937 self._cruiseLevel.set_tooltip_text(xstr("cruise_route_level_tooltip"))
4938 self._cruiseLevel.set_numeric(True)
4939 self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
4940 label.set_mnemonic_widget(self._cruiseLevel)
4941
4942 levelBox.pack_start(self._cruiseLevel, False, False, 8)
4943
4944 self._updateButton = Gtk.Button(xstr("cruise_route_level_update"));
4945 self._updateButton.set_use_underline(True)
4946 self._updateButton.set_tooltip_text(xstr("cruise_route_level_update_tooltip"))
4947 self._updateButton.connect("clicked", self._updateButtonClicked)
4948
4949 levelBox.pack_start(self._updateButton, False, False, 16)
4950
4951 mainBox.pack_start(levelBox, False, False, 0)
4952
4953 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4954 xscale = 0.0, yscale = 1.0)
4955 mainBox.pack_start(alignment, True, True, 0)
4956
4957 self.addCancelFlightButton()
4958
4959 self._backButton = self.addPreviousButton(clicked = self._backClicked)
4960 self._button = self.addNextButton(clicked = self._forwardClicked)
4961
4962 @property
4963 def activated(self):
4964 """Determine if the page is already activated or not."""
4965 return self._activated
4966
4967 @property
4968 def cruiseLevel(self):
4969 """Get the cruise level."""
4970 return self._loggedCruiseLevel
4971
4972 @property
4973 def loggableCruiseLevel(self):
4974 """Get the cruise level which should be logged."""
4975 return self._cruiseLevel.get_value_as_int()
4976
4977 def setLoggable(self, loggable):
4978 """Set whether the cruise altitude can be logged."""
4979 self._loggable = loggable
4980 self._updateButtons()
4981
4982 def activate(self):
4983 """Setup the route from the booked flight."""
4984 self._loggedCruiseLevel = self._wizard.filedCruiseLevel
4985 self._cruiseLevel.set_value(self._loggedCruiseLevel)
4986 self._activated = True
4987
4988 def reset(self):
4989 """Reset the page."""
4990 self._loggable = False
4991 self._activated = False
4992 super(CruisePage, self).reset()
4993
4994 def _updateButtons(self):
4995 """Update the sensitivity of the buttons."""
4996 self._updateButton.set_sensitive(self._loggable and
4997 self.loggableCruiseLevel!=
4998 self._loggedCruiseLevel)
4999
5000 def _cruiseLevelChanged(self, spinButton):
5001 """Called when the cruise level has changed."""
5002 self._updateButtons()
5003
5004 def _updateButtonClicked(self, button):
5005 """Called when the update button is clicked."""
5006 if self._wizard.cruiseLevelChanged():
5007 self._loggedCruiseLevel = self.loggableCruiseLevel
5008 self._updateButtons()
5009
5010 def _backClicked(self, button):
5011 """Called when the Back button is pressed."""
5012 self.goBack()
5013
5014 def _forwardClicked(self, button):
5015 """Called when the Forward button is clicked."""
5016 self._wizard.nextPage()
5017
5018#-----------------------------------------------------------------------------
5019
5020class LandingPage(Page):
5021 """Page for entering landing data."""
5022 def __init__(self, wizard):
5023 """Construct the landing page."""
5024 super(LandingPage, self).__init__(wizard, "landing",
5025 xstr("landing_title"),
5026 xstr("landing_help"),
5027 completedHelp = xstr("landing_chelp"))
5028
5029 self._flightEnded = False
5030
5031 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5032 xscale = 0.0, yscale = 0.0)
5033
5034 table = Gtk.Table(7, 24)
5035 table.set_row_spacings(4)
5036 table.set_col_spacings(16)
5037 table.set_homogeneous(False)
5038 alignment.add(table)
5039 self.setMainWidget(alignment)
5040
5041 row = 0
5042
5043 label = Gtk.Label(xstr("landing_metar"))
5044 label.set_use_underline(True)
5045 label.set_alignment(0.0, 0.5)
5046 table.attach(label, 1, 2, row, row+1)
5047
5048 self._metar = Gtk.Entry()
5049 self._metar.set_width_chars(40)
5050 self._metar.set_tooltip_text(xstr("landing_metar_tooltip"))
5051 self._metar.connect("changed", self._metarChanged)
5052 self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
5053 table.attach(self._metar, 2, 24, row, row+1)
5054 label.set_mnemonic_widget(self._metar)
5055
5056 self._updatingMETAR = False
5057
5058 row += 1
5059
5060 label = Gtk.Label(xstr("landing_star"))
5061 label.set_use_underline(True)
5062 label.set_alignment(0.0, 0.5)
5063 table.attach(label, 1, 2, row, row + 1)
5064
5065 self._star = Gtk.ComboBox.new_with_model_and_entry(comboModel)
5066
5067 self._star.set_entry_text_column(0)
5068 self._star.get_child().set_width_chars(10)
5069 self._star.set_tooltip_text(xstr("landing_star_tooltip"))
5070 self._star.connect("changed", self._upperChangedComboBox)
5071 self._star.set_sensitive(False)
5072 table.attach(self._star, 2, 4, row, row + 1)
5073 label.set_mnemonic_widget(self._star)
5074
5075 row += 1
5076
5077 label = Gtk.Label(xstr("landing_transition"))
5078 label.set_use_underline(True)
5079 label.set_alignment(0.0, 0.5)
5080 table.attach(label, 1, 2, row, row + 1)
5081
5082 self._transition = Gtk.ComboBox.new_with_model_and_entry(comboModel)
5083
5084 self._transition.set_entry_text_column(0)
5085 self._transition.get_child().set_width_chars(10)
5086 self._transition.set_tooltip_text(xstr("landing_transition_tooltip"))
5087 self._transition.connect("changed", self._upperChangedComboBox)
5088 self._transition.set_sensitive(False)
5089 table.attach(self._transition, 2, 4, row, row + 1)
5090 label.set_mnemonic_widget(self._transition)
5091
5092 row += 1
5093
5094 label = Gtk.Label(xstr("landing_runway"))
5095 label.set_use_underline(True)
5096 label.set_alignment(0.0, 0.5)
5097 table.attach(label, 1, 2, row, row + 1)
5098
5099 self._runway = Gtk.Entry()
5100 self._runway.set_width_chars(10)
5101 self._runway.set_tooltip_text(xstr("landing_runway_tooltip"))
5102 self._runway.connect("changed", self._upperChanged)
5103 table.attach(self._runway, 2, 4, row, row + 1)
5104 label.set_mnemonic_widget(self._runway)
5105
5106 row += 1
5107
5108 label = Gtk.Label(xstr("landing_approach"))
5109 label.set_use_underline(True)
5110 label.set_alignment(0.0, 0.5)
5111 table.attach(label, 1, 2, row, row + 1)
5112
5113 self._approachType = Gtk.Entry()
5114 self._approachType.set_width_chars(10)
5115 self._approachType.set_tooltip_text(xstr("landing_approach_tooltip"))
5116 self._approachType.connect("changed", self._upperChanged)
5117 table.attach(self._approachType, 2, 4, row, row + 1)
5118 label.set_mnemonic_widget(self._approachType)
5119
5120 row += 1
5121
5122 label = Gtk.Label(xstr("landing_vref"))
5123 label.set_use_markup(True)
5124 label.set_use_underline(True)
5125 label.set_alignment(0.0, 0.5)
5126 table.attach(label, 1, 2, row, row + 1)
5127
5128 self._vref = IntegerEntry()
5129 self._vref.set_width_chars(5)
5130 self._vref.set_tooltip_markup(xstr("landing_vref_tooltip_knots"))
5131 self._vref.connect("integer-changed", self._vrefChanged)
5132 table.attach(self._vref, 3, 4, row, row + 1)
5133 label.set_mnemonic_widget(self._vref)
5134
5135 self._vrefUnit = Gtk.Label(xstr("label_knots"))
5136 table.attach(self._vrefUnit, 4, 5, row, row + 1)
5137
5138 row += 1
5139
5140 self._antiIceOn = Gtk.CheckButton(xstr("landing_antiice"))
5141 self._antiIceOn.set_use_underline(True)
5142 self._antiIceOn.set_tooltip_text(xstr("landing_antiice_tooltip"))
5143 table.attach(self._antiIceOn, 3, 5, row, row + 1)
5144
5145 self.addCancelFlightButton()
5146
5147 self.addPreviousButton(clicked = self._backClicked)
5148
5149 self._button = self.addNextButton(clicked = self._forwardClicked)
5150
5151 self._active = False
5152
5153 @property
5154 def star(self):
5155 """Get the STAR or None if none entered."""
5156 text = self._star.get_child().get_text()
5157 return text if self._star.get_active()!=0 and text and text!="N/A" \
5158 else None
5159
5160 @property
5161 def transition(self):
5162 """Get the transition or None if none entered."""
5163 text = self._transition.get_child().get_text()
5164 return text if self._transition.get_active()!=0 and text and text!="N/A" \
5165 else None
5166
5167 @property
5168 def approachType(self):
5169 """Get the approach type."""
5170 return self._approachType.get_text()
5171
5172 @property
5173 def runway(self):
5174 """Get the runway."""
5175 return self._runway.get_text()
5176
5177 @property
5178 def vref(self):
5179 """Return the landing reference speed."""
5180 return self._vref.get_int()
5181
5182 @property
5183 def antiIceOn(self):
5184 """Get whether the anti-ice system has been turned on."""
5185 return self._antiIceOn.get_active()
5186
5187 @antiIceOn.setter
5188 def antiIceOn(self, value):
5189 """Set the anti-ice indicator."""
5190 self._antiIceOn.set_active(value)
5191
5192 def reset(self):
5193 """Reset the page if the wizard is reset."""
5194 super(LandingPage, self).reset()
5195 self._vref.reset()
5196 self._antiIceOn.set_active(False)
5197 self._flightEnded = False
5198 self._active = False
5199
5200 def activate(self):
5201 """Called when the page is activated."""
5202 self._updatingMETAR = True
5203 self._metar.get_buffer().set_text(self._wizard.arrivalMETAR, -1)
5204 self._updatingMETAR = False
5205
5206 self._star.set_active(0)
5207 self._star.set_sensitive(True)
5208
5209 self._transition.set_active(0)
5210 self._transition.set_sensitive(True)
5211
5212 if self._wizard.landingRunway is None:
5213 self._runway.set_text("")
5214 else:
5215 self._runway.set_text(self._wizard.landingRunway)
5216 self._runway.set_sensitive(True)
5217
5218 self._approachType.set_text("")
5219 self._approachType.set_sensitive(True)
5220
5221 self._vref.set_int(None)
5222 self._vref.set_sensitive(True)
5223
5224 i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
5225 speedUnit = xstr("label" + i18nSpeedUnit)
5226 self._vrefUnit.set_text(speedUnit)
5227
5228 self._vref.set_tooltip_markup(xstr("landing_vref_tooltip" +
5229 i18nSpeedUnit))
5230
5231 self._updateForwardButton()
5232
5233 self._active = True
5234
5235 def flightEnded(self):
5236 """Called when the flight has ended."""
5237 super(LandingPage, self).flightEnded()
5238 self._flightEnded = True
5239 self._updateForwardButton()
5240
5241 def changeMETAR(self, metar):
5242 """Change the METAR as a result of an edit on one of the other
5243 pages."""
5244 if self._active:
5245 print("LandingPage.changeMETAR")
5246 self._updatingMETAR = True
5247 self._metar.get_buffer().set_text(metar, -1)
5248 self._updatingMETAR = False
5249
5250 self._updateForwardButton()
5251
5252 def _updateForwardButton(self):
5253 """Update the sensitivity of the forward button."""
5254 sensitive = self._flightEnded and \
5255 self._metar.get_text()!="" and \
5256 (self.star is not None or
5257 self.transition is not None) and \
5258 self._runway.get_text()!="" and \
5259 self._approachType.get_text()!="" and \
5260 self.vref is not None
5261 self._button.set_sensitive(sensitive)
5262
5263 def _upperChanged(self, entry):
5264 """Called for entry widgets that must be converted to uppercase."""
5265 entry.set_text(entry.get_text().upper())
5266 self._updateForwardButton()
5267
5268 def _upperChangedComboBox(self, comboBox):
5269 """Called for combo box widgets that must be converted to uppercase."""
5270 if comboBox.get_active()==-1:
5271 entry = comboBox.get_child()
5272 entry.set_text(entry.get_text().upper())
5273 self._updateForwardButton()
5274
5275 def _vrefChanged(self, widget, value):
5276 """Called when the Vref has changed."""
5277 self._updateForwardButton()
5278
5279 def _backClicked(self, button):
5280 """Called when the Back button is pressed."""
5281 self.goBack()
5282
5283 def _forwardClicked(self, button):
5284 """Called when the forward button is clicked."""
5285 wizard = self._wizard
5286
5287 aircraft = wizard.gui.flight.aircraft
5288 aircraft.updateVRef()
5289 aircraft.updateLandingAntiIce()
5290 if wizard.gui.config.onlineGateSystem and \
5291 wizard.loggedIn and not self._completed and \
5292 wizard.bookedFlight.arrivalICAO=="LHBP" and \
5293 not wizard.entranceExam:
5294 wizard.getFleet(callback = self._fleetRetrieved, force = True)
5295 elif wizard.entranceExam:
5296 self._handleEntranceExamDone()
5297 else:
5298 wizard.nextPage()
5299
5300 def _fleetRetrieved(self, fleet):
5301 """Callback for the fleet retrieval."""
5302 self._wizard.nextPage()
5303
5304 def _metarChanged(self, entry):
5305 """Called when the METAR has changed."""
5306 print("LandingPage.metarChanged", self._updatingMETAR)
5307 if not self._updatingMETAR:
5308 self._updateForwardButton()
5309 self._wizard.metarChanged(entry.get_text(), self)
5310
5311 def _metarInserted(self, buffer, position, text, length):
5312 """Called when new characters are inserted into the METAR.
5313
5314 It uppercases all characters."""
5315 print("LandingPage.metarInserted", self._updatingMETAR)
5316 if not self._updatingMETAR:
5317 self._updatingMETAR = True
5318
5319 buffer.delete_text(position, length)
5320 buffer.insert_text(position, text.upper(), length)
5321
5322 self._updatingMETAR = False
5323
5324 def _handleEntranceExamDone(self):
5325 """Handle the end of the entrance exam.
5326
5327 If the there was a NO-GO fault, notify the user that exam is a failure
5328 and take them back to the student page. Otherwise congratulate, update
5329 the database to reflect that the exam has been taken and go back to the
5330 student page."""
5331 self._wizard.jumpPage("chkfinish")
5332
5333#-----------------------------------------------------------------------------
5334
5335class PIREPSaveHelper(object):
5336 """A helper to use for saving PIREPs."""
5337 def __init__(self, wizard):
5338 """Construct the helper."""
5339 super(PIREPSaveHelper, self).__init__()
5340
5341 self._wizard = wizard
5342
5343 self._lastSavePath = None
5344 self._savePIREPDialog = None
5345
5346 def addButton(self, page):
5347 """Add a button to save the PIREP to the given page."""
5348 return page.addButton(xstr("finish_save"), sensitive = False,
5349 clicked = self._saveClicked,
5350 tooltip = xstr("finish_save_tooltip"),
5351 clickedArg = page)
5352
5353 def autoSavePIREP(self, page):
5354 """Perform the automatic saving of the PIREP."""
5355 self._lastSavePath = os.path.join(self._wizard.gui.config.pirepDirectory,
5356 self._getDefaultPIREPName())
5357 self._lastSavePath = self._lastSavePath
5358 self._savePIREP(page, automatic = True)
5359
5360 def _getDefaultPIREPName(self):
5361 """Get the default name of the PIREP."""
5362 gui = self._wizard.gui
5363
5364 bookedFlight = gui.bookedFlight
5365 tm = time.gmtime()
5366
5367 pilotID = self._wizard.pilotID
5368 if pilotID: pilotID += " "
5369 return "%s%s %02d%02d %s-%s.pirep" % \
5370 (pilotID, str(bookedFlight.departureTime.date()),
5371 tm.tm_hour, tm.tm_min,
5372 bookedFlight.departureICAO, bookedFlight.arrivalICAO)
5373
5374 def _saveClicked(self, button, page):
5375 """Called when the Save PIREP button is clicked."""
5376 gui = self._wizard.gui
5377
5378 fileName = self._getDefaultPIREPName()
5379
5380 dialog = self._getSaveDialog()
5381
5382 if self._lastSavePath is None:
5383 pirepDirectory = gui.config.pirepDirectory
5384 if pirepDirectory is not None:
5385 dialog.set_current_folder(pirepDirectory)
5386 else:
5387 dialog.set_current_folder(os.path.dirname(self._lastSavePath))
5388
5389 dialog.set_current_name(fileName)
5390 result = dialog.run()
5391 dialog.hide()
5392
5393 if result==Gtk.ResponseType.OK:
5394 self._lastSavePath = dialog.get_filename()
5395 self._savePIREP(page)
5396
5397 def _savePIREP(self, page, automatic = False):
5398 """Perform the saving of the PIREP."""
5399
5400 gui = self._wizard.gui
5401
5402 if automatic:
5403 gui.beginBusy(xstr("finish_autosave_busy"))
5404
5405 pirep = PIREP(gui.flight)
5406 error = pirep.save(self._lastSavePath)
5407
5408 if automatic:
5409 gui.endBusy()
5410
5411 if error:
5412 type = Gtk.MessageType.ERROR
5413 message = xstr("finish_save_failed")
5414 secondary = xstr("finish_save_failed_sec") % (error,)
5415 else:
5416 type = Gtk.MessageType.INFO
5417 message = xstr("finish_save_done")
5418 if automatic:
5419 secondary = xstr("finish_save_done_sec") % (self._lastSavePath,)
5420 else:
5421 secondary = None
5422 page.setPIREPSaved()
5423
5424 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
5425 type = type, message_format = message)
5426 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
5427 dialog.set_title(WINDOW_TITLE_BASE)
5428 if secondary is not None:
5429 dialog.format_secondary_markup(secondary)
5430
5431 dialog.run()
5432 dialog.hide()
5433
5434 def _getSaveDialog(self):
5435 """Get the PIREP saving dialog.
5436
5437 If it does not exist yet, create it."""
5438 if self._savePIREPDialog is None:
5439 gui = self._wizard.gui
5440 dialog = Gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
5441 xstr("finish_save_title"),
5442 action = Gtk.FileChooserAction.SAVE,
5443 buttons = (Gtk.STOCK_CANCEL,
5444 Gtk.ResponseType.CANCEL,
5445 Gtk.STOCK_OK, Gtk.ResponseType.OK),
5446 parent = gui.mainWindow)
5447 dialog.set_modal(True)
5448 dialog.set_do_overwrite_confirmation(True)
5449
5450 filter = Gtk.FileFilter()
5451 filter.set_name(xstr("file_filter_pireps"))
5452 filter.add_pattern("*.pirep")
5453 dialog.add_filter(filter)
5454
5455 filter = Gtk.FileFilter()
5456 filter.set_name(xstr("file_filter_all"))
5457 filter.add_pattern("*.*")
5458 dialog.add_filter(filter)
5459
5460 self._savePIREPDialog = dialog
5461
5462 return self._savePIREPDialog
5463
5464#-----------------------------------------------------------------------------
5465
5466class FinishPage(Page):
5467 """Flight finish page."""
5468 def __init__(self, wizard, saveHelper):
5469 """Construct the finish page."""
5470 help = xstr("finish_help") + xstr("finish_help_goodtime")
5471 super(FinishPage, self).__init__(wizard, "finish",
5472 xstr("finish_title"), help)
5473
5474 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5475 xscale = 0.0, yscale = 0.0)
5476
5477 table = Gtk.Table(10, 2)
5478 table.set_row_spacings(4)
5479 table.set_col_spacings(16)
5480 table.set_homogeneous(False)
5481 alignment.add(table)
5482 self.setMainWidget(alignment)
5483
5484 row = 0
5485
5486 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5487 label = Gtk.Label(xstr("finish_rating"))
5488 labelAlignment.add(label)
5489 table.attach(labelAlignment, 0, 1, row, row+1)
5490
5491 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5492 self._flightRating = Gtk.Label()
5493 self._flightRating.set_width_chars(8)
5494 self._flightRating.set_alignment(0.0, 0.5)
5495 self._flightRating.set_use_markup(True)
5496 labelAlignment.add(self._flightRating)
5497 table.attach(labelAlignment, 1, 2, row, row+1)
5498
5499 row += 1
5500
5501 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5502 label = Gtk.Label(xstr("finish_dep_time"))
5503 labelAlignment.add(label)
5504 table.attach(labelAlignment, 0, 1, row, row+1)
5505
5506 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5507 self._depTime = Gtk.Label()
5508 self._depTime.set_width_chars(13)
5509 self._depTime.set_alignment(0.0, 0.5)
5510 self._depTime.set_use_markup(True)
5511 self._depTime.set_tooltip_markup(xstr("finish_dep_time_tooltip"))
5512 labelAlignment.add(self._depTime)
5513 table.attach(labelAlignment, 1, 2, row, row+1)
5514
5515 row += 1
5516
5517 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5518 label = Gtk.Label(xstr("finish_flight_time"))
5519 labelAlignment.add(label)
5520 table.attach(labelAlignment, 0, 1, row, row+1)
5521
5522 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5523 self._flightTime = Gtk.Label()
5524 self._flightTime.set_width_chars(10)
5525 self._flightTime.set_alignment(0.0, 0.5)
5526 self._flightTime.set_use_markup(True)
5527 labelAlignment.add(self._flightTime)
5528 table.attach(labelAlignment, 1, 2, row, row+1)
5529
5530 row += 1
5531
5532 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5533 label = Gtk.Label(xstr("finish_block_time"))
5534 labelAlignment.add(label)
5535 table.attach(labelAlignment, 0, 1, row, row+1)
5536
5537 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5538 self._blockTime = Gtk.Label()
5539 self._blockTime.set_width_chars(10)
5540 self._blockTime.set_alignment(0.0, 0.5)
5541 self._blockTime.set_use_markup(True)
5542 labelAlignment.add(self._blockTime)
5543 table.attach(labelAlignment, 1, 2, row, row+1)
5544
5545 row += 1
5546
5547 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5548 label = Gtk.Label(xstr("finish_arr_time"))
5549 labelAlignment.add(label)
5550 table.attach(labelAlignment, 0, 1, row, row+1)
5551
5552 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5553 self._arrTime = Gtk.Label()
5554 self._arrTime.set_width_chars(13)
5555 self._arrTime.set_alignment(0.0, 0.5)
5556 self._arrTime.set_use_markup(True)
5557 self._arrTime.set_tooltip_markup(xstr("finish_arr_time_tooltip"))
5558 labelAlignment.add(self._arrTime)
5559 table.attach(labelAlignment, 1, 2, row, row+1)
5560
5561 row += 1
5562
5563 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5564 label = Gtk.Label(xstr("finish_distance"))
5565 labelAlignment.add(label)
5566 table.attach(labelAlignment, 0, 1, row, row+1)
5567
5568 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5569 self._distanceFlown = Gtk.Label()
5570 self._distanceFlown.set_width_chars(10)
5571 self._distanceFlown.set_alignment(0.0, 0.5)
5572 self._distanceFlown.set_use_markup(True)
5573 labelAlignment.add(self._distanceFlown)
5574 table.attach(labelAlignment, 1, 2, row, row+1)
5575
5576 row += 1
5577
5578 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5579 label = Gtk.Label(xstr("finish_fuel"))
5580 labelAlignment.add(label)
5581 table.attach(labelAlignment, 0, 1, row, row+1)
5582
5583 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5584 self._fuelUsed = Gtk.Label()
5585 self._fuelUsed.set_width_chars(10)
5586 self._fuelUsed.set_alignment(0.0, 0.5)
5587 self._fuelUsed.set_use_markup(True)
5588 labelAlignment.add(self._fuelUsed)
5589 table.attach(labelAlignment, 1, 2, row, row+1)
5590
5591 row += 1
5592
5593 labelAlignment = Gtk.Alignment(xalign = 1.0, xscale = 0.0,
5594 yalign = 0.5, yscale = 0.0)
5595 label = Gtk.Label(xstr("finish_type"))
5596 label.set_use_underline(True)
5597 labelAlignment.add(label)
5598 table.attach(labelAlignment, 0, 1, row, row+1)
5599
5600 self._onlineFlight = Gtk.CheckButton(xstr("finish_online"))
5601 self._onlineFlight.set_use_underline(True)
5602 self._onlineFlight.set_tooltip_text(xstr("finish_online_tooltip"))
5603 onlineFlightAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5604 onlineFlightAlignment.add(self._onlineFlight)
5605 table.attach(onlineFlightAlignment, 1, 2, row, row + 1)
5606
5607 row += 1
5608
5609 labelAlignment = Gtk.Alignment(xalign = 1.0, xscale = 0.0,
5610 yalign = 0.5, yscale = 0.0)
5611 self._gateLabel = Gtk.Label(xstr("finish_gate"))
5612 self._gateLabel.set_use_underline(True)
5613 labelAlignment.add(self._gateLabel)
5614 table.attach(labelAlignment, 0, 1, row, row+1)
5615
5616 self._gatesModel = Gtk.ListStore(str)
5617
5618 self._gate = Gtk.ComboBox(model = self._gatesModel)
5619 renderer = Gtk.CellRendererText()
5620 self._gate.pack_start(renderer, True)
5621 self._gate.add_attribute(renderer, "text", 0)
5622 self._gate.set_tooltip_text(xstr("finish_gate_tooltip"))
5623 self._gate.connect("changed", self._gateChanged)
5624 gateAlignment = Gtk.Alignment(xalign=0.0, xscale=1.0)
5625 gateAlignment.add(self._gate)
5626 table.attach(gateAlignment, 1, 2, row, row+1)
5627 self._gateLabel.set_mnemonic_widget(self._gate)
5628
5629 self.addButton(xstr("finish_newFlight"),
5630 sensitive = True,
5631 clicked = self._newFlightClicked,
5632 tooltip = xstr("finish_newFlight_tooltip"),
5633 padding = 16)
5634
5635 self.addPreviousButton(clicked = self._backClicked)
5636
5637 self._saveHelper = saveHelper
5638 self._saveButton = saveHelper.addButton(self)
5639
5640 self._tooBigTimeDifference = False
5641 self._deferredAutoSave = False
5642 self._pirepSaved = False
5643 self._pirepSent = False
5644
5645 self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
5646 sensitive = False,
5647 clicked = self._sendClicked,
5648 tooltip = xstr("sendPIREP_tooltip"))
5649
5650 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 10*60.0)
5651 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 20*60.0)
5652 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 0*60.0)
5653 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), (23*60.0+50)*60.0)
5654 # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (1*60.0+5)*60.0)
5655 # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (0*60.0+50)*60.0)
5656 # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (0*60.0+5)*60.0)
5657 # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (23*60.0+45)*60.0)
5658
5659 @property
5660 def online(self):
5661 """Get whether the flight was an online flight or not."""
5662 return self._onlineFlight.get_active()
5663
5664 def activate(self):
5665 """Activate the page."""
5666 self._deferredAutoSave = False
5667 self._pirepSaved = False
5668 self._pirepSent = False
5669
5670 flight = self._wizard.gui._flight
5671 rating = flight.logger.getRating()
5672 if rating<0:
5673 self._flightRating.set_markup('<b><span foreground="red">NO GO</span></b>')
5674 else:
5675 self._flightRating.set_markup("<b>%.1f %%</b>" % (rating,))
5676
5677 flightLength = flight.flightTimeEnd - flight.flightTimeStart
5678 self._flightTime.set_markup("<b>%s</b>" % \
5679 (util.getTimeIntervalString(flightLength),))
5680
5681 blockLength = flight.blockTimeEnd - flight.blockTimeStart
5682 self._blockTime.set_markup("<b>%s</b>" % \
5683 (util.getTimeIntervalString(blockLength),))
5684
5685 self._distanceFlown.set_markup("<b>%.2f NM</b>" % \
5686 (flight.flownDistance,))
5687
5688 self._fuelUsed.set_markup("<b>%.0f kg</b>" % \
5689 (flight.startFuel - flight.endFuel,))
5690
5691 self._onlineFlight.set_active(self._wizard.loggedIn)
5692
5693 self._gatesModel.clear()
5694 if self._wizard.gui.config.onlineGateSystem and \
5695 self._wizard.loggedIn and \
5696 self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
5697 not self._wizard.entranceExam:
5698 occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
5699 for gate in lhbpGates.gates:
5700 if gate.isAvailable(lhbpGates, occupiedGateNumbers):
5701 self._gatesModel.append([gate.number])
5702 self._gateLabel.set_sensitive(True)
5703 self._gate.set_sensitive(True)
5704 self._gate.set_active(-1)
5705 else:
5706 self._gateLabel.set_sensitive(False)
5707 self._gate.set_sensitive(False)
5708
5709 self._updateTimes()
5710
5711 def updateButtons(self):
5712 """Update the sensitivity state of the buttons."""
5713 gui = self._wizard.gui
5714 faultsExplained = gui.faultsFullyExplained
5715 timesCorrect = not self._tooBigTimeDifference or \
5716 gui.hasComments or gui.hasDelayCode
5717 sensitive = gui.flight is not None and \
5718 gui.flight.stage==const.STAGE_END and \
5719 (self._gatesModel.get_iter_first() is None or
5720 self._gate.get_active()>=0) and \
5721 faultsExplained and timesCorrect
5722
5723 self._updateHelp(faultsExplained, timesCorrect)
5724
5725 wasSensitive = self._saveButton.get_sensitive()
5726
5727 if gui.config.pirepAutoSave and sensitive and not wasSensitive:
5728 if gui.isWizardActive():
5729 self._saveHelper.autoSavePIREP(self)
5730 else:
5731 self._deferredAutoSave = True
5732
5733 if not sensitive:
5734 self._deferredAutoSave = False
5735
5736 self._saveButton.set_sensitive(sensitive)
5737 self._sendButton.set_sensitive(sensitive and
5738 self._wizard.bookedFlight.id is not None)
5739
5740 def grabDefault(self):
5741 """If the page has a default button, make it the default one."""
5742 super(FinishPage, self).grabDefault()
5743 if self._deferredAutoSave:
5744 self._saveHelper.autoSavePIREP(self)
5745 self._deferredAutoSave = False
5746
5747 def setPIREPSaved(self):
5748 """Mark the PIREP as saved."""
5749 self._pirepSaved = True
5750
5751 def _backClicked(self, button):
5752 """Called when the Back button is pressed."""
5753 self.goBack()
5754
5755 def _gateChanged(self, comboBox):
5756 """Called when the arrival gate has changed."""
5757 self.updateButtons()
5758
5759 def _newFlightClicked(self, button):
5760 """Called when the new flight button is clicked."""
5761 gui = self._wizard.gui
5762 if not self._pirepSent and not self._pirepSaved:
5763 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
5764 type = Gtk.MessageType.QUESTION,
5765 message_format = xstr("finish_newFlight_question"))
5766
5767 dialog.add_button(xstr("button_no"), Gtk.ResponseType.NO)
5768 dialog.add_button(xstr("button_yes"), Gtk.ResponseType.YES)
5769
5770 dialog.set_title(WINDOW_TITLE_BASE)
5771 result = dialog.run()
5772 dialog.hide()
5773 if result!=Gtk.ResponseType.YES:
5774 return
5775
5776 gui.reset()
5777
5778 def _sendClicked(self, button):
5779 """Called when the Send button is clicked."""
5780 pirep = PIREP(self._wizard.gui.flight)
5781 self._wizard.gui.sendPIREP(pirep,
5782 callback = self._handlePIREPSent)
5783
5784 def _handlePIREPSent(self, returned, result):
5785 """Callback for the PIREP sending result."""
5786 self._pirepSent = returned and result.success
5787 if self._wizard.gui.config.onlineGateSystem and \
5788 self._wizard.loggedIn and not self._wizard.entranceExam and \
5789 returned and result.success:
5790 bookedFlight = self._wizard.bookedFlight
5791 if bookedFlight.arrivalICAO=="LHBP":
5792 iter = self._gate.get_active_iter()
5793 gateNumber = None if iter is None \
5794 else self._gatesModel.get_value(iter, 0)
5795
5796 status = const.PLANE_PARKING if gateNumber is None \
5797 else const.PLANE_HOME
5798 else:
5799 gateNumber = None
5800 status = const.PLANE_AWAY
5801
5802 self._wizard.updatePlane(self._planeUpdated,
5803 bookedFlight.tailNumber,
5804 status, gateNumber = gateNumber)
5805
5806 def _planeUpdated(self, success):
5807 """Callback for the plane updating."""
5808 pass
5809
5810 def _formatTime(self, scheduledTime, realTimestamp, warningError):
5811 """Format the departure or arrival time based on the given data as a
5812 markup for a label."""
5813 (warning, error) = warningError
5814 realTime = time.gmtime(realTimestamp)
5815
5816 if warning:
5817 colour = "red" if error else "orange"
5818 markupBegin = '<span foreground="%s">' % (colour,)
5819 markupEnd = '</span>'
5820 else:
5821 markupBegin = markupEnd = ""
5822
5823 markup = "<b>%s%02d:%02d [%02d:%02d]%s</b>" % \
5824 (markupBegin,
5825 realTime.tm_hour, realTime.tm_min,
5826 scheduledTime.hour, scheduledTime.minute,
5827 markupEnd)
5828
5829 return markup
5830
5831 def _updateTimes(self):
5832 """Format the flight times and the help text according to the flight
5833 type.
5834
5835 The buttons are also updated.
5836 """
5837 flight = self._wizard.gui._flight
5838 bookedFlight = flight.bookedFlight
5839
5840 (departureWarning, departureError) = flight.blockTimeStartWrong
5841 (arrivalWarning, arrivalError) = flight.blockTimeEndWrong
5842
5843 if bookedFlight.flightType==const.FLIGHTTYPE_VIP:
5844 departureError = arrivalError = False
5845
5846 self._tooBigTimeDifference = departureError and arrivalError
5847
5848 self._depTime.set_markup(self._formatTime(bookedFlight.departureTime,
5849 flight.blockTimeStart,
5850 (departureWarning,
5851 departureError)))
5852
5853 self._arrTime.set_markup(self._formatTime(bookedFlight.arrivalTime,
5854 flight.blockTimeEnd,
5855 (arrivalWarning,
5856 arrivalError)))
5857
5858 self.updateButtons()
5859
5860 def _updateHelp(self, faultsExplained, timesCorrect):
5861 """Update the help text according to the actual situation."""
5862 if not faultsExplained:
5863 self.setHelp(xstr("finish_help") + xstr("finish_help_faults"))
5864 elif not timesCorrect:
5865 self.setHelp(xstr("finish_help") + xstr("finish_help_wrongtime"))
5866 else:
5867 self.setHelp(xstr("finish_help") + xstr("finish_help_goodtime"))
5868
5869
5870#-----------------------------------------------------------------------------
5871
5872class CheckFlightFinishPage(Page):
5873 """Finish page for a check flight."""
5874 def __init__(self, wizard, saveHelper):
5875 """Construct the check flight finish page."""
5876 super(CheckFlightFinishPage, self).__init__(wizard,
5877 "chkfinish",
5878 xstr("chkfinish_title"),
5879 "")
5880
5881 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5882 xscale = 1.0, yscale = 1.0)
5883 self._label = Gtk.Label()
5884 alignment.add(self._label)
5885
5886 self.setMainWidget(alignment)
5887
5888 self._saveHelper = saveHelper
5889 self._saveButton = saveHelper.addButton(self)
5890
5891 self._button = self.addNextButton(sensitive = False,
5892 clicked = self._forwardClicked)
5893
5894 def activate(self):
5895 """Activate the page."""
5896 wizard = self._wizard
5897 loginResult = wizard.loginResult
5898 gui = wizard.gui
5899 rating = gui.flight.logger.getRating()
5900
5901 if rating>=0:
5902 loginResult.checkFlightStatus = True
5903
5904 firstOfficer = \
5905 loginResult.entryExamPassed and loginResult.checkFlightStatus
5906
5907 if firstOfficer:
5908 loginResult.rank = "FO"
5909
5910 if rating<0:
5911 mainMessage = xstr("chkfinish_failed")
5912 else:
5913 mainMessage = xstr("chkfinish_passed_begin")
5914 if firstOfficer:
5915 mainMessage += xstr("chkfinish_passed_fo")
5916 mainMessage += xstr("chkfinish_passed_end")
5917
5918 if firstOfficer:
5919 nextMessage = xstr("chkfinish_next")
5920 else:
5921 nextMessage = xstr("chkfinish_next_student_begin")
5922 if not loginResult.entryExamPassed and \
5923 not loginResult.checkFlightStatus:
5924 nextMessage += xstr("chkfinish_next_student_nothing")
5925 elif loginResult.entryExamPassed and \
5926 not loginResult.checkFlightStatus:
5927 nextMessage += xstr("chkfinish_next_student_no_flight")
5928 elif not loginResult.entryExamPassed and \
5929 loginResult.checkFlightStatus:
5930 nextMessage += xstr("chkfinish_next_student_no_exam")
5931
5932 self._label.set_text(mainMessage +
5933 xstr("chkfinish_savepirep") +
5934 nextMessage)
5935 self._label.set_use_markup(True)
5936 self._label.set_alignment(0.5, 0.0)
5937
5938 self._saveButton.set_sensitive(True)
5939 self._button.set_sensitive(True)
5940
5941 def _forwardClicked(self, button):
5942 """Jump to the student page if there are some tasks to do,
5943 or to the flight selection page, if the pilot is allowed to perform
5944 MAVA flights."""
5945 wizard = self._wizard
5946 gui = wizard.gui
5947
5948 loginResult = wizard.loginResult
5949 if loginResult.checkFlightStatus:
5950 gui.beginBusy(xstr("chkfinish_updateweb_busy"))
5951 gui.webHandler.setCheckFlightPassed(self._checkFlightPassedSetCallback,
5952 wizard.checkFlightAircraftType)
5953 else:
5954 self._resetGUI()
5955
5956 def _checkFlightPassedSetCallback(self, returned, result):
5957 """Called when the check flight status has been set."""
5958 GObject.idle_add(self._checkFlightPassedSet, returned, result)
5959
5960 def _checkFlightPassedSet(self, returned, result):
5961 """Handle the result of an attempt to set the check flight status."""
5962 gui = self._wizard.gui
5963
5964 gui.endBusy()
5965
5966 if returned:
5967 self._resetGUI()
5968 else:
5969 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
5970 type = Gtk.MessageType.ERROR,
5971 message_format =
5972 xstr("chkfinish_passedset_failed"))
5973 dialog.set_title(WINDOW_TITLE_BASE + " - " +
5974 xstr("chkfinish_passedset_failed_title"))
5975 dialog.format_secondary_markup(xstr("chkfinish_passedset_failed_secondary"))
5976
5977 dialog.add_button(xstr("button_ok"), 0)
5978
5979 dialog.run()
5980 dialog.hide()
5981
5982 def _resetGUI(self):
5983 """Reset the GUI."""
5984 gui = self._wizard.gui
5985 gui.reset()
5986
5987#-----------------------------------------------------------------------------
5988
5989class Wizard(Gtk.VBox):
5990 """The flight wizard."""
5991 def __init__(self, gui):
5992 """Construct the wizard."""
5993 super(Wizard, self).__init__()
5994
5995 self.gui = gui
5996
5997 self._pages = []
5998 self._currentPage = None
5999
6000 self._loginPage = LoginPage(self)
6001 self._pages.append(self._loginPage)
6002 self._flightSelectionPage = FlightSelectionPage(self)
6003 self._pages.append(self._flightSelectionPage)
6004 self._pages.append(GateSelectionPage(self))
6005 self._pages.append(RegisterPage(self))
6006 self._studentPage = StudentPage(self)
6007 self._pages.append(self._studentPage)
6008 self._pages.append(ConnectPage(self))
6009 self._payloadPage = PayloadPage(self)
6010 self._pages.append(self._payloadPage)
6011 self._payloadIndex = len(self._pages)
6012 self._pages.append(TimePage(self))
6013 self._routePage = RoutePage(self)
6014 self._pages.append(self._routePage)
6015 self._simBriefSetupPage = SimBriefSetupPage(self)
6016 self._pages.append(self._simBriefSetupPage)
6017 self._simBriefingPage = SimBriefingPage(self)
6018 self._pages.append(self._simBriefingPage)
6019 self._pages.append(FuelPage(self))
6020 self._departureBriefingPage = BriefingPage(self, True)
6021 self._pages.append(self._departureBriefingPage)
6022 self._arrivalBriefingPage = BriefingPage(self, False)
6023 self._pages.append(self._arrivalBriefingPage)
6024 self._arrivalBriefingIndex = len(self._pages)
6025 self._takeoffPage = TakeoffPage(self)
6026 self._pages.append(self._takeoffPage)
6027 self._cruisePage = CruisePage(self)
6028 self._pages.append(self._cruisePage)
6029 self._landingPage = LandingPage(self)
6030 self._pages.append(self._landingPage)
6031
6032 pirepSaveHelper = PIREPSaveHelper(self)
6033
6034 self._finishPage = FinishPage(self, pirepSaveHelper)
6035 self._pages.append(self._finishPage)
6036 self._pages.append(CheckFlightFinishPage(self, pirepSaveHelper))
6037
6038 self._requestedWidth = None
6039 self._requestedHeight = None
6040
6041 self.connect("size-allocate", self._sizeAllocate)
6042
6043 for page in self._pages:
6044 page.show_all()
6045 page.setStyle()
6046
6047 self._initialize()
6048 self._allocateSize()
6049
6050 def _sizeAllocate(self, widget, allocation):
6051 if self._requestedWidth is not None and \
6052 self._requestedHeight is not None:
6053 return
6054
6055 (maxWidth, maxHeight) = self._allocateSize()
6056
6057 self._requestedWidth = maxWidth
6058 self._requestedHeight = maxHeight
6059
6060 def _allocateSize(self):
6061 """Perform the real size allocation."""
6062
6063 if self._currentPage is not None:
6064 self.remove(self._pages[self._currentPage])
6065
6066 maxWidth = 0
6067 maxHeight = 0
6068 for page in self._pages:
6069 self.add(page)
6070 self.show_all()
6071 pageSizeRequest = page.size_request()
6072 width = pageSizeRequest.width
6073 height = pageSizeRequest.height
6074 maxWidth = max(maxWidth, width)
6075 maxHeight = max(maxHeight, height)
6076 self.remove(page)
6077
6078 if self._currentPage is not None:
6079 self.add(self._pages[self._currentPage])
6080
6081 self.set_size_request(maxWidth, maxHeight)
6082
6083 return (maxWidth, maxHeight)
6084
6085 @property
6086 def pilotID(self):
6087 """Get the pilot ID, if given."""
6088 return self._loginPage.pilotID
6089
6090 @property
6091 def entranceExam(self):
6092 """Get whether an entrance exam is about to be taken."""
6093 return self._loginResult is not None and self._loginResult.rank=="STU"
6094
6095 @property
6096 def loggedIn(self):
6097 """Indicate if there was a successful login."""
6098 return self._loginResult is not None
6099
6100 @property
6101 def loginResult(self):
6102 """Get the login result."""
6103 return self._loginResult
6104
6105 @property
6106 def checkFlightAircraftType(self):
6107 """Get the type of the aircraft used to perform the check flight."""
6108 return self._studentPage.aircraftType
6109
6110 def setCurrentPage(self, index, finalize = False, fromPageShift = None):
6111 """Set the current page to the one with the given index.
6112
6113 @param fromPageShift if given, the relative index of one of the
6114 previous pages that should be used as the from-page of the next
6115 page. E.g. if fromPageShift is 1, the previous page will be the
6116 from-page."""
6117 assert index < len(self._pages)
6118
6119 fromPage = self._currentPage
6120 if fromPage is not None:
6121 page = self._pages[fromPage]
6122 if finalize and not page._completed:
6123 page.complete()
6124 page.prepareHide()
6125 self.remove(page)
6126 if fromPageShift is not None:
6127 fromPage -= fromPageShift
6128
6129 self._currentPage = index
6130 page = self._pages[index]
6131 self.add(page)
6132 if page._fromPage is None:
6133 page._fromPage = fromPage
6134 page.initialize()
6135 page.prepareShow()
6136 self.show_all()
6137 if fromPage is not None:
6138 self.grabDefault()
6139
6140 @property
6141 def bookedFlight(self):
6142 """Get the booked flight selected."""
6143 return self._bookedFlight
6144
6145 @property
6146 def numCockpitCrew(self):
6147 """Get the number of cockpit crew members."""
6148 return self._payloadPage.numCockpitCrew
6149
6150 @property
6151 def numCabinCrew(self):
6152 """Get the number of cabin crew members."""
6153 return self._payloadPage.numCabinCrew
6154
6155 @property
6156 def numPassengers(self):
6157 """Get the number of passengers."""
6158 return self._payloadPage.numPassengers
6159
6160 @property
6161 def numChildren(self):
6162 """Get the number of child passengers."""
6163 return self._payloadPage.numChildren
6164
6165 @property
6166 def numInfants(self):
6167 """Get the number of infant passengers."""
6168 return self._payloadPage.numInfants
6169
6170 @property
6171 def bagWeight(self):
6172 """Get the baggage weight."""
6173 return self._payloadPage.bagWeight
6174
6175 @property
6176 def cargoWeight(self):
6177 """Get the cargo weight."""
6178 return self._payloadPage.cargoWeight
6179
6180 @property
6181 def mailWeight(self):
6182 """Get the mail weight."""
6183 return self._payloadPage.mailWeight
6184
6185 @property
6186 def zfw(self):
6187 """Get the calculated ZFW value."""
6188 return 0 if self._bookedFlight is None \
6189 else self._payloadPage.calculateZFW()
6190
6191 @property
6192 def filedCruiseLevel(self):
6193 """Get the filed cruise level."""
6194 return self._routePage.filedCruiseLevel
6195
6196 @property
6197 def filedCruiseAltitude(self):
6198 """Get the filed cruise altitude."""
6199 return self._routePage.filedCruiseLevel * 100
6200
6201 @property
6202 def cruiseAltitude(self):
6203 """Get the cruise altitude."""
6204 level = self._cruisePage.cruiseLevel if self._cruisePage.activated \
6205 else self._routePage.filedCruiseLevel
6206 return level * 100
6207
6208 @property
6209 def loggableCruiseAltitude(self):
6210 """Get the cruise altitude that can be logged."""
6211 if self._cruisePage.activated:
6212 return self._cruisePage.loggableCruiseLevel * 100
6213 else:
6214 return 0
6215
6216 @property
6217 def route(self):
6218 """Get the route."""
6219 return self._routePage.route
6220
6221 @property
6222 def alternate(self):
6223 """Get the ICAO code of the alternate airport."""
6224 return self._routePage.alternate
6225
6226 @property
6227 def departureMETAR(self):
6228 """Get the METAR of the departure airport."""
6229 return self._departureBriefingPage.metar
6230
6231 @property
6232 def arrivalMETAR(self):
6233 """Get the METAR of the arrival airport."""
6234 return self._arrivalBriefingPage.metar
6235
6236 @property
6237 def departureRunway(self):
6238 """Get the departure runway."""
6239 return self._takeoffPage.runway
6240
6241 @property
6242 def sid(self):
6243 """Get the SID."""
6244 return self._takeoffPage.sid
6245
6246 @property
6247 def v1(self):
6248 """Get the V1 speed."""
6249 return self._takeoffPage.v1
6250
6251 @property
6252 def vr(self):
6253 """Get the Vr speed."""
6254 return self._takeoffPage.vr
6255
6256 @property
6257 def v2(self):
6258 """Get the V2 speed."""
6259 return self._takeoffPage.v2
6260
6261 @property
6262 def derate(self):
6263 """Get the derate value."""
6264 return self._takeoffPage.derate
6265
6266 @property
6267 def takeoffAntiIceOn(self):
6268 """Get whether the anti-ice system was on during take-off."""
6269 return self._takeoffPage.antiIceOn
6270
6271 @takeoffAntiIceOn.setter
6272 def takeoffAntiIceOn(self, value):
6273 """Set anti-ice on indicator."""
6274 self._takeoffPage.antiIceOn = value
6275
6276 @property
6277 def rtoIndicated(self):
6278 """Get whether the pilot has indicated that an RTO has occured."""
6279 return self._takeoffPage.rtoIndicated
6280
6281 @property
6282 def arrivalRunway(self):
6283 """Get the arrival runway."""
6284 return self._landingPage.runway
6285
6286 @property
6287 def star(self):
6288 """Get the STAR."""
6289 return self._landingPage.star
6290
6291 @property
6292 def transition(self):
6293 """Get the transition."""
6294 return self._landingPage.transition
6295
6296 @property
6297 def approachType(self):
6298 """Get the approach type."""
6299 return self._landingPage.approachType
6300
6301 @property
6302 def vref(self):
6303 """Get the Vref speed."""
6304 return self._landingPage.vref
6305
6306 @property
6307 def landingAntiIceOn(self):
6308 """Get whether the anti-ice system was on during landing."""
6309 return self._landingPage.antiIceOn
6310
6311 @landingAntiIceOn.setter
6312 def landingAntiIceOn(self, value):
6313 """Set anti-ice on indicator."""
6314 self._landingPage.antiIceOn = value
6315
6316 @property
6317 def flightType(self):
6318 """Get the flight type."""
6319 return self._finishPage.flightType
6320
6321 @property
6322 def online(self):
6323 """Get whether the flight was online or not."""
6324 return self._finishPage.online
6325
6326 @property
6327 def usingSimBrief(self):
6328 """Indicate if we are using a SimBrief briefing or not."""
6329 return self._usingSimBrief
6330
6331 @usingSimBrief.setter
6332 def usingSimBrief(self, x):
6333 """Set whether we are using a SimBrief briefing or not."""
6334 self._usingSimBrief = x
6335
6336 def nextPage(self, finalize = True):
6337 """Go to the next page."""
6338 nextPageID = self._pages[self._currentPage].nextPageID
6339 self.jumpPage(1 if nextPageID is None else nextPageID, finalize)
6340
6341 def jumpPage(self, countOrID, finalize = True, fromPageShift = None):
6342 """Go to the page which is 'count' pages after the current one."""
6343 if isinstance(countOrID, str):
6344 targetIndex = self._getIndexOf(countOrID)
6345 else:
6346 targetIndex = self._currentPage + countOrID
6347 self.setCurrentPage(targetIndex,
6348 finalize = finalize, fromPageShift = fromPageShift)
6349
6350 def grabDefault(self):
6351 """Make the default button of the current page the default."""
6352 self._pages[self._currentPage].grabDefault()
6353
6354 def connected(self, fsType, descriptor):
6355 """Called when the connection could be made to the simulator."""
6356 self.nextPage()
6357
6358 def reset(self, loginResult):
6359 """Resets the wizard to go back to the login page."""
6360 self._initialize(keepLoginResult = loginResult is None,
6361 loginResult = loginResult)
6362
6363 def setStage(self, stage):
6364 """Set the flight stage to the given one."""
6365 if stage!=const.STAGE_END:
6366 self._cruisePage.setLoggable(Flight.canLogCruiseAltitude(stage))
6367
6368 if stage==const.STAGE_TAKEOFF:
6369 self._takeoffPage.allowForward()
6370 elif stage==const.STAGE_LANDING:
6371 if not self._arrivalBriefingPage.metarEdited:
6372 print("Downloading arrival METAR again")
6373 self.gui.webHandler.getMETARs(self._arrivalMETARCallback,
6374 [self._bookedFlight.arrivalICAO])
6375
6376 elif stage==const.STAGE_END:
6377 for page in self._pages:
6378 page.flightEnded()
6379
6380 def _initialize(self, keepLoginResult = False, loginResult = None):
6381 """Initialize the wizard."""
6382 if not keepLoginResult:
6383 self._loginResult = loginResult
6384
6385 self._loginCallback = None
6386
6387 self._fleet = None
6388 self._fleetCallback = None
6389
6390 self._bookedFlight = None
6391 self._departureGate = "-"
6392 self._fuelData = None
6393 self._departureNOTAMs = None
6394 self._departureMETAR = None
6395 self._arrivalNOTAMs = None
6396 self._arrivalMETAR = None
6397 self._usingSimBrief = None
6398 self.takeoffRunway = None
6399 self.landingRunway = None
6400
6401 firstPage = 0 if self._loginResult is None else 1
6402 for page in self._pages[firstPage:]:
6403 page.reset()
6404
6405 self.setCurrentPage(firstPage)
6406 #self.setCurrentPage(10)
6407
6408 def login(self, callback, pilotID, password):
6409 """Called when the login button was clicked."""
6410 self._loginCallback = callback
6411 if pilotID is None:
6412 loginResult = self._loginResult
6413 assert loginResult is not None and loginResult.loggedIn
6414 pilotID = loginResult.pilotID
6415 password = loginResult.password
6416 busyMessage = xstr("reload_busy")
6417 else:
6418 self._loginResult = None
6419 busyMessage = xstr("login_busy")
6420
6421 self.gui.beginBusy(busyMessage)
6422
6423 self.gui.webHandler.login(self._loginResultCallback,
6424 pilotID, password)
6425
6426 def reloadFlights(self, callback):
6427 """Reload the flights from the MAVA server."""
6428 self.login(callback, None, None)
6429
6430 def addFlight(self, bookedFlight):
6431 """Add the given booked flight to the flight selection page."""
6432 self._flightSelectionPage.addFlight(bookedFlight)
6433
6434 def reflyFlight(self, bookedFlight):
6435 """Add the given booked flight to the flight selection page."""
6436 self._removePendingFlight(bookedFlight)
6437 self._flightSelectionPage._reflyFlight(bookedFlight)
6438
6439 def deleteFlight(self, bookedFlight):
6440 """Remove the given flight from the pending flight list."""
6441 self._removePendingFlight(bookedFlight)
6442 self._flightSelectionPage._updatePending()
6443
6444 def cancelFlight(self, reloadCallback):
6445 """Cancel the flight.
6446
6447 If it is an entry exam flight, we go back to the student page.
6448 Otherwise we reload the flights."""
6449 if self.entranceExam:
6450 self.reset(None)
6451 self.jumpPage("student")
6452 else:
6453 self.reloadFlights(reloadCallback)
6454
6455 def cruiseLevelChanged(self):
6456 """Called when the cruise level is changed."""
6457 return self.gui.cruiseLevelChanged()
6458
6459 def metarChanged(self, metar, originator):
6460 """Called when a METER is changed on on of the pages.
6461
6462 originator is the page that originated the changed. It will be used to
6463 determine which METAR (departure or arrival) has changed."""
6464 metar = metar.upper()
6465 if originator in [self._departureBriefingPage, self._takeoffPage]:
6466 self.departureMETARChanged(metar, originator)
6467 else:
6468 self.arrivalMETARChanged(metar, originator)
6469
6470 def departureMETARChanged(self, metar, originator):
6471 """Called when the departure METAR has been edited on one of the
6472 pages.
6473
6474 originator is the page that originated the change. It will not be
6475 called to set the METAR, while others will be."""
6476 for page in [self._departureBriefingPage, self._takeoffPage]:
6477 if page is not originator:
6478 page.changeMETAR(metar)
6479
6480 def arrivalMETARChanged(self, metar, originator):
6481 """Called when the arrival METAR has been edited on one of the
6482 pages.
6483
6484 originator is the page that originated the change. It will not be
6485 called to set the METAR, while others will be."""
6486 for page in [self._arrivalBriefingPage, self._landingPage]:
6487 if page is not originator:
6488 page.changeMETAR(metar)
6489
6490 def _removePendingFlight(self, flight):
6491 """Remove the given pending flight from the login result."""
6492 for flights in [self._loginResult.reportedFlights,
6493 self._loginResult.rejectedFlights]:
6494 for f in flights:
6495 if f.id==flight.id:
6496 flights.remove(f)
6497 return
6498
6499 def _loginResultCallback(self, returned, result):
6500 """The login result callback, called in the web handler's thread."""
6501 GObject.idle_add(self._handleLoginResult, returned, result)
6502
6503 def _handleLoginResult(self, returned, result):
6504 """Handle the login result."""
6505 self.gui.endBusy()
6506 isReload = self._loginResult is not None
6507 if returned:
6508 if result.loggedIn:
6509 self._loginResult = result
6510 self.gui.loginSuccessful()
6511 else:
6512 if isReload:
6513 message = xstr("reload_failed")
6514 else:
6515 message = xstr("login_entranceExam_invalid"
6516 if self.entranceExam else
6517 xstr("login_invalid"))
6518 dialog = Gtk.MessageDialog(parent = self.gui.mainWindow,
6519 type = Gtk.MessageType.ERROR,
6520 message_format = message)
6521 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
6522 dialog.set_title(WINDOW_TITLE_BASE)
6523 if isReload:
6524 secondary = xstr("reload_failed_sec")
6525 else:
6526 secondary = xstr("login_entranceExam_invalid_sec"
6527 if self.entranceExam else
6528 xstr("login_invalid_sec"))
6529 dialog.format_secondary_markup(secondary)
6530 dialog.run()
6531 dialog.hide()
6532 else:
6533 message = xstr("reload_failconn") if isReload \
6534 else xstr("login_failconn")
6535 dialog = Gtk.MessageDialog(parent = self.gui.mainWindow,
6536 type = Gtk.MessageType.ERROR,
6537 message_format = message)
6538 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
6539 dialog.set_title(WINDOW_TITLE_BASE)
6540 secondary = xstr("reload_failconn_sec") if isReload \
6541 else xstr("login_failconn_sec")
6542 dialog.format_secondary_markup(secondary)
6543
6544 dialog.run()
6545 dialog.hide()
6546
6547 callback = self._loginCallback
6548 self._loginCallback = None
6549 callback(returned, result)
6550
6551 def getFleet(self, callback, force = False):
6552 """Get the fleet via the GUI and call the given callback."""
6553 self._fleetCallback = callback
6554 self.gui.getFleet(callback = self._fleetRetrieved, force = force)
6555
6556 def _fleetRetrieved(self, fleet):
6557 """Callback for the fleet retrieval."""
6558 self._fleet = fleet
6559 if self._fleetCallback is not None:
6560 self._fleetCallback(fleet)
6561 self._fleetCallback = None
6562
6563 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
6564 """Update the given plane's gate information."""
6565 self.gui.updatePlane(tailNumber, status, gateNumber = gateNumber,
6566 callback = callback)
6567
6568 def updateRTO(self):
6569 """Update the RTO state.
6570
6571 The RTO checkbox will be enabled if the flight has an RTO state and the
6572 comments field contains some text."""
6573 flight = self.gui.flight
6574 rtoEnabled = flight is not None and flight.hasRTO and \
6575 self.gui.hasComments
6576 self._takeoffPage.setRTOEnabled(rtoEnabled)
6577
6578 def commentsChanged(self):
6579 """Called when the comments have changed."""
6580 self.updateRTO()
6581 self._finishPage.updateButtons()
6582
6583 def delayCodesChanged(self):
6584 """Called when the delay codes have changed."""
6585 self._finishPage.updateButtons()
6586
6587 def faultExplanationsChanged(self):
6588 """Called when the faults and their explanations have changed."""
6589 self._finishPage.updateButtons()
6590
6591 def rtoToggled(self, indicated):
6592 """Called when the RTO indication has changed."""
6593 self.gui.rtoToggled(indicated)
6594
6595 def _connectSimulator(self, simulatorType):
6596 """Connect to the simulator."""
6597 self.gui.connectSimulator(self._bookedFlight, simulatorType)
6598
6599 def _arrivalMETARCallback(self, returned, result):
6600 """Called when the METAR of the arrival airport is retrieved."""
6601 GObject.idle_add(self._handleArrivalMETAR, returned, result)
6602
6603 def _handleArrivalMETAR(self, returned, result):
6604 """Called when the METAR of the arrival airport is retrieved."""
6605 icao = self._bookedFlight.arrivalICAO
6606 if returned and icao in result.metars:
6607 metar = result.metars[icao]
6608 if metar!="":
6609 self._arrivalBriefingPage.setMETAR(metar)
6610
6611 def _getIndexOf(self, pageID):
6612 """Get the index for the given page ID.
6613
6614 It is an assertion failure if the ID is not found."""
6615 for index in range(0, len(self._pages)):
6616 page = self._pages[index]
6617 if page.id==pageID:
6618 return index
6619 assert False
6620
6621#-----------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.