source: src/mlx/gui/flight.py@ 1086:54924f6e0818

python3
Last change on this file since 1086:54924f6e0818 was 1086:54924f6e0818, checked in by István Váradi <ivaradi@…>, 20 months ago

The Next button's sensitivity on the SimBrief setup page is updated when the checkbox for using the internal browser is clicked.

File size: 248.2 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(7, 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 frame = Gtk.Frame.new()
3256 self._useInternalBrowser = Gtk.CheckButton(xstr("simbrief_uib"))
3257 frame.set_label_widget(self._useInternalBrowser)
3258 frame.set_label_align(0.025, 0.5)
3259
3260 self._useInternalBrowser.set_use_underline(True)
3261 self._useInternalBrowser.connect("toggled",
3262 self._useInternalBrowserToggled)
3263 self._useInternalBrowser.set_tooltip_text(xstr("simbrief_uib_tooltip"))
3264
3265 uibTable = Gtk.Table(2, 3)
3266 uibTable.set_row_spacings(4)
3267 uibTable.set_col_spacings(16)
3268 uibTable.set_homogeneous(False)
3269
3270 uibRow = 0
3271
3272 label = Gtk.Label(xstr("simbrief_username"))
3273 label.set_use_underline(True)
3274 label.set_alignment(0.0, 0.5)
3275 uibTable.attach(label, 0, 1, uibRow, uibRow+1)
3276
3277 self._userName = Gtk.Entry()
3278 self._userName.set_width_chars(16)
3279 self._userName.connect("changed",
3280 lambda button: self._updateForwardButton())
3281 self._userName.set_tooltip_text(xstr("simbrief_username_tooltip"))
3282 uibTable.attach(self._userName, 1, 2, uibRow, uibRow+1)
3283 label.set_mnemonic_widget(self._userName)
3284 uibRow += 1
3285
3286 label = Gtk.Label(xstr("simbrief_password"))
3287 label.set_use_underline(True)
3288 label.set_alignment(0.0, 0.5)
3289 uibTable.attach(label, 0, 1, uibRow, uibRow+1)
3290
3291 self._password = Gtk.Entry()
3292 self._password.set_visibility(False)
3293 self._password.connect("changed",
3294 lambda button: self._updateForwardButton())
3295 self._password.set_tooltip_text(xstr("simbrief_password_tooltip"))
3296 uibTable.attach(self._password, 1, 2, uibRow, uibRow+1)
3297 label.set_mnemonic_widget(self._password)
3298
3299 self._rememberButton = Gtk.CheckButton(xstr("simbrief_remember_password"))
3300 self._rememberButton.set_use_underline(True)
3301 self._rememberButton.set_tooltip_text(xstr("simbrief_remember_tooltip"))
3302 uibTable.attach(self._rememberButton, 2, 3, uibRow, uibRow+1, ypadding = 8)
3303 uibRow += 1
3304
3305 uibTable.set_margin_top(6)
3306 uibTable.set_margin_bottom(6)
3307 uibTable.set_margin_left(4)
3308 uibTable.set_margin_right(4)
3309
3310 frame.add(uibTable)
3311 frame.set_margin_bottom(10)
3312
3313 table.attach(frame, 0, 3, row, row+1)
3314 row += 1
3315
3316 label = Gtk.Label(xstr("simbrief_extra_fuel"))
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._extraFuel = IntegerEntry(defaultValue = 0)
3322 self._extraFuel.set_width_chars(6)
3323 self._extraFuel.set_tooltip_text(xstr("simbrief_extra_fuel_tooltip"))
3324 table.attach(self._extraFuel, 1, 2, row, row+1)
3325 label.set_mnemonic_widget(self._extraFuel)
3326
3327 label = Gtk.Label("kg")
3328 label.set_alignment(0.0, 0.5)
3329 table.attach(label, 2, 3, row, row+1)
3330 row += 1
3331
3332 label = Gtk.Label(xstr("simbrief_takeoff_runway"))
3333 label.set_use_underline(True)
3334 label.set_alignment(0.0, 0.5)
3335 table.attach(label, 0, 1, row, row+1)
3336
3337 self._takeoffRunway = Gtk.Entry()
3338 self._takeoffRunway.set_width_chars(10)
3339 self._takeoffRunway.set_tooltip_text(xstr("simbrief_takeoff_runway_tooltip"))
3340 self._takeoffRunway.connect("changed", self._upperChanged)
3341 table.attach(self._takeoffRunway, 1, 2, row, row+1)
3342 label.set_mnemonic_widget(self._takeoffRunway)
3343 row += 1
3344
3345 label = Gtk.Label(xstr("simbrief_landing_runway"))
3346 label.set_use_underline(True)
3347 label.set_alignment(0.0, 0.5)
3348 table.attach(label, 0, 1, row, row+1)
3349
3350 self._landingRunway = Gtk.Entry()
3351 self._landingRunway.set_width_chars(10)
3352 self._landingRunway.set_tooltip_text(xstr("simbrief_landing_runway_tooltip"))
3353 self._landingRunway.connect("changed", self._upperChanged)
3354 table.attach(self._landingRunway, 1, 2, row, row+1)
3355 label.set_mnemonic_widget(self._landingRunway)
3356 row += 1
3357
3358 label = Gtk.Label(xstr("simbrief_climb_profile"))
3359 label.set_use_underline(True)
3360 label.set_alignment(0.0, 0.5)
3361 table.attach(label, 0, 1, row, row+1)
3362
3363 self._climbProfile = Gtk.ComboBox()
3364 renderer = Gtk.CellRendererText()
3365 self._climbProfile.pack_start(renderer, True)
3366 self._climbProfile.add_attribute(renderer, "text", 0)
3367 self._climbProfile.set_tooltip_text(xstr("simbrief_climb_profile_tooltip"))
3368 table.attach(self._climbProfile, 1, 2, row, row+1)
3369 label.set_mnemonic_widget(self._climbProfile)
3370 row += 1
3371
3372 label = Gtk.Label(xstr("simbrief_cruise_profile"))
3373 label.set_use_underline(True)
3374 label.set_alignment(0.0, 0.5)
3375 table.attach(label, 0, 1, row, row+1)
3376
3377 self._cruiseProfile = Gtk.ComboBox()
3378 renderer = Gtk.CellRendererText()
3379 self._cruiseProfile.pack_start(renderer, True)
3380 self._cruiseProfile.add_attribute(renderer, "text", 0)
3381 self._cruiseProfile.set_tooltip_text(xstr("simbrief_cruise_profile_tooltip"))
3382 table.attach(self._cruiseProfile, 1, 2, row, row+1)
3383 label.set_mnemonic_widget(self._cruiseProfile)
3384 row += 1
3385
3386 label = Gtk.Label(xstr("simbrief_descent_profile"))
3387 label.set_use_underline(True)
3388 label.set_alignment(0.0, 0.5)
3389 table.attach(label, 0, 1, row, row+1)
3390
3391 self._descentProfile = Gtk.ComboBox()
3392 renderer = Gtk.CellRendererText()
3393 self._descentProfile.pack_start(renderer, True)
3394 self._descentProfile.add_attribute(renderer, "text", 0)
3395 self._descentProfile.set_tooltip_text(xstr("simbrief_descent_profile_tooltip"))
3396 table.attach(self._descentProfile, 1, 2, row, row+1)
3397 label.set_mnemonic_widget(self._descentProfile)
3398
3399 self.addCancelFlightButton()
3400
3401 self._backButton = self.addPreviousButton(clicked = self._backClicked)
3402 self._button = self.addNextButton(clicked = self._forwardClicked)
3403
3404 self._resultTimestamp = None
3405
3406 def activate(self):
3407 """Activate the SimBrief setup page"""
3408 config = self._wizard.gui.config
3409
3410 self._useInternalBrowser.set_active(config.useInternalBrowserForSimBrief)
3411 self._useInternalBrowser.set_sensitive(True)
3412
3413 self._userName.set_text(config.simBriefUserName)
3414 self._userName.set_sensitive(config.useInternalBrowserForSimBrief)
3415
3416 self._password.set_text(config.simBriefPassword)
3417 self._password.set_sensitive(config.useInternalBrowserForSimBrief)
3418
3419 self._rememberButton.set_active(config.rememberSimBriefPassword)
3420 self._rememberButton.set_sensitive(config.useInternalBrowserForSimBrief)
3421
3422 self._extraFuel.set_int(0)
3423 self._extraFuel.set_sensitive(True)
3424
3425 self._takeoffRunway.set_text("")
3426 self._takeoffRunway.set_sensitive(True)
3427
3428 self._landingRunway.set_text("")
3429 self._landingRunway.set_sensitive(True)
3430
3431 simBriefData = self._wizard.gui.flight.aircraft.simBriefData
3432 for (control, profiles) in [(self._climbProfile,
3433 simBriefData.climbProfiles),
3434 (self._cruiseProfile,
3435 simBriefData.cruiseProfiles),
3436 (self._descentProfile,
3437 simBriefData.descentProfiles)]:
3438 model = Gtk.ListStore(str)
3439 for profile in profiles:
3440 model.append([profile])
3441 control.set_model(model)
3442 control.set_sensitive(True)
3443
3444 self._climbProfile.set_active(0)
3445 self._cruiseProfile.set_active(0)
3446 self._descentProfile.set_active(0)
3447
3448 self._resultTimestamp = None
3449 self._waitEnd = None
3450
3451 self._updateForwardButton()
3452
3453 def _updateForwardButton(self):
3454 """Update the sensitivity of the forward button."""
3455 self._button.set_sensitive(True)
3456 self._button.set_sensitive(len(self._userName.get_text())>0 and
3457 len(self._password.get_text())>0)
3458
3459 def _backClicked(self, button):
3460 """Called when the Back button is pressed."""
3461 self.goBack()
3462
3463 def _forwardClicked(self, button):
3464 if self._completed:
3465 self._wizard.nextPage()
3466 else:
3467 config = self._wizard.gui.config
3468
3469 config.simBriefUserName = self._userName.get_text()
3470
3471 rememberPassword = self._rememberButton.get_active()
3472 config.simBriefPassword = \
3473 self._password.get_text() if rememberPassword else ""
3474 config.rememberSimBriefPassword = rememberPassword
3475
3476 config.useInternalBrowserForSimBrief = \
3477 self._useInternalBrowser.get_active()
3478
3479 config.save()
3480
3481 self._wizard.gui.simulator.requestTime(self._handleTime)
3482
3483 def _useInternalBrowserToggled(self, checkButton):
3484 """Called when the check button indicating if the internal browser
3485 should be used is toggled."""
3486 useInternalBrowserForSimBrief = self._useInternalBrowser.get_active()
3487 self._userName.set_sensitive(useInternalBrowserForSimBrief)
3488 self._password.set_sensitive(useInternalBrowserForSimBrief)
3489 self._rememberButton.set_sensitive(useInternalBrowserForSimBrief)
3490 self._updateForwardButton()
3491
3492 def _handleTime(self, simulatorNow):
3493 """Handle the result of a time retrieval."""
3494 GObject.idle_add(self._processTime, simulatorNow)
3495
3496 def _processTime(self, simulatorNow):
3497 """Process the given time."""
3498 plan = self._getPlan(simulatorNow)
3499 print("plan:", plan)
3500
3501 takeoffRunway = self._takeoffRunway.get_text()
3502 if takeoffRunway:
3503 self._wizard.takeoffRunway = takeoffRunway
3504
3505 landingRunway = self._landingRunway.get_text()
3506 if landingRunway:
3507 self._wizard.landingRunway = landingRunway
3508
3509 self._useInternalBrowser.set_sensitive(False)
3510 self._userName.set_sensitive(False)
3511 self._password.set_sensitive(False)
3512 self._rememberButton.set_sensitive(False)
3513 self._extraFuel.set_sensitive(False)
3514 self._takeoffRunway.set_sensitive(False)
3515 self._landingRunway.set_sensitive(False)
3516
3517 self._climbProfile.set_sensitive(False)
3518 self._cruiseProfile.set_sensitive(False)
3519 self._descentProfile.set_sensitive(False)
3520
3521 self._wizard.gui.beginBusy(xstr("simbrief_calling"))
3522
3523 url = cef.SimBriefHandler.getFormURL(plan)
3524 print("url:", url)
3525
3526 config = self._wizard.gui.config
3527 if config.useInternalBrowserForSimBrief:
3528 self._waitEnd = 0
3529 cef.callSimBrief(plan,
3530 self._getCredentials,
3531 self._simBriefProgress,
3532 SimBriefSetupPage.getHTMLFilePath())
3533 else:
3534 webbrowser.open(url = url, new = 1)
3535 self._waitEnd = time.time() + SimBriefSetupPage._waitTimeout
3536 GObject.timeout_add(SimBriefSetupPage._resultQueryInterval,
3537 lambda: self._wizard.gui.webHandler. \
3538 getSimBriefResult(self._resultCallback,
3539 self._resultTimestamp))
3540
3541
3542 startSound(const.SOUND_NOTAM)
3543
3544 def _getCredentials(self, count):
3545 """Get the credentials.
3546
3547 If count is 0, the user name and password entered into the setup page
3548 are returned. Otherwise a dialog box is displayed informing the user of
3549 invalid credentials and requesting another set of them."""
3550 print("_getCredentials", count)
3551 if count==0:
3552 return (self._userName.get_text(), self._password.get_text())
3553 else:
3554 gui = self._wizard.gui
3555 config = gui.config
3556
3557 dialog = SimBriefCredentialsDialog(gui,
3558 config.simBriefUserName,
3559 config.simBriefPassword,
3560 config.rememberSimBriefPassword)
3561 response = dialog.run()
3562
3563 if response==Gtk.ResponseType.OK:
3564 userName = dialog.userName
3565 self._userName.set_text(userName)
3566 password = dialog.password
3567 self._password.set_text(password)
3568 rememberPassword = dialog.rememberPassword
3569
3570 config.simBriefUserName = userName
3571
3572 config.simBriefPassword = \
3573 password if rememberPassword else ""
3574 config.rememberSimBriefPassword = rememberPassword
3575
3576 config.save()
3577
3578 return (userName, password)
3579 else:
3580 return (None, None)
3581
3582 def _simBriefProgress(self, progress, result, flightInfo):
3583 """The real SimBrief progress handler."""
3584 print("_simBriefProgress", progress, result, flightInfo)
3585 if result==cef.SIMBRIEF_RESULT_NONE:
3586 message = SimBriefSetupPage.progress2Message.get(progress,
3587 "simbrief_progress_unknown")
3588 self._wizard.gui.updateBusyState(xstr(message))
3589 else:
3590 if result==cef.SIMBRIEF_RESULT_OK:
3591 self._waitEnd = 0
3592 self._wizard.gui.webHandler. \
3593 getSimBriefResult(self._resultCallback,
3594 self._resultTimestamp)
3595 else:
3596 self._wizard.gui.endBusy()
3597
3598 message = SimBriefSetupPage.result2Message.get(result,
3599 "simbrief_result_unknown")
3600 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
3601 type = Gtk.MessageType.ERROR,
3602 message_format =
3603 xstr(message) + "\n"+
3604 xstr("simbrief_cancelled"))
3605
3606 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
3607 dialog.set_title(WINDOW_TITLE_BASE)
3608 secondary = xstr("flightsel_save_failed_sec")
3609 dialog.format_secondary_markup(secondary)
3610 dialog.run()
3611 dialog.hide()
3612
3613 self._wizard.usingSimBrief = False
3614 self._wizard.jumpPage("fuel", fromPageShift = 1)
3615
3616 def _getPlan(self, simulatorNow):
3617 """Get the flight plan data for SimBrief."""
3618 print("timestamp:", simulatorNow)
3619
3620 self._resultTimestamp = now = int(time.time())
3621
3622 plan = {
3623 "airline": "MAH",
3624 "selcal": "XXXX",
3625 "fuelfactor": "P000",
3626 "contpct": "0.05",
3627 "resvrule": "45",
3628 "taxiout": "10",
3629 "taxiin": "10",
3630 "civalue": "AUTO",
3631 "lang": "hun" if getLanguage().lower()=="hu" else "eng",
3632 "sessionID": self._wizard._loginResult.sessionID,
3633 "timestamp": now
3634 }
3635
3636 wizard = self._wizard
3637 gui = wizard.gui
3638
3639 loginResult = wizard.loginResult
3640 plan["cpt"] = loginResult.pilotName
3641 plan["pid"] = loginResult.pilotID
3642
3643 bookedFlight = wizard.bookedFlight
3644 plan["fltnum"] = wizard.bookedFlight.callsign[2:]
3645 plan["type"] = const.icaoCodes[bookedFlight.aircraftType]
3646 plan["orig"] = bookedFlight.departureICAO
3647 plan["dest"] = bookedFlight.arrivalICAO
3648 plan["reg"] = bookedFlight.tailNumber
3649 plan["pax"] = str(bookedFlight.numPassengers)
3650
3651 departureTime = bookedFlight.departureTime
3652 print("departureTime", departureTime)
3653
3654 tm = time.gmtime(simulatorNow)
3655 diffMinutes = (departureTime.hour - tm.tm_hour)*60 + \
3656 departureTime.minute - tm.tm_min
3657 while diffMinutes<0:
3658 diffMinutes += 24*60
3659
3660 realDepartureTime = datetime.datetime.utcnow()
3661 realDepartureTime += datetime.timedelta(minutes = diffMinutes)
3662
3663 plan["date"] = "%02d%s%02d" % (realDepartureTime.day,
3664 SimBriefSetupPage.monthNum2Name[realDepartureTime.month-1],
3665 realDepartureTime.year%100)
3666 plan["deph"] = str(realDepartureTime.hour)
3667 plan["depm"] = str(realDepartureTime.minute)
3668
3669 arrivalTime = bookedFlight.arrivalTime
3670 blockMinutes = (arrivalTime.hour - departureTime.hour)*60 + \
3671 arrivalTime.minute - departureTime.minute
3672 while blockMinutes<0:
3673 blockMinutes += 24*60
3674 plan["steh"] = str(blockMinutes//60)
3675 plan["stem"] = str(blockMinutes%60)
3676
3677 plan["manualzfw"] = str(wizard.zfw / 1000.0)
3678 plan["cargo"] = str((wizard.bagWeight + wizard.cargoWeight + wizard.mailWeight)/1000.0)
3679
3680 plan["route"] = wizard.route
3681 plan["fl"] = str(wizard.filedCruiseAltitude)
3682 plan["altn"] = wizard.alternate
3683
3684 plan["addedfuel"] = str(self._extraFuel.get_int() / 1000.0)
3685 plan["origrwy"] = self._takeoffRunway.get_text()
3686 plan["destrwy"] = self._landingRunway.get_text()
3687
3688 for (key, control) in [("climb", self._climbProfile),
3689 ("cruise", self._cruiseProfile),
3690 ("descent", self._descentProfile)]:
3691 model = control.get_model()
3692 active = control.get_active_iter()
3693 value = model.get_value(active, 0)
3694 plan[key] = value
3695
3696 return plan
3697
3698 def _resultCallback(self, returned, result):
3699 """Callback for the SimBrief result query."""
3700 GObject.idle_add(self._handleResult, returned, result)
3701
3702 def _handleResult(self, returned, result):
3703 """Handle an RPC query result."""
3704 print("_handleResult", returned, result)
3705 if result.result and result.result.get("result"):
3706 link = result.result.get("result")
3707 if link=="<failed>":
3708 self._wizard.gui.endBusy()
3709
3710 self._finishWithError()
3711 else:
3712 link ="https://www.simbrief.com/ofp/flightplans/xml/" + link + ".xml"
3713
3714 thread = threading.Thread(target = self._getResults, args = (link,))
3715 thread.daemon = True
3716 thread.start()
3717
3718 elif time.time() >= self._waitEnd:
3719 self._wizard.gui.endBusy()
3720
3721 self._finishWithError()
3722 else:
3723 GObject.timeout_add(SimBriefSetupPage._resultQueryInterval,
3724 lambda: self._wizard.gui.webHandler. \
3725 getSimBriefResult(self._resultCallback,
3726 self._resultTimestamp))
3727
3728 def _upperChanged(self, entry, arg = None):
3729 """Called when the value of some entry widget has changed and the value
3730 should be converted to uppercase."""
3731 entry.set_text(entry.get_text().upper())
3732
3733 def _getResults(self, link):
3734 """Get the result from the given link."""
3735 ## Holds analysis data to be used
3736 flightInfo = {}
3737 try:
3738 availableInfo = {}
3739
3740 # Obtaining the xml
3741 response = urllib.request.urlopen(link,
3742 cafile = certifi.where())
3743 content = etree.iterparse(response)
3744
3745 for (action, element) in content:
3746 # Processing tags that occur multiple times
3747 if element.tag == "weather":
3748 weatherElementList = list(element)
3749 for weatherElement in weatherElementList:
3750 flightInfo[weatherElement.tag] = weatherElement.text
3751 elif element.tag in ["files", "prefile"]:
3752 availableInfo[element.tag] = element
3753 else:
3754 availableInfo[element.tag] = element.text
3755
3756 # Processing plan_html
3757 ## Obtaining chart links
3758 imageLinks = []
3759 for imageLinkElement in lxml.html.find_class(availableInfo["plan_html"],
3760 "ofpmaplink"):
3761 for imageLink in imageLinkElement.iterlinks():
3762 if imageLink[1] == 'src':
3763 imageLinks.append(imageLink[2])
3764 flightInfo["image_links"] = imageLinks
3765 print((sorted(availableInfo.keys())))
3766 htmlFilePath = SimBriefSetupPage.getHTMLFilePath()
3767 with open(htmlFilePath, 'w') as f:
3768 f.write("<html>")
3769 f.write("<body>")
3770 f.write("<h3 style=\"text-align: center;\">%s</h3></br>" % (xstr("simbrief_result_briefing"),))
3771 f.write("<div style=\"height: 300px; width: fit-content; overflow: auto; margin: 0 auto;\">")
3772 f.write(availableInfo["plan_html"])
3773 f.write("</div>\n")
3774
3775 f.write("<h2/>");
3776 f.write("<h3 style=\"text-align: center;\">%s</h3></br>" % (xstr("simbrief_result_downloads"),))
3777 f.write("<div style=\"height: 300px; width: fit-content; overflow: auto; margin: 0 auto\">")
3778 f.write("<table style=\"border: 1px solid; border-collapse: collapse;\">")
3779
3780 directory = ""
3781 for fileData in availableInfo["files"]:
3782 if fileData.tag=="directory":
3783 directory = fileData.text
3784
3785 for fileData in availableInfo["files"]:
3786 if fileData.tag in ["pdf", "file"]:
3787 name = fileData.find("name").text
3788 link = fileData.find("link").text
3789
3790 f.write("<tr>")
3791 f.write("<td style=\"border: 1px solid; padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px;\">")
3792 f.write(name)
3793 f.write("</td>")
3794 f.write("<td style=\"border: 1px solid; padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px;\">")
3795 url = directory + link
3796 target = " target=\"_blank\"" if fileData.tag=="pdf" else ""
3797 f.write("<a href=\"%s\"%s>%s</a>" % (url, target, name))
3798 f.write("</td>")
3799 f.write("</tr>")
3800
3801 f.write("</table>")
3802 f.write("</div>\n")
3803
3804 f.write("<h2/>")
3805 f.write("<h3 style=\"text-align: center;\">%s</h3></br>" % (xstr("simbrief_result_prefile"),))
3806 f.write("<div style=\"height: 400px; width: fit-content; margin: 0 auto\">")
3807 f.write("<table style=\"border: 1px solid; border-collapse: collapse;\">")
3808 for prefileData in availableInfo["prefile"]:
3809 name = prefileData.find("name").text
3810 link = prefileData.find("link").text
3811 f.write("<tr>")
3812 f.write("<td style=\"border: 1px solid; padding-left: 10px; padding-right: 10px; padding-top: 5px; padding-bottom: 5px;\">")
3813 f.write("<a href=\"%s\" target=\"_blank\">%s</a>" % (link, name))
3814 f.write("</td>");
3815 f.write("</tr>")
3816
3817 f.write("</table>")
3818 f.write("</div>\n")
3819
3820 f.write("</body>")
3821 f.write("</html>")
3822
3823 except Exception as e:
3824 print("_getResults", e)
3825
3826 GObject.idle_add(self._resultsAvailable, flightInfo)
3827
3828 def _resultsAvailable(self, flightInfo):
3829 """Called from the result retrieval thread when the result is
3830 available."""
3831 print("_resultsAvailable")
3832 self._wizard.gui.endBusy()
3833
3834 if flightInfo:
3835 self._wizard.departureMETARChanged(flightInfo["orig_metar"],
3836 self)
3837 self._wizard.arrivalMETARChanged(flightInfo["dest_metar"], self)
3838 self._wizard.nextPage()
3839 else:
3840 self._finishWithError()
3841
3842 def _finishWithError(self):
3843 """Display an error dialog, and when it is accepted, cancel
3844 SimBrief briefing and jump to the fuel page."""
3845 dialog = Gtk.MessageDialog(parent = self._wizard.gui.mainWindow,
3846 type = Gtk.MessageType.ERROR,
3847 message_format =
3848 xstr("simbrief_result_error_other") + "\n"+
3849 xstr("simbrief_cancelled"))
3850
3851 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
3852 dialog.set_title(WINDOW_TITLE_BASE)
3853 secondary = xstr("flightsel_save_failed_sec")
3854 dialog.format_secondary_markup(secondary)
3855 dialog.run()
3856 dialog.hide()
3857
3858 self._wizard.usingSimBrief = False
3859 self._wizard.jumpPage("fuel", fromPageShift = 1)
3860
3861
3862
3863#-----------------------------------------------------------------------------
3864
3865class SimBriefingPage(Page):
3866 """Page to display the SimBrief HTML briefing."""
3867 class BrowserLifeSpanHandler(object):
3868 """The life-span handler of a browser."""
3869 def __init__(self, simBriefingPage):
3870 """Construct the life-span handler for the given page."""
3871 self._simBriefingPage = simBriefingPage
3872
3873 def OnBeforeClose(self, browser):
3874 """Called before closing the browser."""
3875 if browser is self._simBriefingPage._browser:
3876 self._simBriefingPage._invalidateBrowser()
3877
3878 def OnBeforeDownload(self, browser, downloadItem, suggestedName, callback):
3879 callback.Continue(suggestedName, True)
3880 return True
3881
3882 def __init__(self, wizard):
3883 """Construct the setup page."""
3884
3885 super(SimBriefingPage, self).__init__(wizard, "simbrief_result",
3886 xstr("simbrief_result_title"), "")
3887
3888 self._container = cef.getContainer()
3889 self._container.set_size_request(650, -1)
3890
3891 self.setMainWidget(self._container)
3892
3893 self._browser = None
3894
3895 self.addCancelFlightButton()
3896
3897 self.addPreviousButton(clicked = self._backClicked)
3898
3899 self._button = self.addNextButton(clicked = self._forwardClicked)
3900 self._button.set_label(xstr("briefing_button"))
3901 self._button.set_has_tooltip(False)
3902 self._button.set_use_stock(False)
3903
3904 def prepareShow(self):
3905 """Prepare the page for showing (again)."""
3906 if self._browser is None:
3907 self._startBrowser()
3908 else:
3909 self._browser.Reload()
3910
3911 def prepareHide(self):
3912 """Prepare the page for hiding."""
3913 if os.name!="nt":
3914 self._browser.CloseBrowser(False)
3915
3916 def grabDefault(self):
3917 """If the page has a default button, make it the default one."""
3918 super(SimBriefingPage, self).grabDefault()
3919
3920 if self._browser is None:
3921 self._startBrowser()
3922
3923 def _backClicked(self, button):
3924 """Called when the Back button has been pressed."""
3925 self.goBack()
3926
3927 def _forwardClicked(self, button):
3928 """Called when the Forward button has been pressed."""
3929 if not self._completed:
3930 self._button.set_label(xstr("button_next"))
3931 self._button.set_tooltip_text(xstr("button_next_tooltip"))
3932 self._wizard.usingSimBrief = True
3933 self.complete()
3934
3935 self._wizard.nextPage()
3936
3937 def _startBrowser(self):
3938 """Start the browser.
3939
3940 If a container is needed, create one."""
3941 if self._container is None:
3942 self._container = cef.getContainer()
3943 self.setMainWidget(self._container)
3944 else:
3945 self._container.show()
3946
3947 url = "file://" + SimBriefSetupPage.getHTMLFilePath()
3948 self._browser = cef.startInContainer(self._container, url)
3949
3950 lifeSpanHandler = SimBriefingPage.BrowserLifeSpanHandler(self)
3951 self._browser.SetClientHandler(lifeSpanHandler)
3952
3953 def _invalidateBrowser(self):
3954 """Invalidate the browser (and associated stuff)."""
3955 self._container.hide()
3956 self._browser = None
3957
3958#-----------------------------------------------------------------------------
3959
3960class FuelTank(Gtk.VBox):
3961 """Widget for the fuel tank."""
3962 def __init__(self, fuelTank, name, capacity, currentWeight):
3963 """Construct the widget for the tank with the given name."""
3964 super(FuelTank, self).__init__()
3965
3966 self._enabled = True
3967 self.fuelTank = fuelTank
3968 self.capacity = capacity
3969 self.currentWeight = currentWeight
3970 self.expectedWeight = currentWeight
3971
3972 self._label = label = Gtk.Label("<b>" + name + "</b>")
3973 label.set_use_markup(True)
3974 label.set_use_underline(True)
3975 label.set_justify(Gtk.Justification.CENTER)
3976 label.set_alignment(0.5, 1.0)
3977
3978 self._tankFigure = Gtk.EventBox()
3979 self._tankFigure.set_size_request(38, 200)
3980 self._tankFigure.set_visible_window(False)
3981 self._tankFigure.set_tooltip_markup(xstr("fuel_tank_tooltip"))
3982
3983 self._tankFigure.connect("draw", self._drawTankFigure)
3984 self._tankFigure.connect("button_press_event", self._buttonPressed)
3985 self._tankFigure.connect("motion_notify_event", self._motionNotify)
3986
3987 self._tankFigure.add_events(Gdk.EventMask.SCROLL_MASK)
3988 self._tankFigure.connect("scroll-event", self._scrolled)
3989
3990 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
3991 xscale = 0.0, yscale = 1.0)
3992 alignment.add(self._tankFigure)
3993
3994 self.pack_start(alignment, True, True, 4)
3995
3996 self._expectedButton = Gtk.SpinButton()
3997 self._expectedButton.set_numeric(True)
3998 self._expectedButton.set_range(0, self.capacity)
3999 self._expectedButton.set_increments(10, 100)
4000 self._expectedButton.set_value(currentWeight)
4001 self._expectedButton.set_alignment(1.0)
4002 self._expectedButton.set_width_chars(5)
4003 self._expectedButton.connect("value-changed", self._expectedChanged)
4004
4005 label.set_mnemonic_widget(self._expectedButton)
4006
4007 @property
4008 def label(self):
4009 """Get the label with the caption."""
4010 return self._label
4011
4012 @property
4013 def expectedButton(self):
4014 """Get the button containing the expected value."""
4015 return self._expectedButton
4016
4017 def setCurrent(self, currentWeight):
4018 """Set the current weight."""
4019 self.currentWeight = currentWeight
4020 self._redraw()
4021
4022 def isCorrect(self):
4023 """Determine if the contents of the fuel tank are as expected"""
4024 return abs(self.expectedWeight - self.currentWeight)<=1
4025
4026 def disable(self):
4027 """Disable the fuel tank."""
4028 self._expectedButton.set_sensitive(False)
4029 self._enabled = False
4030
4031 def _redraw(self):
4032 """Redraw the tank figure."""
4033 self._tankFigure.queue_draw()
4034
4035 def _drawTankFigure(self, tankFigure, eventOrContext):
4036 """Draw the tank figure."""
4037 triangleSize = 5
4038
4039 context = eventOrContext
4040 (xOffset, yOffset) = (0, 0)
4041
4042 width = tankFigure.get_allocated_width()
4043 height = tankFigure.get_allocated_height()
4044
4045 rectangleX0 = triangleSize
4046 rectangleY0 = triangleSize
4047 rectangleX1 = width - 1 - triangleSize
4048 rectangleY1 = height - 1 - triangleSize
4049 rectangleLineWidth = 2.0
4050
4051 context.set_source_rgb(0.0, 0.0, 0.0)
4052 context.set_line_width(rectangleLineWidth)
4053 context.rectangle(xOffset + rectangleX0 + rectangleLineWidth/2,
4054 yOffset + rectangleY0 + rectangleLineWidth/2,
4055 rectangleX1 - rectangleX0 - rectangleLineWidth,
4056 rectangleY1 - rectangleY0 - rectangleLineWidth)
4057 context.stroke()
4058
4059 rectangleInnerLeft = rectangleX0 + rectangleLineWidth
4060 rectangleInnerRight = rectangleX1 - rectangleLineWidth
4061 self._rectangleInnerTop = rectangleInnerTop = rectangleY0 + rectangleLineWidth
4062 self._rectangleInnerBottom = rectangleInnerBottom = rectangleY1 - rectangleLineWidth
4063
4064 rectangleInnerWidth = rectangleInnerRight - rectangleInnerLeft
4065 rectangleInnerHeight = rectangleInnerBottom - rectangleInnerTop
4066
4067 context.set_source_rgb(1.0, 0.9, 0.6)
4068 currentHeight = self.currentWeight * rectangleInnerHeight / self.capacity
4069 currentX = rectangleInnerTop + rectangleInnerHeight - currentHeight
4070 context.rectangle(xOffset + rectangleInnerLeft,
4071 yOffset + rectangleInnerTop +
4072 rectangleInnerHeight - currentHeight,
4073 rectangleInnerWidth, currentHeight)
4074 context.fill()
4075
4076 expectedHeight = self.expectedWeight * rectangleInnerHeight / self.capacity
4077 expectedY = rectangleInnerTop + rectangleInnerHeight - expectedHeight
4078
4079 context.set_line_width(1.5)
4080 context.set_source_rgb(0.0, 0.85, 0.85)
4081 context.move_to(xOffset + rectangleX0, yOffset + expectedY)
4082 context.line_to(xOffset + rectangleX1, yOffset + expectedY)
4083 context.stroke()
4084
4085 context.set_line_width(0.0)
4086 context.move_to(xOffset + 0, yOffset + expectedY - triangleSize)
4087 context.line_to(xOffset + 0, yOffset + expectedY + triangleSize)
4088 context.line_to(xOffset + rectangleX0 + 1, yOffset + expectedY)
4089 context.line_to(xOffset + 0, yOffset + expectedY - triangleSize)
4090 context.fill()
4091
4092 context.set_line_width(0.0)
4093 context.move_to(xOffset + width, yOffset + expectedY - triangleSize)
4094 context.line_to(xOffset + width, yOffset + expectedY + triangleSize)
4095 context.line_to(xOffset + rectangleX1 - 1, yOffset + expectedY)
4096 context.line_to(xOffset + width, yOffset + expectedY - triangleSize)
4097 context.fill()
4098
4099 return True
4100
4101 def _setExpectedFromY(self, y):
4102 """Set the expected weight from the given Y-coordinate."""
4103 level = (self._rectangleInnerBottom - y) / \
4104 (self._rectangleInnerBottom - self._rectangleInnerTop)
4105 level = min(1.0, max(0.0, level))
4106 self._expectedButton.set_value(level * self.capacity)
4107
4108 def _buttonPressed(self, tankFigure, event):
4109 """Called when a button is pressed in the figure.
4110
4111 The expected level will be set there."""
4112 if self._enabled and event.button==1:
4113 self._setExpectedFromY(event.y)
4114
4115 def _motionNotify(self, tankFigure, event):
4116 """Called when the mouse pointer moves within the area of a tank figure."""
4117 if self._enabled and event.state==Gdk.ModifierType.BUTTON1_MASK:
4118 self._setExpectedFromY(event.y)
4119
4120 def _scrolled(self, tankFigure, event):
4121 """Called when a scroll event is received."""
4122 if self._enabled:
4123 increment = 1 if event.state==Gdk.ModifierType.CONTROL_MASK \
4124 else 100 if event.state==Gdk.ModifierType.SHIFT_MASK \
4125 else 10 if event.state==0 else 0
4126 if increment!=0:
4127 if event.direction==Gdk.ScrollDirection.DOWN:
4128 increment *= -1
4129 self._expectedButton.spin(Gtk.SpinType.USER_DEFINED, increment)
4130
4131 def _expectedChanged(self, spinButton):
4132 """Called when the expected value has changed."""
4133 self.expectedWeight = spinButton.get_value_as_int()
4134 self._redraw()
4135
4136#-----------------------------------------------------------------------------
4137
4138class FuelPage(Page):
4139 """The page containing the fuel tank filling."""
4140 _pumpStep = 0.02
4141
4142 def __init__(self, wizard):
4143 """Construct the page."""
4144 super(FuelPage, self).__init__(wizard, "fuel",
4145 xstr("fuel_title"),
4146 xstr("fuel_help_pre") +
4147 xstr("fuel_help_post"),
4148 completedHelp = xstr("fuel_chelp"))
4149
4150 self._fuelTanks = []
4151 self._fuelTable = None
4152 self._fuelAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4153 xscale = 0.0, yscale = 1.0)
4154 self.setMainWidget(self._fuelAlignment)
4155
4156 tankData = [(tank, 2500, 3900) for tank in acft.mostFuelTanks]
4157 self._setupTanks(tankData)
4158
4159 self.addCancelFlightButton()
4160
4161 self._backButton = self.addPreviousButton(clicked = self._backClicked)
4162 self._button = self.addNextButton(clicked = self._forwardClicked)
4163
4164 self._pumpIndex = 0
4165
4166 def activate(self):
4167 """Activate the page."""
4168 self._setupTanks(self._wizard._fuelData)
4169
4170 aircraft = self._wizard.gui.flight.aircraft
4171 minLandingFuel = aircraft.minLandingFuel
4172 recommendedLandingFuel = aircraft.recommendedLandingFuel
4173
4174 middleHelp = "" if minLandingFuel is None else \
4175 (xstr("fuel_help_min") % (minLandingFuel,)) \
4176 if recommendedLandingFuel is None else \
4177 (xstr("fuel_help_min_rec") % (minLandingFuel,
4178 recommendedLandingFuel))
4179 self.setHelp(xstr("fuel_help_pre") + middleHelp + xstr("fuel_help_post"))
4180
4181 def finalize(self):
4182 """Finalize the page."""
4183 for fuelTank in self._fuelTanks:
4184 fuelTank.disable()
4185
4186 def _backClicked(self, button):
4187 """Called when the Back button is pressed."""
4188 self.goBack()
4189
4190 def _forwardClicked(self, button):
4191 """Called when the forward button is clicked."""
4192 if not self._completed:
4193 self._pumpIndex = 0
4194 self._wizard.gui.beginBusy(xstr("fuel_pump_busy"))
4195 self._pump()
4196 elif self._wizard.usingSimBrief:
4197 self._wizard.jumpPage("takeoff")
4198 else:
4199 self._wizard.jumpPage("briefing1")
4200
4201 def _setupTanks(self, tankData):
4202 """Setup the tanks for the given data."""
4203 numTanks = len(tankData)
4204 if self._fuelTable is not None:
4205 self._fuelAlignment.remove(self._fuelTable)
4206
4207 self._fuelTanks = []
4208 self._fuelTable = Gtk.Grid()
4209 self._fuelTable.set_column_homogeneous(True)
4210 self._fuelTable.set_row_spacing(4)
4211 self._fuelTable.set_column_spacing(16 if numTanks<5 else 0)
4212 index = 0
4213 for (tank, current, capacity) in tankData:
4214 fuelTank = FuelTank(tank,
4215 xstr("fuel_tank_" +
4216 const.fuelTank2string(tank)),
4217 capacity, current)
4218 self._fuelTanks.append(fuelTank)
4219
4220 alignment = Gtk.Alignment(xalign = 0.5, yalign = 1.0,
4221 xscale = 1.0, yscale = 0.0)
4222 alignment.add(fuelTank.label)
4223 self._fuelTable.attach(alignment,
4224 index*(1 if numTanks<5 else 2), 0,
4225 1 if numTanks<5 else 3, 1)
4226
4227 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4228 xscale = 0.0, yscale = 1.0)
4229 alignment.add(fuelTank)
4230 self._fuelTable.attach(alignment,
4231 index*(1 if numTanks<5 else 2) +
4232 (0 if numTanks<5 else 1), 1, 1, 1)
4233
4234
4235 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4236 xscale = 1.0, yscale = 0.0)
4237 alignment.add(fuelTank.expectedButton)
4238
4239 self._fuelTable.attach(alignment,
4240 index* (1 if numTanks<5 else 2),
4241 2 if (index%2)==0 or numTanks<5 else 3,
4242 1 if numTanks<5 else 3, 1)
4243
4244 index += 1
4245
4246 self._fuelAlignment.add(self._fuelTable)
4247 self.show_all()
4248
4249 def _pump(self):
4250 """Perform one step of pumping.
4251
4252 It is checked, if the current tank's contents are of the right
4253 quantity. If not, it is filled one step further to the desired
4254 contents. Otherwise the next tank is started. If all tanks are are
4255 filled, the next page is selected."""
4256 numTanks = len(self._fuelTanks)
4257
4258 fuelTank = None
4259 while self._pumpIndex < numTanks:
4260 fuelTank = self._fuelTanks[self._pumpIndex]
4261 if fuelTank.isCorrect():
4262 self._pumpIndex += 1
4263 fuelTank = None
4264 else:
4265 break
4266
4267 if fuelTank is None:
4268 self._wizard.gui.endBusy()
4269 if self._wizard.usingSimBrief:
4270 self._wizard.gui.startMonitoring()
4271 self._wizard.jumpPage("takeoff")
4272 else:
4273 bookedFlight = self._wizard._bookedFlight
4274 self._wizard.gui.beginBusy(xstr("route_down_notams"))
4275 self._wizard.gui.webHandler.getNOTAMs(self._notamsCallback,
4276 bookedFlight.departureICAO,
4277 bookedFlight.arrivalICAO)
4278 startSound(const.SOUND_NOTAM)
4279 else:
4280 currentLevel = fuelTank.currentWeight / fuelTank.capacity
4281 expectedLevel = fuelTank.expectedWeight / fuelTank.capacity
4282 if currentLevel<expectedLevel:
4283 currentLevel += FuelPage._pumpStep
4284 if currentLevel>expectedLevel: currentLevel = expectedLevel
4285 else:
4286 currentLevel -= FuelPage._pumpStep
4287 if currentLevel<expectedLevel: currentLevel = expectedLevel
4288 fuelTank.setCurrent(currentLevel * fuelTank.capacity)
4289 self._wizard.gui.simulator.setFuelLevel([(fuelTank.fuelTank,
4290 currentLevel)])
4291 GObject.timeout_add(50, self._pump)
4292
4293 def _notamsCallback(self, returned, result):
4294 """Callback for the NOTAMs."""
4295 GObject.idle_add(self._handleNOTAMs, returned, result)
4296
4297 def _handleNOTAMs(self, returned, result):
4298 """Handle the NOTAMs."""
4299 if returned:
4300 self._wizard._departureNOTAMs = result.departureNOTAMs
4301 self._wizard._arrivalNOTAMs = result.arrivalNOTAMs
4302 else:
4303 self._wizard._departureNOTAMs = None
4304 self._wizard._arrivalNOTAMs = None
4305
4306 bookedFlight = self._wizard._bookedFlight
4307 self._wizard.gui.beginBusy(xstr("route_down_metars"))
4308 self._wizard.gui.webHandler.getMETARs(self._metarsCallback,
4309 [bookedFlight.departureICAO,
4310 bookedFlight.arrivalICAO])
4311
4312 def _metarsCallback(self, returned, result):
4313 """Callback for the METARs."""
4314 GObject.idle_add(self._handleMETARs, returned, result)
4315
4316 def _handleMETARs(self, returned, result):
4317 """Handle the METARs."""
4318 self._wizard._departureMETAR = None
4319 self._wizard._arrivalMETAR = None
4320 bookedFlight = self._wizard._bookedFlight
4321 if returned:
4322 if bookedFlight.departureICAO in result.metars:
4323 self._wizard._departureMETAR = result.metars[bookedFlight.departureICAO]
4324 if bookedFlight.arrivalICAO in result.metars:
4325 self._wizard._arrivalMETAR = result.metars[bookedFlight.arrivalICAO]
4326
4327 self._wizard.gui.endBusy()
4328 self._backButton.set_sensitive(True)
4329 self._button.set_sensitive(True)
4330 self._wizard.nextPage()
4331
4332#-----------------------------------------------------------------------------
4333
4334class BriefingPage(Page):
4335 """Page for the briefing."""
4336 def __init__(self, wizard, departure):
4337 """Construct the briefing page."""
4338 self._departure = departure
4339
4340 number = 1 if departure else 2
4341
4342 title = xstr("briefing_title") % (number,
4343 xstr("briefing_departure")
4344 if departure
4345 else xstr("briefing_arrival"))
4346 super(BriefingPage, self).__init__(wizard,
4347 "briefing%d" % (number,),
4348 title, xstr("briefing_help"),
4349 completedHelp = xstr("briefing_chelp"))
4350
4351 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4352 xscale = 1.0, yscale = 1.0)
4353
4354 mainBox = Gtk.VBox()
4355 alignment.add(mainBox)
4356 self.setMainWidget(alignment)
4357
4358 self._notamsFrame = Gtk.Frame()
4359 self._notamsFrame.set_label(xstr("briefing_notams_init"))
4360 scrolledWindow = Gtk.ScrolledWindow()
4361 scrolledWindow.set_size_request(-1, 128)
4362 # FIXME: these constants should be in common
4363 scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
4364 Gtk.PolicyType.AUTOMATIC)
4365 self._notams = Gtk.TextView()
4366 self._notams.set_editable(False)
4367 self._notams.set_accepts_tab(False)
4368 self._notams.set_wrap_mode(Gtk.WrapMode.WORD)
4369 scrolledWindow.add(self._notams)
4370 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4371 xscale = 1.0, yscale = 1.0)
4372 alignment.set_padding(padding_top = 4, padding_bottom = 0,
4373 padding_left = 0, padding_right = 0)
4374 alignment.add(scrolledWindow)
4375 self._notamsFrame.add(alignment)
4376 mainBox.pack_start(self._notamsFrame, True, True, 4)
4377
4378 self._metarFrame = Gtk.Frame()
4379 self._metarFrame.set_label(xstr("briefing_metar_init"))
4380 scrolledWindow = Gtk.ScrolledWindow()
4381 scrolledWindow.set_size_request(-1, 32)
4382 scrolledWindow.set_policy(Gtk.PolicyType.AUTOMATIC,
4383 Gtk.PolicyType.AUTOMATIC)
4384
4385 self._updatingMETAR = False
4386
4387 self._metar = Gtk.TextView()
4388 self._metar.set_accepts_tab(False)
4389 self._metar.set_wrap_mode(Gtk.WrapMode.WORD)
4390 self._metar.get_buffer().connect("changed", self._metarChanged)
4391 self._metar.get_buffer().connect_after("insert-text", self._metarInserted)
4392 scrolledWindow.add(self._metar)
4393 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
4394 xscale = 1.0, yscale = 1.0)
4395 alignment.set_padding(padding_top = 4, padding_bottom = 0,
4396 padding_left = 0, padding_right = 0)
4397 alignment.add(scrolledWindow)
4398 self._metarFrame.add(alignment)
4399 mainBox.pack_start(self._metarFrame, True, True, 4)
4400 self.metarEdited = False
4401
4402 self.addCancelFlightButton()
4403
4404 self.addPreviousButton(clicked = self._backClicked)
4405 self._button = self.addNextButton(clicked = self._forwardClicked)
4406
4407 @property
4408 def metar(self):
4409 """Get the METAR on the page."""
4410 buffer = self._metar.get_buffer()
4411 return buffer.get_text(buffer.get_start_iter(),
4412 buffer.get_end_iter(), True)
4413
4414 def setMETAR(self, metar):
4415 """Set the METAR."""
4416 self._metar.get_buffer().set_text(metar)
4417 self.metarEdited = False
4418
4419 def changeMETAR(self, metar):
4420 """Change the METAR as a result of an edit on one of the other
4421 pages."""
4422 self._updatingMETAR = True
4423 self._metar.get_buffer().set_text(metar)
4424 self._updatingMETAR = False
4425
4426 self._updateButton()
4427 self.metarEdited = True
4428
4429 def activate(self):
4430 """Activate the page."""
4431 if not self._departure:
4432 self._button.set_label(xstr("briefing_button"))
4433 self._button.set_has_tooltip(False)
4434 self._button.set_use_stock(False)
4435
4436 bookedFlight = self._wizard._bookedFlight
4437
4438 icao = bookedFlight.departureICAO if self._departure \
4439 else bookedFlight.arrivalICAO
4440 notams = self._wizard._departureNOTAMs if self._departure \
4441 else self._wizard._arrivalNOTAMs
4442 metar = self._wizard._departureMETAR if self._departure \
4443 else self._wizard._arrivalMETAR
4444
4445 self._notamsFrame.set_label(xstr("briefing_notams_template") % (icao,))
4446 buffer = self._notams.get_buffer()
4447 if notams is None:
4448 buffer.set_text(xstr("briefing_notams_failed"))
4449 elif not notams:
4450 buffer.set_text(xstr("briefing_notams_missing"))
4451 else:
4452 s = ""
4453 for notam in notams:
4454 s += str(notam)
4455 s += "-------------------- * --------------------\n"
4456 buffer.set_text(s)
4457
4458 self._metarFrame.set_label(xstr("briefing_metar_template") % (icao,))
4459 buffer = self._metar.get_buffer()
4460 self._updatingMETAR = True
4461 if metar is None:
4462 buffer.set_text("")
4463 self.setHelp(xstr("briefing_help_nometar"))
4464 else:
4465 buffer.set_text(metar)
4466 self._updatingMETAR = False
4467 self._updateButton()
4468
4469 label = self._metarFrame.get_label_widget()
4470 label.set_use_underline(True)
4471 label.set_mnemonic_widget(self._metar)
4472
4473 self.metarEdited = False
4474
4475 def _backClicked(self, button):
4476 """Called when the Back button is pressed."""
4477 self.goBack()
4478
4479 def _forwardClicked(self, button):
4480 """Called when the forward button is clicked."""
4481 if not self._departure:
4482 if not self._completed:
4483 self._wizard.gui.startMonitoring()
4484 self._button.set_label(xstr("button_next"))
4485 self._button.set_tooltip_text(xstr("button_next_tooltip"))
4486 self.complete()
4487
4488 self._wizard.nextPage()
4489
4490 def _metarChanged(self, buffer):
4491 """Called when the METAR has changed."""
4492 print("BriefingPage.metarChanged", self._updatingMETAR)
4493 if not self._updatingMETAR:
4494 self.metarEdited = True
4495 self._updateButton()
4496 metar = buffer.get_text(buffer.get_start_iter(),
4497 buffer.get_end_iter(), True)
4498 self._wizard.metarChanged(metar, self)
4499
4500 def _metarInserted(self, textBuffer, iter, text, length):
4501 """Called when new characters are inserted into the METAR.
4502
4503 It uppercases all characters."""
4504 print("BriefingPage.metarInserted", self._updatingMETAR)
4505 if not self._updatingMETAR:
4506 self._updatingMETAR = True
4507
4508 iter1 = iter.copy()
4509 iter1.backward_chars(length)
4510 textBuffer.delete(iter, iter1)
4511
4512 textBuffer.insert(iter, text.upper())
4513
4514 self._updatingMETAR = False
4515
4516 def _updateButton(self):
4517 """Update the sensitivity of the Next button based on the contents of
4518 the METAR field."""
4519 buffer = self._metar.get_buffer()
4520 self._button.set_sensitive(buffer.get_text(buffer.get_start_iter(),
4521 buffer.get_end_iter(),
4522 True)!="")
4523
4524
4525#-----------------------------------------------------------------------------
4526
4527class TakeoffPage(Page):
4528 """Page for entering the takeoff data."""
4529 def __init__(self, wizard):
4530 """Construct the takeoff page."""
4531 super(TakeoffPage, self).__init__(wizard, "takeoff",
4532 xstr("takeoff_title"),
4533 xstr("takeoff_help"),
4534 completedHelp = xstr("takeoff_chelp"))
4535
4536 self._forwardAllowed = False
4537
4538 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
4539 xscale = 0.0, yscale = 0.0)
4540
4541 table = Gtk.Table(9, 24)
4542 table.set_row_spacings(4)
4543 table.set_col_spacings(16)
4544 table.set_homogeneous(False)
4545 alignment.add(table)
4546 self.setMainWidget(alignment)
4547
4548 row = 0
4549
4550 label = Gtk.Label(xstr("takeoff_metar"))
4551 label.set_use_underline(True)
4552 label.set_alignment(0.0, 0.5)
4553 table.attach(label, 0, 1, row, row+1)
4554
4555 self._metar = Gtk.Entry()
4556 self._metar.set_width_chars(40)
4557 self._metar.set_tooltip_text(xstr("takeoff_metar_tooltip"))
4558 self._metar.connect("changed", self._metarChanged)
4559 self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
4560 table.attach(self._metar, 1, 24, row, row+1)
4561 label.set_mnemonic_widget(self._metar)
4562
4563 self._updatingMETAR = False
4564
4565 row += 1
4566
4567 label = Gtk.Label(xstr("takeoff_runway"))
4568 label.set_use_underline(True)
4569 label.set_alignment(0.0, 0.5)
4570 table.attach(label, 0, 1, row, row+1)
4571
4572 self._runway = Gtk.Entry()
4573 self._runway.set_width_chars(10)
4574 self._runway.set_tooltip_text(xstr("takeoff_runway_tooltip"))
4575 self._runway.connect("changed", self._upperChanged)
4576 table.attach(self._runway, 1, 3, row, row+1)
4577 label.set_mnemonic_widget(self._runway)
4578
4579 row += 1
4580
4581 label = Gtk.Label(xstr("takeoff_sid"))
4582 label.set_use_underline(True)
4583 label.set_alignment(0.0, 0.5)
4584 table.attach(label, 0, 1, row, row+1)
4585
4586 self._sid = Gtk.ComboBox.new_with_model_and_entry(comboModel)
4587
4588 self._sid.set_entry_text_column(0)
4589 self._sid.get_child().set_width_chars(10)
4590 self._sid.set_tooltip_text(xstr("takeoff_sid_tooltip"))
4591 self._sid.connect("changed", self._upperChangedComboBox)
4592 table.attach(self._sid, 1, 3, row, row+1)
4593 label.set_mnemonic_widget(self._sid)
4594
4595 row += 1
4596
4597 label = Gtk.Label(xstr("takeoff_v1"))
4598 label.set_use_markup(True)
4599 label.set_use_underline(True)
4600 label.set_alignment(0.0, 0.5)
4601 table.attach(label, 0, 1, row, row+1)
4602
4603 self._v1 = IntegerEntry()
4604 self._v1.set_width_chars(4)
4605 self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip_knots"))
4606 self._v1.connect("integer-changed", self._valueChanged)
4607 table.attach(self._v1, 2, 3, row, row+1)
4608 label.set_mnemonic_widget(self._v1)
4609
4610 self._v1Unit = Gtk.Label(xstr("label_knots"))
4611 self._v1Unit.set_alignment(0.0, 0.5)
4612 table.attach(self._v1Unit, 3, 4, row, row+1)
4613
4614 row += 1
4615
4616 label = Gtk.Label(xstr("takeoff_vr"))
4617 label.set_use_markup(True)
4618 label.set_use_underline(True)
4619 label.set_alignment(0.0, 0.5)
4620 table.attach(label, 0, 1, row, row+1)
4621
4622 self._vr = IntegerEntry()
4623 self._vr.set_width_chars(4)
4624 self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip_knots"))
4625 self._vr.connect("integer-changed", self._valueChanged)
4626 table.attach(self._vr, 2, 3, row, row+1)
4627 label.set_mnemonic_widget(self._vr)
4628
4629 self._vrUnit = Gtk.Label(xstr("label_knots"))
4630 self._vrUnit.set_alignment(0.0, 0.5)
4631 table.attach(self._vrUnit, 3, 4, row, row+1)
4632
4633 row += 1
4634
4635 label = Gtk.Label(xstr("takeoff_v2"))
4636 label.set_use_markup(True)
4637 label.set_use_underline(True)
4638 label.set_alignment(0.0, 0.5)
4639 table.attach(label, 0, 1, row, row+1)
4640
4641 self._v2 = IntegerEntry()
4642 self._v2.set_width_chars(4)
4643 self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip_knots"))
4644 self._v2.connect("integer-changed", self._valueChanged)
4645 table.attach(self._v2, 2, 3, row, row+1)
4646 label.set_mnemonic_widget(self._v2)
4647
4648 self._v2Unit = Gtk.Label(xstr("label_knots"))
4649 self._v2Unit.set_alignment(0.0, 0.5)
4650 table.attach(self._v2Unit, 3, 4, row, row+1)
4651
4652 row += 1
4653
4654 self._derateType = acft.DERATE_NONE
4655
4656 self._derateLabel = Gtk.Label()
4657 self._derateLabel.set_use_underline(True)
4658 self._derateLabel.set_markup(xstr("takeoff_derate_tupolev"))
4659 self._derateLabel.set_alignment(0.0, 0.5)
4660 table.attach(self._derateLabel, 0, 1, row, row+1)
4661
4662 self._derate = Gtk.Alignment()
4663 table.attach(self._derate, 2, 4, row, row+1)
4664 self._derateWidget = None
4665 self._derateEntry = None
4666 self._derateUnit = None
4667 self._derateButtons = None
4668
4669 row += 1
4670
4671 self._antiIceOn = Gtk.CheckButton(xstr("takeoff_antiice"))
4672 self._antiIceOn.set_use_underline(True)
4673 self._antiIceOn.set_tooltip_text(xstr("takeoff_antiice_tooltip"))
4674 table.attach(self._antiIceOn, 2, 4, row, row+1)
4675
4676 row += 1
4677
4678 self._rto = Gtk.CheckButton(xstr("takeoff_rto"))
4679 self._rto.set_use_underline(True)
4680 self._rto.set_tooltip_text(xstr("takeoff_rto_tooltip"))
4681 self._rto.connect("toggled", self._rtoToggled)
4682 table.attach(self._rto, 2, 4, row, row+1, ypadding = 8)
4683
4684 self.addCancelFlightButton()
4685
4686 self.addPreviousButton(clicked = self._backClicked)
4687
4688 self._button = self.addNextButton(clicked = self._forwardClicked)
4689
4690 self._active = False
4691
4692 @property
4693 def runway(self):
4694 """Get the runway."""
4695 return self._runway.get_text()
4696
4697 @property
4698 def sid(self):
4699 """Get the SID."""
4700 text = self._sid.get_child().get_text()
4701 return text if self._sid.get_active()!=0 and text and text!="N/A" \
4702 else None
4703
4704 @property
4705 def v1(self):
4706 """Get the v1 speed."""
4707 return self._v1.get_int()
4708
4709 @property
4710 def vr(self):
4711 """Get the vr speed."""
4712 return self._vr.get_int()
4713
4714 @property
4715 def v2(self):
4716 """Get the v2 speed."""
4717 return self._v2.get_int()
4718
4719 @property
4720 def derate(self):
4721 """Get the derate value, if any."""
4722 if self._derateWidget is None:
4723 return None
4724 if self._derateType==acft.DERATE_BOEING:
4725 derate = self._derateEntry.get_text()
4726 return derate if derate else None
4727 elif self._derateType==acft.DERATE_EPR:
4728 derate = self._derateWidget.get_text()
4729 return derate if derate else None
4730 elif self._derateType==acft.DERATE_TUPOLEV:
4731 return acft.DERATE_TUPOLEV_NOMINAL \
4732 if self._derateButtons[0].get_active() \
4733 else acft.DERATE_TUPOLEV_TAKEOFF
4734 elif self._derateType==acft.DERATE_B462:
4735 return self._derateWidget.get_active()
4736 else:
4737 return None
4738
4739 @property
4740 def antiIceOn(self):
4741 """Get whether the anti-ice system has been turned on."""
4742 return self._antiIceOn.get_active()
4743
4744 @antiIceOn.setter
4745 def antiIceOn(self, value):
4746 """Set the anti-ice indicator."""
4747 self._antiIceOn.set_active(value)
4748
4749 @property
4750 def rtoIndicated(self):
4751 """Get whether the pilot has indicated if there was an RTO."""
4752 return self._rto.get_active()
4753
4754 def activate(self):
4755 """Activate the page."""
4756 print("TakeoffPage.activate")
4757
4758 self._updatingMETAR = True
4759 self._metar.get_buffer().set_text(self._wizard.departureMETAR, -1)
4760 self._updatingMETAR = False
4761
4762 if self._wizard.takeoffRunway is None:
4763 self._runway.set_text("")
4764 else:
4765 self._runway.set_text(self._wizard.takeoffRunway)
4766 self._runway.set_sensitive(True)
4767 self._sid.set_active(0)
4768 self._sid.set_sensitive(True)
4769 self._v1.set_int(None)
4770 self._v1.set_sensitive(True)
4771 self._vr.set_int(None)
4772 self._vr.set_sensitive(True)
4773 self._v2.set_int(None)
4774 self._v2.set_sensitive(True)
4775
4776 i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
4777 speedUnit = xstr("label" + i18nSpeedUnit)
4778 self._v1Unit.set_text(speedUnit)
4779 self._vrUnit.set_text(speedUnit)
4780 self._v2Unit.set_text(speedUnit)
4781
4782 self._v1.set_tooltip_markup(xstr("takeoff_v1_tooltip" + i18nSpeedUnit))
4783 self._vr.set_tooltip_markup(xstr("takeoff_vr_tooltip" + i18nSpeedUnit))
4784 self._v2.set_tooltip_markup(xstr("takeoff_v2_tooltip" + i18nSpeedUnit))
4785
4786 self._derateType = self._wizard.gui.flight.aircraft.derateType
4787
4788 self._setupDerateWidget()
4789
4790 self._rto.set_active(False)
4791 self._rto.set_sensitive(False)
4792
4793 self._button.set_sensitive(False)
4794 self._forwardAllowed = False
4795
4796 self._active = True
4797
4798 def allowForward(self):
4799 """Allow going to the next page."""
4800 print("TakeoffPage.allowForward")
4801 self._forwardAllowed = True
4802 self._updateForwardButton()
4803
4804 def reset(self):
4805 """Reset the page if the wizard is reset."""
4806 print("TakeoffPage.reset")
4807
4808 super(TakeoffPage, self).reset()
4809 self._v1.reset()
4810 self._vr.reset()
4811 self._v2.reset()
4812 self._hasDerate = False
4813 self._antiIceOn.set_active(False)
4814 self._active = False
4815
4816 def setRTOEnabled(self, enabled):
4817 """Set the RTO checkbox enabled or disabled."""
4818 if not enabled:
4819 self._rto.set_active(False)
4820 self._rto.set_sensitive(enabled)
4821
4822 def changeMETAR(self, metar):
4823 """Change the METAR as a result of an edit on one of the other
4824 pages."""
4825 if self._active:
4826 print("TakeoffPage.changeMETAR")
4827 self._updatingMETAR = True
4828 self._metar.get_buffer().set_text(metar, -1)
4829 self._updatingMETAR = False
4830
4831 self._updateForwardButton()
4832
4833 def _updateForwardButton(self):
4834 """Update the sensitivity of the forward button based on some conditions."""
4835 sensitive = self._forwardAllowed and \
4836 self._metar.get_text()!="" and \
4837 self._runway.get_text()!="" and \
4838 self.sid is not None and \
4839 self.v1 is not None and \
4840 self.vr is not None and \
4841 self.v2 is not None and \
4842 self.v1 <= self.vr and \
4843 self.vr <= self.v2 and \
4844 (self._derateType==acft.DERATE_NONE or
4845 self.derate is not None)
4846
4847 print("TakeoffPage._updateForwardButton: forwardAllowed:", self._forwardAllowed, ", sensitive:", sensitive)
4848 if self._forwardAllowed:
4849 print(" METAR: ", self._metar.get_text())
4850 print(" runway: ", self._runway.get_text())
4851 print(" SID:", self.sid)
4852 print(" V1:", self.v1)
4853 print(" VR:", self.vr)
4854 print(" V2:", self.v2)
4855 print(" derateType:", self._derateType)
4856 print(" derate:", self.derate)
4857
4858 self._button.set_sensitive(sensitive)
4859
4860 def _valueChanged(self, widget, arg = None):
4861 """Called when the value of some widget has changed."""
4862 print("TakeoffPage._valueChanged")
4863
4864 self._updateForwardButton()
4865
4866 def _upperChanged(self, entry, arg = None):
4867 """Called when the value of some entry widget has changed and the value
4868 should be converted to uppercase."""
4869 print("TakeoffPage._upperChanged")
4870 entry.set_text(entry.get_text().upper())
4871 self._valueChanged(entry, arg)
4872
4873 def _upperChangedComboBox(self, comboBox):
4874 """Called for combo box widgets that must be converted to uppercase."""
4875 entry = comboBox.get_child()
4876 if comboBox.get_active()==-1:
4877 entry.set_text(entry.get_text().upper())
4878 self._valueChanged(entry)
4879
4880 def _derateChanged(self, entry):
4881 """Called when the value of the derate is changed."""
4882 print("TakeoffPage._derateChanged")
4883 self._updateForwardButton()
4884
4885 def _rtoToggled(self, button):
4886 """Called when the RTO check button is toggled."""
4887 self._wizard.rtoToggled(button.get_active())
4888
4889 def _backClicked(self, button):
4890 """Called when the Back button is pressed."""
4891 self.goBack()
4892
4893 def _forwardClicked(self, button):
4894 """Called when the forward button is clicked."""
4895 aircraft = self._wizard.gui.flight.aircraft
4896 aircraft.updateV1R2()
4897 if self.derate is not None:
4898 aircraft.updateDerate()
4899 aircraft.updateTakeoffAntiIce()
4900 self._wizard.nextPage()
4901
4902 def _setupDerateWidget(self):
4903 """Setup the derate widget."""
4904 if self._derateWidget is not None:
4905 self._derate.remove(self._derateWidget)
4906
4907 if self._derateType==acft.DERATE_BOEING:
4908 self._derateLabel.set_text(xstr("takeoff_derate_boeing"))
4909 self._derateLabel.set_use_underline(True)
4910 self._derateLabel.set_sensitive(True)
4911
4912 self._derateEntry = Gtk.Entry()
4913 self._derateEntry.set_width_chars(7)
4914 self._derateEntry.set_tooltip_text(xstr("takeoff_derate_boeing_tooltip"))
4915 self._derateEntry.set_alignment(1.0)
4916 self._derateEntry.connect("changed", self._derateChanged)
4917 self._derateLabel.set_mnemonic_widget(self._derateEntry)
4918
4919 self._derateUnit = Gtk.Label("%")
4920 self._derateUnit.set_alignment(0.0, 0.5)
4921
4922 self._derateWidget = Gtk.Table(3, 1)
4923 self._derateWidget.set_row_spacings(4)
4924 self._derateWidget.set_col_spacings(16)
4925 self._derateWidget.set_homogeneous(False)
4926
4927 self._derateWidget.attach(self._derateEntry, 0, 2, 0, 1)
4928 self._derateWidget.attach(self._derateUnit, 2, 3, 0, 1)
4929
4930 self._derate.add(self._derateWidget)
4931 elif self._derateType==acft.DERATE_EPR:
4932 self._derateLabel.set_text("_EPR:")
4933 self._derateLabel.set_use_underline(True)
4934 self._derateLabel.set_sensitive(True)
4935
4936 self._derateWidget = Gtk.Entry()
4937 self._derateWidget.set_width_chars(7)
4938 self._derateWidget.set_tooltip_text(xstr("takeoff_derate_epr_tooltip"))
4939 self._derateWidget.set_alignment(1.0)
4940 self._derateWidget.connect("changed", self._derateChanged)
4941 self._derateLabel.set_mnemonic_widget(self._derateWidget)
4942
4943 self._derate.add(self._derateWidget)
4944 elif self._derateType==acft.DERATE_TUPOLEV:
4945 self._derateLabel.set_text(xstr("takeoff_derate_tupolev"))
4946 self._derateLabel.set_use_underline(True)
4947 self._derateLabel.set_sensitive(True)
4948
4949 nominal = Gtk.RadioButton.\
4950 new_with_label_from_widget(None,
4951 xstr("takeoff_derate_tupolev_nominal"))
4952 nominal.set_use_underline(True)
4953 nominal.set_tooltip_text(xstr("takeoff_derate_tupolev_nominal_tooltip"))
4954 nominal.connect("toggled", self._derateChanged)
4955
4956 takeoff = Gtk.RadioButton.\
4957 new_with_label_from_widget(nominal,
4958 xstr("takeoff_derate_tupolev_takeoff"))
4959
4960 takeoff.set_use_underline(True)
4961 takeoff.set_tooltip_text(xstr("takeoff_derate_tupolev_takeoff_tooltip"))
4962 takeoff.connect("toggled", self._derateChanged)
4963
4964 self._derateButtons = [nominal, takeoff]
4965
4966 self._derateWidget = Gtk.HBox()
4967 self._derateWidget.pack_start(nominal, False, False, 4)
4968 self._derateWidget.pack_start(takeoff, False, False, 4)
4969
4970 self._derate.add(self._derateWidget)
4971 elif self._derateType==acft.DERATE_B462:
4972 self._derateLabel.set_text("")
4973
4974 self._derateWidget = Gtk.CheckButton(xstr("takeoff_derate_b462"))
4975 self._derateWidget.set_tooltip_text(xstr("takeoff_derate_b462_tooltip"))
4976 self._derateWidget.set_use_underline(True)
4977 self._derate.add(self._derateWidget)
4978 else:
4979 self._derateWidget = None
4980 self._derateLabel.set_text("")
4981 self._derateLabel.set_sensitive(False)
4982
4983 def _metarChanged(self, entry):
4984 """Called when the METAR has changed."""
4985 print("TakeoffPage.metarChanged", self._updatingMETAR)
4986 if not self._updatingMETAR:
4987 self._updateForwardButton()
4988 self._wizard.metarChanged(entry.get_text(), self)
4989
4990 def _metarInserted(self, buffer, position, text, length):
4991 """Called when new characters are inserted into the METAR.
4992
4993 It uppercases all characters."""
4994 print("TakeoffPage.metarInserted", self._updatingMETAR)
4995 if not self._updatingMETAR:
4996 self._updatingMETAR = True
4997
4998 buffer.delete_text(position, length)
4999 buffer.insert_text(position, text.upper(), length)
5000
5001 self._updatingMETAR = False
5002
5003#-----------------------------------------------------------------------------
5004
5005class CruisePage(Page):
5006 """The page containing the flight level that might change during flight."""
5007 def __init__(self, wizard):
5008 """Construct the page."""
5009 super(CruisePage, self).__init__(wizard, "cruise",
5010 xstr("cruise_title"),
5011 xstr("cruise_help"))
5012
5013 self._loggable = False
5014 self._loggedCruiseLevel = 240
5015 self._activated = False
5016
5017 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.0,
5018 xscale = 0.0, yscale = 1.0)
5019
5020 mainBox = Gtk.VBox()
5021 alignment.add(mainBox)
5022 self.setMainWidget(alignment)
5023
5024 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
5025 xscale = 0.0, yscale = 0.0)
5026 mainBox.pack_start(alignment, False, False, 16)
5027
5028 levelBox = Gtk.HBox()
5029
5030 label = Gtk.Label(xstr("route_level"))
5031 label.set_use_underline(True)
5032 levelBox.pack_start(label, True, True, 0)
5033
5034 self._cruiseLevel = Gtk.SpinButton()
5035 self._cruiseLevel.set_increments(step = 10, page = 100)
5036 self._cruiseLevel.set_range(min = 50, max = 500)
5037 self._cruiseLevel.set_tooltip_text(xstr("cruise_route_level_tooltip"))
5038 self._cruiseLevel.set_numeric(True)
5039 self._cruiseLevel.connect("value-changed", self._cruiseLevelChanged)
5040 label.set_mnemonic_widget(self._cruiseLevel)
5041
5042 levelBox.pack_start(self._cruiseLevel, False, False, 8)
5043
5044 self._updateButton = Gtk.Button(xstr("cruise_route_level_update"));
5045 self._updateButton.set_use_underline(True)
5046 self._updateButton.set_tooltip_text(xstr("cruise_route_level_update_tooltip"))
5047 self._updateButton.connect("clicked", self._updateButtonClicked)
5048
5049 levelBox.pack_start(self._updateButton, False, False, 16)
5050
5051 mainBox.pack_start(levelBox, False, False, 0)
5052
5053 alignment = Gtk.Alignment(xalign = 0.0, yalign = 0.0,
5054 xscale = 0.0, yscale = 1.0)
5055 mainBox.pack_start(alignment, True, True, 0)
5056
5057 self.addCancelFlightButton()
5058
5059 self._backButton = self.addPreviousButton(clicked = self._backClicked)
5060 self._button = self.addNextButton(clicked = self._forwardClicked)
5061
5062 @property
5063 def activated(self):
5064 """Determine if the page is already activated or not."""
5065 return self._activated
5066
5067 @property
5068 def cruiseLevel(self):
5069 """Get the cruise level."""
5070 return self._loggedCruiseLevel
5071
5072 @property
5073 def loggableCruiseLevel(self):
5074 """Get the cruise level which should be logged."""
5075 return self._cruiseLevel.get_value_as_int()
5076
5077 def setLoggable(self, loggable):
5078 """Set whether the cruise altitude can be logged."""
5079 self._loggable = loggable
5080 self._updateButtons()
5081
5082 def activate(self):
5083 """Setup the route from the booked flight."""
5084 self._loggedCruiseLevel = self._wizard.filedCruiseLevel
5085 self._cruiseLevel.set_value(self._loggedCruiseLevel)
5086 self._activated = True
5087
5088 def reset(self):
5089 """Reset the page."""
5090 self._loggable = False
5091 self._activated = False
5092 super(CruisePage, self).reset()
5093
5094 def _updateButtons(self):
5095 """Update the sensitivity of the buttons."""
5096 self._updateButton.set_sensitive(self._loggable and
5097 self.loggableCruiseLevel!=
5098 self._loggedCruiseLevel)
5099
5100 def _cruiseLevelChanged(self, spinButton):
5101 """Called when the cruise level has changed."""
5102 self._updateButtons()
5103
5104 def _updateButtonClicked(self, button):
5105 """Called when the update button is clicked."""
5106 if self._wizard.cruiseLevelChanged():
5107 self._loggedCruiseLevel = self.loggableCruiseLevel
5108 self._updateButtons()
5109
5110 def _backClicked(self, button):
5111 """Called when the Back button is pressed."""
5112 self.goBack()
5113
5114 def _forwardClicked(self, button):
5115 """Called when the Forward button is clicked."""
5116 self._wizard.nextPage()
5117
5118#-----------------------------------------------------------------------------
5119
5120class LandingPage(Page):
5121 """Page for entering landing data."""
5122 def __init__(self, wizard):
5123 """Construct the landing page."""
5124 super(LandingPage, self).__init__(wizard, "landing",
5125 xstr("landing_title"),
5126 xstr("landing_help"),
5127 completedHelp = xstr("landing_chelp"))
5128
5129 self._flightEnded = False
5130
5131 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5132 xscale = 0.0, yscale = 0.0)
5133
5134 table = Gtk.Table(7, 24)
5135 table.set_row_spacings(4)
5136 table.set_col_spacings(16)
5137 table.set_homogeneous(False)
5138 alignment.add(table)
5139 self.setMainWidget(alignment)
5140
5141 row = 0
5142
5143 label = Gtk.Label(xstr("landing_metar"))
5144 label.set_use_underline(True)
5145 label.set_alignment(0.0, 0.5)
5146 table.attach(label, 1, 2, row, row+1)
5147
5148 self._metar = Gtk.Entry()
5149 self._metar.set_width_chars(40)
5150 self._metar.set_tooltip_text(xstr("landing_metar_tooltip"))
5151 self._metar.connect("changed", self._metarChanged)
5152 self._metar.get_buffer().connect_after("inserted-text", self._metarInserted)
5153 table.attach(self._metar, 2, 24, row, row+1)
5154 label.set_mnemonic_widget(self._metar)
5155
5156 self._updatingMETAR = False
5157
5158 row += 1
5159
5160 label = Gtk.Label(xstr("landing_star"))
5161 label.set_use_underline(True)
5162 label.set_alignment(0.0, 0.5)
5163 table.attach(label, 1, 2, row, row + 1)
5164
5165 self._star = Gtk.ComboBox.new_with_model_and_entry(comboModel)
5166
5167 self._star.set_entry_text_column(0)
5168 self._star.get_child().set_width_chars(10)
5169 self._star.set_tooltip_text(xstr("landing_star_tooltip"))
5170 self._star.connect("changed", self._upperChangedComboBox)
5171 self._star.set_sensitive(False)
5172 table.attach(self._star, 2, 4, row, row + 1)
5173 label.set_mnemonic_widget(self._star)
5174
5175 row += 1
5176
5177 label = Gtk.Label(xstr("landing_transition"))
5178 label.set_use_underline(True)
5179 label.set_alignment(0.0, 0.5)
5180 table.attach(label, 1, 2, row, row + 1)
5181
5182 self._transition = Gtk.ComboBox.new_with_model_and_entry(comboModel)
5183
5184 self._transition.set_entry_text_column(0)
5185 self._transition.get_child().set_width_chars(10)
5186 self._transition.set_tooltip_text(xstr("landing_transition_tooltip"))
5187 self._transition.connect("changed", self._upperChangedComboBox)
5188 self._transition.set_sensitive(False)
5189 table.attach(self._transition, 2, 4, row, row + 1)
5190 label.set_mnemonic_widget(self._transition)
5191
5192 row += 1
5193
5194 label = Gtk.Label(xstr("landing_runway"))
5195 label.set_use_underline(True)
5196 label.set_alignment(0.0, 0.5)
5197 table.attach(label, 1, 2, row, row + 1)
5198
5199 self._runway = Gtk.Entry()
5200 self._runway.set_width_chars(10)
5201 self._runway.set_tooltip_text(xstr("landing_runway_tooltip"))
5202 self._runway.connect("changed", self._upperChanged)
5203 table.attach(self._runway, 2, 4, row, row + 1)
5204 label.set_mnemonic_widget(self._runway)
5205
5206 row += 1
5207
5208 label = Gtk.Label(xstr("landing_approach"))
5209 label.set_use_underline(True)
5210 label.set_alignment(0.0, 0.5)
5211 table.attach(label, 1, 2, row, row + 1)
5212
5213 self._approachType = Gtk.Entry()
5214 self._approachType.set_width_chars(10)
5215 self._approachType.set_tooltip_text(xstr("landing_approach_tooltip"))
5216 self._approachType.connect("changed", self._upperChanged)
5217 table.attach(self._approachType, 2, 4, row, row + 1)
5218 label.set_mnemonic_widget(self._approachType)
5219
5220 row += 1
5221
5222 label = Gtk.Label(xstr("landing_vref"))
5223 label.set_use_markup(True)
5224 label.set_use_underline(True)
5225 label.set_alignment(0.0, 0.5)
5226 table.attach(label, 1, 2, row, row + 1)
5227
5228 self._vref = IntegerEntry()
5229 self._vref.set_width_chars(5)
5230 self._vref.set_tooltip_markup(xstr("landing_vref_tooltip_knots"))
5231 self._vref.connect("integer-changed", self._vrefChanged)
5232 table.attach(self._vref, 3, 4, row, row + 1)
5233 label.set_mnemonic_widget(self._vref)
5234
5235 self._vrefUnit = Gtk.Label(xstr("label_knots"))
5236 table.attach(self._vrefUnit, 4, 5, row, row + 1)
5237
5238 row += 1
5239
5240 self._antiIceOn = Gtk.CheckButton(xstr("landing_antiice"))
5241 self._antiIceOn.set_use_underline(True)
5242 self._antiIceOn.set_tooltip_text(xstr("landing_antiice_tooltip"))
5243 table.attach(self._antiIceOn, 3, 5, row, row + 1)
5244
5245 self.addCancelFlightButton()
5246
5247 self.addPreviousButton(clicked = self._backClicked)
5248
5249 self._button = self.addNextButton(clicked = self._forwardClicked)
5250
5251 self._active = False
5252
5253 @property
5254 def star(self):
5255 """Get the STAR or None if none entered."""
5256 text = self._star.get_child().get_text()
5257 return text if self._star.get_active()!=0 and text and text!="N/A" \
5258 else None
5259
5260 @property
5261 def transition(self):
5262 """Get the transition or None if none entered."""
5263 text = self._transition.get_child().get_text()
5264 return text if self._transition.get_active()!=0 and text and text!="N/A" \
5265 else None
5266
5267 @property
5268 def approachType(self):
5269 """Get the approach type."""
5270 return self._approachType.get_text()
5271
5272 @property
5273 def runway(self):
5274 """Get the runway."""
5275 return self._runway.get_text()
5276
5277 @property
5278 def vref(self):
5279 """Return the landing reference speed."""
5280 return self._vref.get_int()
5281
5282 @property
5283 def antiIceOn(self):
5284 """Get whether the anti-ice system has been turned on."""
5285 return self._antiIceOn.get_active()
5286
5287 @antiIceOn.setter
5288 def antiIceOn(self, value):
5289 """Set the anti-ice indicator."""
5290 self._antiIceOn.set_active(value)
5291
5292 def reset(self):
5293 """Reset the page if the wizard is reset."""
5294 super(LandingPage, self).reset()
5295 self._vref.reset()
5296 self._antiIceOn.set_active(False)
5297 self._flightEnded = False
5298 self._active = False
5299
5300 def activate(self):
5301 """Called when the page is activated."""
5302 self._updatingMETAR = True
5303 self._metar.get_buffer().set_text(self._wizard.arrivalMETAR, -1)
5304 self._updatingMETAR = False
5305
5306 self._star.set_active(0)
5307 self._star.set_sensitive(True)
5308
5309 self._transition.set_active(0)
5310 self._transition.set_sensitive(True)
5311
5312 if self._wizard.landingRunway is None:
5313 self._runway.set_text("")
5314 else:
5315 self._runway.set_text(self._wizard.landingRunway)
5316 self._runway.set_sensitive(True)
5317
5318 self._approachType.set_text("")
5319 self._approachType.set_sensitive(True)
5320
5321 self._vref.set_int(None)
5322 self._vref.set_sensitive(True)
5323
5324 i18nSpeedUnit = self._wizard.gui.flight.getI18NSpeedUnit()
5325 speedUnit = xstr("label" + i18nSpeedUnit)
5326 self._vrefUnit.set_text(speedUnit)
5327
5328 self._vref.set_tooltip_markup(xstr("landing_vref_tooltip" +
5329 i18nSpeedUnit))
5330
5331 self._updateForwardButton()
5332
5333 self._active = True
5334
5335 def flightEnded(self):
5336 """Called when the flight has ended."""
5337 super(LandingPage, self).flightEnded()
5338 self._flightEnded = True
5339 self._updateForwardButton()
5340
5341 def changeMETAR(self, metar):
5342 """Change the METAR as a result of an edit on one of the other
5343 pages."""
5344 if self._active:
5345 print("LandingPage.changeMETAR")
5346 self._updatingMETAR = True
5347 self._metar.get_buffer().set_text(metar, -1)
5348 self._updatingMETAR = False
5349
5350 self._updateForwardButton()
5351
5352 def _updateForwardButton(self):
5353 """Update the sensitivity of the forward button."""
5354 sensitive = self._flightEnded and \
5355 self._metar.get_text()!="" and \
5356 (self.star is not None or
5357 self.transition is not None) and \
5358 self._runway.get_text()!="" and \
5359 self._approachType.get_text()!="" and \
5360 self.vref is not None
5361 self._button.set_sensitive(sensitive)
5362
5363 def _upperChanged(self, entry):
5364 """Called for entry widgets that must be converted to uppercase."""
5365 entry.set_text(entry.get_text().upper())
5366 self._updateForwardButton()
5367
5368 def _upperChangedComboBox(self, comboBox):
5369 """Called for combo box widgets that must be converted to uppercase."""
5370 if comboBox.get_active()==-1:
5371 entry = comboBox.get_child()
5372 entry.set_text(entry.get_text().upper())
5373 self._updateForwardButton()
5374
5375 def _vrefChanged(self, widget, value):
5376 """Called when the Vref has changed."""
5377 self._updateForwardButton()
5378
5379 def _backClicked(self, button):
5380 """Called when the Back button is pressed."""
5381 self.goBack()
5382
5383 def _forwardClicked(self, button):
5384 """Called when the forward button is clicked."""
5385 wizard = self._wizard
5386
5387 aircraft = wizard.gui.flight.aircraft
5388 aircraft.updateVRef()
5389 aircraft.updateLandingAntiIce()
5390 if wizard.gui.config.onlineGateSystem and \
5391 wizard.loggedIn and not self._completed and \
5392 wizard.bookedFlight.arrivalICAO=="LHBP" and \
5393 not wizard.entranceExam:
5394 wizard.getFleet(callback = self._fleetRetrieved, force = True)
5395 elif wizard.entranceExam:
5396 self._handleEntranceExamDone()
5397 else:
5398 wizard.nextPage()
5399
5400 def _fleetRetrieved(self, fleet):
5401 """Callback for the fleet retrieval."""
5402 self._wizard.nextPage()
5403
5404 def _metarChanged(self, entry):
5405 """Called when the METAR has changed."""
5406 print("LandingPage.metarChanged", self._updatingMETAR)
5407 if not self._updatingMETAR:
5408 self._updateForwardButton()
5409 self._wizard.metarChanged(entry.get_text(), self)
5410
5411 def _metarInserted(self, buffer, position, text, length):
5412 """Called when new characters are inserted into the METAR.
5413
5414 It uppercases all characters."""
5415 print("LandingPage.metarInserted", self._updatingMETAR)
5416 if not self._updatingMETAR:
5417 self._updatingMETAR = True
5418
5419 buffer.delete_text(position, length)
5420 buffer.insert_text(position, text.upper(), length)
5421
5422 self._updatingMETAR = False
5423
5424 def _handleEntranceExamDone(self):
5425 """Handle the end of the entrance exam.
5426
5427 If the there was a NO-GO fault, notify the user that exam is a failure
5428 and take them back to the student page. Otherwise congratulate, update
5429 the database to reflect that the exam has been taken and go back to the
5430 student page."""
5431 self._wizard.jumpPage("chkfinish")
5432
5433#-----------------------------------------------------------------------------
5434
5435class PIREPSaveHelper(object):
5436 """A helper to use for saving PIREPs."""
5437 def __init__(self, wizard):
5438 """Construct the helper."""
5439 super(PIREPSaveHelper, self).__init__()
5440
5441 self._wizard = wizard
5442
5443 self._lastSavePath = None
5444 self._savePIREPDialog = None
5445
5446 def addButton(self, page):
5447 """Add a button to save the PIREP to the given page."""
5448 return page.addButton(xstr("finish_save"), sensitive = False,
5449 clicked = self._saveClicked,
5450 tooltip = xstr("finish_save_tooltip"),
5451 clickedArg = page)
5452
5453 def autoSavePIREP(self, page):
5454 """Perform the automatic saving of the PIREP."""
5455 self._lastSavePath = os.path.join(self._wizard.gui.config.pirepDirectory,
5456 self._getDefaultPIREPName())
5457 self._lastSavePath = self._lastSavePath
5458 self._savePIREP(page, automatic = True)
5459
5460 def _getDefaultPIREPName(self):
5461 """Get the default name of the PIREP."""
5462 gui = self._wizard.gui
5463
5464 bookedFlight = gui.bookedFlight
5465 tm = time.gmtime()
5466
5467 pilotID = self._wizard.pilotID
5468 if pilotID: pilotID += " "
5469 return "%s%s %02d%02d %s-%s.pirep" % \
5470 (pilotID, str(bookedFlight.departureTime.date()),
5471 tm.tm_hour, tm.tm_min,
5472 bookedFlight.departureICAO, bookedFlight.arrivalICAO)
5473
5474 def _saveClicked(self, button, page):
5475 """Called when the Save PIREP button is clicked."""
5476 gui = self._wizard.gui
5477
5478 fileName = self._getDefaultPIREPName()
5479
5480 dialog = self._getSaveDialog()
5481
5482 if self._lastSavePath is None:
5483 pirepDirectory = gui.config.pirepDirectory
5484 if pirepDirectory is not None:
5485 dialog.set_current_folder(pirepDirectory)
5486 else:
5487 dialog.set_current_folder(os.path.dirname(self._lastSavePath))
5488
5489 dialog.set_current_name(fileName)
5490 result = dialog.run()
5491 dialog.hide()
5492
5493 if result==Gtk.ResponseType.OK:
5494 self._lastSavePath = dialog.get_filename()
5495 self._savePIREP(page)
5496
5497 def _savePIREP(self, page, automatic = False):
5498 """Perform the saving of the PIREP."""
5499
5500 gui = self._wizard.gui
5501
5502 if automatic:
5503 gui.beginBusy(xstr("finish_autosave_busy"))
5504
5505 pirep = PIREP(gui.flight)
5506 error = pirep.save(self._lastSavePath)
5507
5508 if automatic:
5509 gui.endBusy()
5510
5511 if error:
5512 type = Gtk.MessageType.ERROR
5513 message = xstr("finish_save_failed")
5514 secondary = xstr("finish_save_failed_sec") % (error,)
5515 else:
5516 type = Gtk.MessageType.INFO
5517 message = xstr("finish_save_done")
5518 if automatic:
5519 secondary = xstr("finish_save_done_sec") % (self._lastSavePath,)
5520 else:
5521 secondary = None
5522 page.setPIREPSaved()
5523
5524 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
5525 type = type, message_format = message)
5526 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
5527 dialog.set_title(WINDOW_TITLE_BASE)
5528 if secondary is not None:
5529 dialog.format_secondary_markup(secondary)
5530
5531 dialog.run()
5532 dialog.hide()
5533
5534 def _getSaveDialog(self):
5535 """Get the PIREP saving dialog.
5536
5537 If it does not exist yet, create it."""
5538 if self._savePIREPDialog is None:
5539 gui = self._wizard.gui
5540 dialog = Gtk.FileChooserDialog(title = WINDOW_TITLE_BASE + " - " +
5541 xstr("finish_save_title"),
5542 action = Gtk.FileChooserAction.SAVE,
5543 buttons = (Gtk.STOCK_CANCEL,
5544 Gtk.ResponseType.CANCEL,
5545 Gtk.STOCK_OK, Gtk.ResponseType.OK),
5546 parent = gui.mainWindow)
5547 dialog.set_modal(True)
5548 dialog.set_do_overwrite_confirmation(True)
5549
5550 filter = Gtk.FileFilter()
5551 filter.set_name(xstr("file_filter_pireps"))
5552 filter.add_pattern("*.pirep")
5553 dialog.add_filter(filter)
5554
5555 filter = Gtk.FileFilter()
5556 filter.set_name(xstr("file_filter_all"))
5557 filter.add_pattern("*.*")
5558 dialog.add_filter(filter)
5559
5560 self._savePIREPDialog = dialog
5561
5562 return self._savePIREPDialog
5563
5564#-----------------------------------------------------------------------------
5565
5566class FinishPage(Page):
5567 """Flight finish page."""
5568 def __init__(self, wizard, saveHelper):
5569 """Construct the finish page."""
5570 help = xstr("finish_help") + xstr("finish_help_goodtime")
5571 super(FinishPage, self).__init__(wizard, "finish",
5572 xstr("finish_title"), help)
5573
5574 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5575 xscale = 0.0, yscale = 0.0)
5576
5577 table = Gtk.Table(10, 2)
5578 table.set_row_spacings(4)
5579 table.set_col_spacings(16)
5580 table.set_homogeneous(False)
5581 alignment.add(table)
5582 self.setMainWidget(alignment)
5583
5584 row = 0
5585
5586 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5587 label = Gtk.Label(xstr("finish_rating"))
5588 labelAlignment.add(label)
5589 table.attach(labelAlignment, 0, 1, row, row+1)
5590
5591 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5592 self._flightRating = Gtk.Label()
5593 self._flightRating.set_width_chars(8)
5594 self._flightRating.set_alignment(0.0, 0.5)
5595 self._flightRating.set_use_markup(True)
5596 labelAlignment.add(self._flightRating)
5597 table.attach(labelAlignment, 1, 2, row, row+1)
5598
5599 row += 1
5600
5601 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5602 label = Gtk.Label(xstr("finish_dep_time"))
5603 labelAlignment.add(label)
5604 table.attach(labelAlignment, 0, 1, row, row+1)
5605
5606 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5607 self._depTime = Gtk.Label()
5608 self._depTime.set_width_chars(13)
5609 self._depTime.set_alignment(0.0, 0.5)
5610 self._depTime.set_use_markup(True)
5611 self._depTime.set_tooltip_markup(xstr("finish_dep_time_tooltip"))
5612 labelAlignment.add(self._depTime)
5613 table.attach(labelAlignment, 1, 2, row, row+1)
5614
5615 row += 1
5616
5617 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5618 label = Gtk.Label(xstr("finish_flight_time"))
5619 labelAlignment.add(label)
5620 table.attach(labelAlignment, 0, 1, row, row+1)
5621
5622 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5623 self._flightTime = Gtk.Label()
5624 self._flightTime.set_width_chars(10)
5625 self._flightTime.set_alignment(0.0, 0.5)
5626 self._flightTime.set_use_markup(True)
5627 labelAlignment.add(self._flightTime)
5628 table.attach(labelAlignment, 1, 2, row, row+1)
5629
5630 row += 1
5631
5632 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5633 label = Gtk.Label(xstr("finish_block_time"))
5634 labelAlignment.add(label)
5635 table.attach(labelAlignment, 0, 1, row, row+1)
5636
5637 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5638 self._blockTime = Gtk.Label()
5639 self._blockTime.set_width_chars(10)
5640 self._blockTime.set_alignment(0.0, 0.5)
5641 self._blockTime.set_use_markup(True)
5642 labelAlignment.add(self._blockTime)
5643 table.attach(labelAlignment, 1, 2, row, row+1)
5644
5645 row += 1
5646
5647 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5648 label = Gtk.Label(xstr("finish_arr_time"))
5649 labelAlignment.add(label)
5650 table.attach(labelAlignment, 0, 1, row, row+1)
5651
5652 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5653 self._arrTime = Gtk.Label()
5654 self._arrTime.set_width_chars(13)
5655 self._arrTime.set_alignment(0.0, 0.5)
5656 self._arrTime.set_use_markup(True)
5657 self._arrTime.set_tooltip_markup(xstr("finish_arr_time_tooltip"))
5658 labelAlignment.add(self._arrTime)
5659 table.attach(labelAlignment, 1, 2, row, row+1)
5660
5661 row += 1
5662
5663 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5664 label = Gtk.Label(xstr("finish_distance"))
5665 labelAlignment.add(label)
5666 table.attach(labelAlignment, 0, 1, row, row+1)
5667
5668 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5669 self._distanceFlown = Gtk.Label()
5670 self._distanceFlown.set_width_chars(10)
5671 self._distanceFlown.set_alignment(0.0, 0.5)
5672 self._distanceFlown.set_use_markup(True)
5673 labelAlignment.add(self._distanceFlown)
5674 table.attach(labelAlignment, 1, 2, row, row+1)
5675
5676 row += 1
5677
5678 labelAlignment = Gtk.Alignment(xalign=1.0, xscale=0.0)
5679 label = Gtk.Label(xstr("finish_fuel"))
5680 labelAlignment.add(label)
5681 table.attach(labelAlignment, 0, 1, row, row+1)
5682
5683 labelAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5684 self._fuelUsed = Gtk.Label()
5685 self._fuelUsed.set_width_chars(10)
5686 self._fuelUsed.set_alignment(0.0, 0.5)
5687 self._fuelUsed.set_use_markup(True)
5688 labelAlignment.add(self._fuelUsed)
5689 table.attach(labelAlignment, 1, 2, row, row+1)
5690
5691 row += 1
5692
5693 labelAlignment = Gtk.Alignment(xalign = 1.0, xscale = 0.0,
5694 yalign = 0.5, yscale = 0.0)
5695 label = Gtk.Label(xstr("finish_type"))
5696 label.set_use_underline(True)
5697 labelAlignment.add(label)
5698 table.attach(labelAlignment, 0, 1, row, row+1)
5699
5700 self._onlineFlight = Gtk.CheckButton(xstr("finish_online"))
5701 self._onlineFlight.set_use_underline(True)
5702 self._onlineFlight.set_tooltip_text(xstr("finish_online_tooltip"))
5703 onlineFlightAlignment = Gtk.Alignment(xalign=0.0, xscale=0.0)
5704 onlineFlightAlignment.add(self._onlineFlight)
5705 table.attach(onlineFlightAlignment, 1, 2, row, row + 1)
5706
5707 row += 1
5708
5709 labelAlignment = Gtk.Alignment(xalign = 1.0, xscale = 0.0,
5710 yalign = 0.5, yscale = 0.0)
5711 self._gateLabel = Gtk.Label(xstr("finish_gate"))
5712 self._gateLabel.set_use_underline(True)
5713 labelAlignment.add(self._gateLabel)
5714 table.attach(labelAlignment, 0, 1, row, row+1)
5715
5716 self._gatesModel = Gtk.ListStore(str)
5717
5718 self._gate = Gtk.ComboBox(model = self._gatesModel)
5719 renderer = Gtk.CellRendererText()
5720 self._gate.pack_start(renderer, True)
5721 self._gate.add_attribute(renderer, "text", 0)
5722 self._gate.set_tooltip_text(xstr("finish_gate_tooltip"))
5723 self._gate.connect("changed", self._gateChanged)
5724 gateAlignment = Gtk.Alignment(xalign=0.0, xscale=1.0)
5725 gateAlignment.add(self._gate)
5726 table.attach(gateAlignment, 1, 2, row, row+1)
5727 self._gateLabel.set_mnemonic_widget(self._gate)
5728
5729 self.addButton(xstr("finish_newFlight"),
5730 sensitive = True,
5731 clicked = self._newFlightClicked,
5732 tooltip = xstr("finish_newFlight_tooltip"),
5733 padding = 16)
5734
5735 self.addPreviousButton(clicked = self._backClicked)
5736
5737 self._saveHelper = saveHelper
5738 self._saveButton = saveHelper.addButton(self)
5739
5740 self._tooBigTimeDifference = False
5741 self._deferredAutoSave = False
5742 self._pirepSaved = False
5743 self._pirepSent = False
5744
5745 self._sendButton = self.addButton(xstr("sendPIREP"), default = True,
5746 sensitive = False,
5747 clicked = self._sendClicked,
5748 tooltip = xstr("sendPIREP_tooltip"))
5749
5750 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 10*60.0)
5751 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 20*60.0)
5752 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), 0*60.0)
5753 # self._formatTime(datetime.datetime(1970, 1, 1, 0, 10), (23*60.0+50)*60.0)
5754 # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (1*60.0+5)*60.0)
5755 # self._formatTime(datetime.datetime(1970, 1, 1, 1, 0), (0*60.0+50)*60.0)
5756 # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (0*60.0+5)*60.0)
5757 # self._formatTime(datetime.datetime(1970, 1, 1, 23, 55), (23*60.0+45)*60.0)
5758
5759 @property
5760 def online(self):
5761 """Get whether the flight was an online flight or not."""
5762 return self._onlineFlight.get_active()
5763
5764 def activate(self):
5765 """Activate the page."""
5766 self._deferredAutoSave = False
5767 self._pirepSaved = False
5768 self._pirepSent = False
5769
5770 flight = self._wizard.gui._flight
5771 rating = flight.logger.getRating()
5772 if rating<0:
5773 self._flightRating.set_markup('<b><span foreground="red">NO GO</span></b>')
5774 else:
5775 self._flightRating.set_markup("<b>%.1f %%</b>" % (rating,))
5776
5777 flightLength = flight.flightTimeEnd - flight.flightTimeStart
5778 self._flightTime.set_markup("<b>%s</b>" % \
5779 (util.getTimeIntervalString(flightLength),))
5780
5781 blockLength = flight.blockTimeEnd - flight.blockTimeStart
5782 self._blockTime.set_markup("<b>%s</b>" % \
5783 (util.getTimeIntervalString(blockLength),))
5784
5785 self._distanceFlown.set_markup("<b>%.2f NM</b>" % \
5786 (flight.flownDistance,))
5787
5788 self._fuelUsed.set_markup("<b>%.0f kg</b>" % \
5789 (flight.startFuel - flight.endFuel,))
5790
5791 self._onlineFlight.set_active(self._wizard.loggedIn)
5792
5793 self._gatesModel.clear()
5794 if self._wizard.gui.config.onlineGateSystem and \
5795 self._wizard.loggedIn and \
5796 self._wizard.bookedFlight.arrivalICAO=="LHBP" and \
5797 not self._wizard.entranceExam:
5798 occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
5799 for gate in lhbpGates.gates:
5800 if gate.isAvailable(lhbpGates, occupiedGateNumbers):
5801 self._gatesModel.append([gate.number])
5802 self._gateLabel.set_sensitive(True)
5803 self._gate.set_sensitive(True)
5804 self._gate.set_active(-1)
5805 else:
5806 self._gateLabel.set_sensitive(False)
5807 self._gate.set_sensitive(False)
5808
5809 self._updateTimes()
5810
5811 def updateButtons(self):
5812 """Update the sensitivity state of the buttons."""
5813 gui = self._wizard.gui
5814 faultsExplained = gui.faultsFullyExplained
5815 timesCorrect = not self._tooBigTimeDifference or \
5816 gui.hasComments or gui.hasDelayCode
5817 sensitive = gui.flight is not None and \
5818 gui.flight.stage==const.STAGE_END and \
5819 (self._gatesModel.get_iter_first() is None or
5820 self._gate.get_active()>=0) and \
5821 faultsExplained and timesCorrect
5822
5823 self._updateHelp(faultsExplained, timesCorrect)
5824
5825 wasSensitive = self._saveButton.get_sensitive()
5826
5827 if gui.config.pirepAutoSave and sensitive and not wasSensitive:
5828 if gui.isWizardActive():
5829 self._saveHelper.autoSavePIREP(self)
5830 else:
5831 self._deferredAutoSave = True
5832
5833 if not sensitive:
5834 self._deferredAutoSave = False
5835
5836 self._saveButton.set_sensitive(sensitive)
5837 self._sendButton.set_sensitive(sensitive and
5838 self._wizard.bookedFlight.id is not None)
5839
5840 def grabDefault(self):
5841 """If the page has a default button, make it the default one."""
5842 super(FinishPage, self).grabDefault()
5843 if self._deferredAutoSave:
5844 self._saveHelper.autoSavePIREP(self)
5845 self._deferredAutoSave = False
5846
5847 def setPIREPSaved(self):
5848 """Mark the PIREP as saved."""
5849 self._pirepSaved = True
5850
5851 def _backClicked(self, button):
5852 """Called when the Back button is pressed."""
5853 self.goBack()
5854
5855 def _gateChanged(self, comboBox):
5856 """Called when the arrival gate has changed."""
5857 self.updateButtons()
5858
5859 def _newFlightClicked(self, button):
5860 """Called when the new flight button is clicked."""
5861 gui = self._wizard.gui
5862 if not self._pirepSent and not self._pirepSaved:
5863 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
5864 type = Gtk.MessageType.QUESTION,
5865 message_format = xstr("finish_newFlight_question"))
5866
5867 dialog.add_button(xstr("button_no"), Gtk.ResponseType.NO)
5868 dialog.add_button(xstr("button_yes"), Gtk.ResponseType.YES)
5869
5870 dialog.set_title(WINDOW_TITLE_BASE)
5871 result = dialog.run()
5872 dialog.hide()
5873 if result!=Gtk.ResponseType.YES:
5874 return
5875
5876 gui.reset()
5877
5878 def _sendClicked(self, button):
5879 """Called when the Send button is clicked."""
5880 pirep = PIREP(self._wizard.gui.flight)
5881 self._wizard.gui.sendPIREP(pirep,
5882 callback = self._handlePIREPSent)
5883
5884 def _handlePIREPSent(self, returned, result):
5885 """Callback for the PIREP sending result."""
5886 self._pirepSent = returned and result.success
5887 if self._wizard.gui.config.onlineGateSystem and \
5888 self._wizard.loggedIn and not self._wizard.entranceExam and \
5889 returned and result.success:
5890 bookedFlight = self._wizard.bookedFlight
5891 if bookedFlight.arrivalICAO=="LHBP":
5892 iter = self._gate.get_active_iter()
5893 gateNumber = None if iter is None \
5894 else self._gatesModel.get_value(iter, 0)
5895
5896 status = const.PLANE_PARKING if gateNumber is None \
5897 else const.PLANE_HOME
5898 else:
5899 gateNumber = None
5900 status = const.PLANE_AWAY
5901
5902 self._wizard.updatePlane(self._planeUpdated,
5903 bookedFlight.tailNumber,
5904 status, gateNumber = gateNumber)
5905
5906 def _planeUpdated(self, success):
5907 """Callback for the plane updating."""
5908 pass
5909
5910 def _formatTime(self, scheduledTime, realTimestamp, warningError):
5911 """Format the departure or arrival time based on the given data as a
5912 markup for a label."""
5913 (warning, error) = warningError
5914 realTime = time.gmtime(realTimestamp)
5915
5916 if warning:
5917 colour = "red" if error else "orange"
5918 markupBegin = '<span foreground="%s">' % (colour,)
5919 markupEnd = '</span>'
5920 else:
5921 markupBegin = markupEnd = ""
5922
5923 markup = "<b>%s%02d:%02d [%02d:%02d]%s</b>" % \
5924 (markupBegin,
5925 realTime.tm_hour, realTime.tm_min,
5926 scheduledTime.hour, scheduledTime.minute,
5927 markupEnd)
5928
5929 return markup
5930
5931 def _updateTimes(self):
5932 """Format the flight times and the help text according to the flight
5933 type.
5934
5935 The buttons are also updated.
5936 """
5937 flight = self._wizard.gui._flight
5938 bookedFlight = flight.bookedFlight
5939
5940 (departureWarning, departureError) = flight.blockTimeStartWrong
5941 (arrivalWarning, arrivalError) = flight.blockTimeEndWrong
5942
5943 if bookedFlight.flightType==const.FLIGHTTYPE_VIP:
5944 departureError = arrivalError = False
5945
5946 self._tooBigTimeDifference = departureError and arrivalError
5947
5948 self._depTime.set_markup(self._formatTime(bookedFlight.departureTime,
5949 flight.blockTimeStart,
5950 (departureWarning,
5951 departureError)))
5952
5953 self._arrTime.set_markup(self._formatTime(bookedFlight.arrivalTime,
5954 flight.blockTimeEnd,
5955 (arrivalWarning,
5956 arrivalError)))
5957
5958 self.updateButtons()
5959
5960 def _updateHelp(self, faultsExplained, timesCorrect):
5961 """Update the help text according to the actual situation."""
5962 if not faultsExplained:
5963 self.setHelp(xstr("finish_help") + xstr("finish_help_faults"))
5964 elif not timesCorrect:
5965 self.setHelp(xstr("finish_help") + xstr("finish_help_wrongtime"))
5966 else:
5967 self.setHelp(xstr("finish_help") + xstr("finish_help_goodtime"))
5968
5969
5970#-----------------------------------------------------------------------------
5971
5972class CheckFlightFinishPage(Page):
5973 """Finish page for a check flight."""
5974 def __init__(self, wizard, saveHelper):
5975 """Construct the check flight finish page."""
5976 super(CheckFlightFinishPage, self).__init__(wizard,
5977 "chkfinish",
5978 xstr("chkfinish_title"),
5979 "")
5980
5981 alignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
5982 xscale = 1.0, yscale = 1.0)
5983 self._label = Gtk.Label()
5984 alignment.add(self._label)
5985
5986 self.setMainWidget(alignment)
5987
5988 self._saveHelper = saveHelper
5989 self._saveButton = saveHelper.addButton(self)
5990
5991 self._button = self.addNextButton(sensitive = False,
5992 clicked = self._forwardClicked)
5993
5994 def activate(self):
5995 """Activate the page."""
5996 wizard = self._wizard
5997 loginResult = wizard.loginResult
5998 gui = wizard.gui
5999 rating = gui.flight.logger.getRating()
6000
6001 if rating>=0:
6002 loginResult.checkFlightStatus = True
6003
6004 firstOfficer = \
6005 loginResult.entryExamPassed and loginResult.checkFlightStatus
6006
6007 if firstOfficer:
6008 loginResult.rank = "FO"
6009
6010 if rating<0:
6011 mainMessage = xstr("chkfinish_failed")
6012 else:
6013 mainMessage = xstr("chkfinish_passed_begin")
6014 if firstOfficer:
6015 mainMessage += xstr("chkfinish_passed_fo")
6016 mainMessage += xstr("chkfinish_passed_end")
6017
6018 if firstOfficer:
6019 nextMessage = xstr("chkfinish_next")
6020 else:
6021 nextMessage = xstr("chkfinish_next_student_begin")
6022 if not loginResult.entryExamPassed and \
6023 not loginResult.checkFlightStatus:
6024 nextMessage += xstr("chkfinish_next_student_nothing")
6025 elif loginResult.entryExamPassed and \
6026 not loginResult.checkFlightStatus:
6027 nextMessage += xstr("chkfinish_next_student_no_flight")
6028 elif not loginResult.entryExamPassed and \
6029 loginResult.checkFlightStatus:
6030 nextMessage += xstr("chkfinish_next_student_no_exam")
6031
6032 self._label.set_text(mainMessage +
6033 xstr("chkfinish_savepirep") +
6034 nextMessage)
6035 self._label.set_use_markup(True)
6036 self._label.set_alignment(0.5, 0.0)
6037
6038 self._saveButton.set_sensitive(True)
6039 self._button.set_sensitive(True)
6040
6041 def _forwardClicked(self, button):
6042 """Jump to the student page if there are some tasks to do,
6043 or to the flight selection page, if the pilot is allowed to perform
6044 MAVA flights."""
6045 wizard = self._wizard
6046 gui = wizard.gui
6047
6048 loginResult = wizard.loginResult
6049 if loginResult.checkFlightStatus:
6050 gui.beginBusy(xstr("chkfinish_updateweb_busy"))
6051 gui.webHandler.setCheckFlightPassed(self._checkFlightPassedSetCallback,
6052 wizard.checkFlightAircraftType)
6053 else:
6054 self._resetGUI()
6055
6056 def _checkFlightPassedSetCallback(self, returned, result):
6057 """Called when the check flight status has been set."""
6058 GObject.idle_add(self._checkFlightPassedSet, returned, result)
6059
6060 def _checkFlightPassedSet(self, returned, result):
6061 """Handle the result of an attempt to set the check flight status."""
6062 gui = self._wizard.gui
6063
6064 gui.endBusy()
6065
6066 if returned:
6067 self._resetGUI()
6068 else:
6069 dialog = Gtk.MessageDialog(parent = gui.mainWindow,
6070 type = Gtk.MessageType.ERROR,
6071 message_format =
6072 xstr("chkfinish_passedset_failed"))
6073 dialog.set_title(WINDOW_TITLE_BASE + " - " +
6074 xstr("chkfinish_passedset_failed_title"))
6075 dialog.format_secondary_markup(xstr("chkfinish_passedset_failed_secondary"))
6076
6077 dialog.add_button(xstr("button_ok"), 0)
6078
6079 dialog.run()
6080 dialog.hide()
6081
6082 def _resetGUI(self):
6083 """Reset the GUI."""
6084 gui = self._wizard.gui
6085 gui.reset()
6086
6087#-----------------------------------------------------------------------------
6088
6089class Wizard(Gtk.VBox):
6090 """The flight wizard."""
6091 def __init__(self, gui):
6092 """Construct the wizard."""
6093 super(Wizard, self).__init__()
6094
6095 self.gui = gui
6096
6097 self._pages = []
6098 self._currentPage = None
6099
6100 self._loginPage = LoginPage(self)
6101 self._pages.append(self._loginPage)
6102 self._flightSelectionPage = FlightSelectionPage(self)
6103 self._pages.append(self._flightSelectionPage)
6104 self._pages.append(GateSelectionPage(self))
6105 self._pages.append(RegisterPage(self))
6106 self._studentPage = StudentPage(self)
6107 self._pages.append(self._studentPage)
6108 self._pages.append(ConnectPage(self))
6109 self._payloadPage = PayloadPage(self)
6110 self._pages.append(self._payloadPage)
6111 self._payloadIndex = len(self._pages)
6112 self._pages.append(TimePage(self))
6113 self._routePage = RoutePage(self)
6114 self._pages.append(self._routePage)
6115 self._simBriefSetupPage = SimBriefSetupPage(self)
6116 self._pages.append(self._simBriefSetupPage)
6117 self._simBriefingPage = SimBriefingPage(self)
6118 self._pages.append(self._simBriefingPage)
6119 self._pages.append(FuelPage(self))
6120 self._departureBriefingPage = BriefingPage(self, True)
6121 self._pages.append(self._departureBriefingPage)
6122 self._arrivalBriefingPage = BriefingPage(self, False)
6123 self._pages.append(self._arrivalBriefingPage)
6124 self._arrivalBriefingIndex = len(self._pages)
6125 self._takeoffPage = TakeoffPage(self)
6126 self._pages.append(self._takeoffPage)
6127 self._cruisePage = CruisePage(self)
6128 self._pages.append(self._cruisePage)
6129 self._landingPage = LandingPage(self)
6130 self._pages.append(self._landingPage)
6131
6132 pirepSaveHelper = PIREPSaveHelper(self)
6133
6134 self._finishPage = FinishPage(self, pirepSaveHelper)
6135 self._pages.append(self._finishPage)
6136 self._pages.append(CheckFlightFinishPage(self, pirepSaveHelper))
6137
6138 self._requestedWidth = None
6139 self._requestedHeight = None
6140
6141 self.connect("size-allocate", self._sizeAllocate)
6142
6143 for page in self._pages:
6144 page.show_all()
6145 page.setStyle()
6146
6147 self._initialize()
6148 self._allocateSize()
6149
6150 def _sizeAllocate(self, widget, allocation):
6151 if self._requestedWidth is not None and \
6152 self._requestedHeight is not None:
6153 return
6154
6155 (maxWidth, maxHeight) = self._allocateSize()
6156
6157 self._requestedWidth = maxWidth
6158 self._requestedHeight = maxHeight
6159
6160 def _allocateSize(self):
6161 """Perform the real size allocation."""
6162
6163 if self._currentPage is not None:
6164 self.remove(self._pages[self._currentPage])
6165
6166 maxWidth = 0
6167 maxHeight = 0
6168 for page in self._pages:
6169 self.add(page)
6170 self.show_all()
6171 pageSizeRequest = page.size_request()
6172 width = pageSizeRequest.width
6173 height = pageSizeRequest.height
6174 maxWidth = max(maxWidth, width)
6175 maxHeight = max(maxHeight, height)
6176 self.remove(page)
6177
6178 if self._currentPage is not None:
6179 self.add(self._pages[self._currentPage])
6180
6181 self.set_size_request(maxWidth, maxHeight)
6182
6183 return (maxWidth, maxHeight)
6184
6185 @property
6186 def pilotID(self):
6187 """Get the pilot ID, if given."""
6188 return self._loginPage.pilotID
6189
6190 @property
6191 def entranceExam(self):
6192 """Get whether an entrance exam is about to be taken."""
6193 return self._loginResult is not None and self._loginResult.rank=="STU"
6194
6195 @property
6196 def loggedIn(self):
6197 """Indicate if there was a successful login."""
6198 return self._loginResult is not None
6199
6200 @property
6201 def loginResult(self):
6202 """Get the login result."""
6203 return self._loginResult
6204
6205 @property
6206 def checkFlightAircraftType(self):
6207 """Get the type of the aircraft used to perform the check flight."""
6208 return self._studentPage.aircraftType
6209
6210 def setCurrentPage(self, index, finalize = False, fromPageShift = None):
6211 """Set the current page to the one with the given index.
6212
6213 @param fromPageShift if given, the relative index of one of the
6214 previous pages that should be used as the from-page of the next
6215 page. E.g. if fromPageShift is 1, the previous page will be the
6216 from-page."""
6217 assert index < len(self._pages)
6218
6219 fromPage = self._currentPage
6220 if fromPage is not None:
6221 page = self._pages[fromPage]
6222 if finalize and not page._completed:
6223 page.complete()
6224 page.prepareHide()
6225 self.remove(page)
6226 if fromPageShift is not None:
6227 fromPage -= fromPageShift
6228
6229 self._currentPage = index
6230 page = self._pages[index]
6231 self.add(page)
6232 if page._fromPage is None:
6233 page._fromPage = fromPage
6234 page.initialize()
6235 page.prepareShow()
6236 self.show_all()
6237 if fromPage is not None:
6238 self.grabDefault()
6239
6240 @property
6241 def bookedFlight(self):
6242 """Get the booked flight selected."""
6243 return self._bookedFlight
6244
6245 @property
6246 def numCockpitCrew(self):
6247 """Get the number of cockpit crew members."""
6248 return self._payloadPage.numCockpitCrew
6249
6250 @property
6251 def numCabinCrew(self):
6252 """Get the number of cabin crew members."""
6253 return self._payloadPage.numCabinCrew
6254
6255 @property
6256 def numPassengers(self):
6257 """Get the number of passengers."""
6258 return self._payloadPage.numPassengers
6259
6260 @property
6261 def numChildren(self):
6262 """Get the number of child passengers."""
6263 return self._payloadPage.numChildren
6264
6265 @property
6266 def numInfants(self):
6267 """Get the number of infant passengers."""
6268 return self._payloadPage.numInfants
6269
6270 @property
6271 def bagWeight(self):
6272 """Get the baggage weight."""
6273 return self._payloadPage.bagWeight
6274
6275 @property
6276 def cargoWeight(self):
6277 """Get the cargo weight."""
6278 return self._payloadPage.cargoWeight
6279
6280 @property
6281 def mailWeight(self):
6282 """Get the mail weight."""
6283 return self._payloadPage.mailWeight
6284
6285 @property
6286 def zfw(self):
6287 """Get the calculated ZFW value."""
6288 return 0 if self._bookedFlight is None \
6289 else self._payloadPage.calculateZFW()
6290
6291 @property
6292 def filedCruiseLevel(self):
6293 """Get the filed cruise level."""
6294 return self._routePage.filedCruiseLevel
6295
6296 @property
6297 def filedCruiseAltitude(self):
6298 """Get the filed cruise altitude."""
6299 return self._routePage.filedCruiseLevel * 100
6300
6301 @property
6302 def cruiseAltitude(self):
6303 """Get the cruise altitude."""
6304 level = self._cruisePage.cruiseLevel if self._cruisePage.activated \
6305 else self._routePage.filedCruiseLevel
6306 return level * 100
6307
6308 @property
6309 def loggableCruiseAltitude(self):
6310 """Get the cruise altitude that can be logged."""
6311 if self._cruisePage.activated:
6312 return self._cruisePage.loggableCruiseLevel * 100
6313 else:
6314 return 0
6315
6316 @property
6317 def route(self):
6318 """Get the route."""
6319 return self._routePage.route
6320
6321 @property
6322 def alternate(self):
6323 """Get the ICAO code of the alternate airport."""
6324 return self._routePage.alternate
6325
6326 @property
6327 def departureMETAR(self):
6328 """Get the METAR of the departure airport."""
6329 return self._departureBriefingPage.metar
6330
6331 @property
6332 def arrivalMETAR(self):
6333 """Get the METAR of the arrival airport."""
6334 return self._arrivalBriefingPage.metar
6335
6336 @property
6337 def departureRunway(self):
6338 """Get the departure runway."""
6339 return self._takeoffPage.runway
6340
6341 @property
6342 def sid(self):
6343 """Get the SID."""
6344 return self._takeoffPage.sid
6345
6346 @property
6347 def v1(self):
6348 """Get the V1 speed."""
6349 return self._takeoffPage.v1
6350
6351 @property
6352 def vr(self):
6353 """Get the Vr speed."""
6354 return self._takeoffPage.vr
6355
6356 @property
6357 def v2(self):
6358 """Get the V2 speed."""
6359 return self._takeoffPage.v2
6360
6361 @property
6362 def derate(self):
6363 """Get the derate value."""
6364 return self._takeoffPage.derate
6365
6366 @property
6367 def takeoffAntiIceOn(self):
6368 """Get whether the anti-ice system was on during take-off."""
6369 return self._takeoffPage.antiIceOn
6370
6371 @takeoffAntiIceOn.setter
6372 def takeoffAntiIceOn(self, value):
6373 """Set anti-ice on indicator."""
6374 self._takeoffPage.antiIceOn = value
6375
6376 @property
6377 def rtoIndicated(self):
6378 """Get whether the pilot has indicated that an RTO has occured."""
6379 return self._takeoffPage.rtoIndicated
6380
6381 @property
6382 def arrivalRunway(self):
6383 """Get the arrival runway."""
6384 return self._landingPage.runway
6385
6386 @property
6387 def star(self):
6388 """Get the STAR."""
6389 return self._landingPage.star
6390
6391 @property
6392 def transition(self):
6393 """Get the transition."""
6394 return self._landingPage.transition
6395
6396 @property
6397 def approachType(self):
6398 """Get the approach type."""
6399 return self._landingPage.approachType
6400
6401 @property
6402 def vref(self):
6403 """Get the Vref speed."""
6404 return self._landingPage.vref
6405
6406 @property
6407 def landingAntiIceOn(self):
6408 """Get whether the anti-ice system was on during landing."""
6409 return self._landingPage.antiIceOn
6410
6411 @landingAntiIceOn.setter
6412 def landingAntiIceOn(self, value):
6413 """Set anti-ice on indicator."""
6414 self._landingPage.antiIceOn = value
6415
6416 @property
6417 def flightType(self):
6418 """Get the flight type."""
6419 return self._finishPage.flightType
6420
6421 @property
6422 def online(self):
6423 """Get whether the flight was online or not."""
6424 return self._finishPage.online
6425
6426 @property
6427 def usingSimBrief(self):
6428 """Indicate if we are using a SimBrief briefing or not."""
6429 return self._usingSimBrief
6430
6431 @usingSimBrief.setter
6432 def usingSimBrief(self, x):
6433 """Set whether we are using a SimBrief briefing or not."""
6434 self._usingSimBrief = x
6435
6436 def nextPage(self, finalize = True):
6437 """Go to the next page."""
6438 nextPageID = self._pages[self._currentPage].nextPageID
6439 self.jumpPage(1 if nextPageID is None else nextPageID, finalize)
6440
6441 def jumpPage(self, countOrID, finalize = True, fromPageShift = None):
6442 """Go to the page which is 'count' pages after the current one."""
6443 if isinstance(countOrID, str):
6444 targetIndex = self._getIndexOf(countOrID)
6445 else:
6446 targetIndex = self._currentPage + countOrID
6447 self.setCurrentPage(targetIndex,
6448 finalize = finalize, fromPageShift = fromPageShift)
6449
6450 def grabDefault(self):
6451 """Make the default button of the current page the default."""
6452 self._pages[self._currentPage].grabDefault()
6453
6454 def connected(self, fsType, descriptor):
6455 """Called when the connection could be made to the simulator."""
6456 self.nextPage()
6457
6458 def reset(self, loginResult):
6459 """Resets the wizard to go back to the login page."""
6460 self._initialize(keepLoginResult = loginResult is None,
6461 loginResult = loginResult)
6462
6463 def setStage(self, stage):
6464 """Set the flight stage to the given one."""
6465 if stage!=const.STAGE_END:
6466 self._cruisePage.setLoggable(Flight.canLogCruiseAltitude(stage))
6467
6468 if stage==const.STAGE_TAKEOFF:
6469 self._takeoffPage.allowForward()
6470 elif stage==const.STAGE_LANDING:
6471 if not self._arrivalBriefingPage.metarEdited:
6472 print("Downloading arrival METAR again")
6473 self.gui.webHandler.getMETARs(self._arrivalMETARCallback,
6474 [self._bookedFlight.arrivalICAO])
6475
6476 elif stage==const.STAGE_END:
6477 for page in self._pages:
6478 page.flightEnded()
6479
6480 def _initialize(self, keepLoginResult = False, loginResult = None):
6481 """Initialize the wizard."""
6482 if not keepLoginResult:
6483 self._loginResult = loginResult
6484
6485 self._loginCallback = None
6486
6487 self._fleet = None
6488 self._fleetCallback = None
6489
6490 self._bookedFlight = None
6491 self._departureGate = "-"
6492 self._fuelData = None
6493 self._departureNOTAMs = None
6494 self._departureMETAR = None
6495 self._arrivalNOTAMs = None
6496 self._arrivalMETAR = None
6497 self._usingSimBrief = None
6498 self.takeoffRunway = None
6499 self.landingRunway = None
6500
6501 firstPage = 0 if self._loginResult is None else 1
6502 for page in self._pages[firstPage:]:
6503 page.reset()
6504
6505 self.setCurrentPage(firstPage)
6506 #self.setCurrentPage(10)
6507
6508 def login(self, callback, pilotID, password):
6509 """Called when the login button was clicked."""
6510 self._loginCallback = callback
6511 if pilotID is None:
6512 loginResult = self._loginResult
6513 assert loginResult is not None and loginResult.loggedIn
6514 pilotID = loginResult.pilotID
6515 password = loginResult.password
6516 busyMessage = xstr("reload_busy")
6517 else:
6518 self._loginResult = None
6519 busyMessage = xstr("login_busy")
6520
6521 self.gui.beginBusy(busyMessage)
6522
6523 self.gui.webHandler.login(self._loginResultCallback,
6524 pilotID, password)
6525
6526 def reloadFlights(self, callback):
6527 """Reload the flights from the MAVA server."""
6528 self.login(callback, None, None)
6529
6530 def addFlight(self, bookedFlight):
6531 """Add the given booked flight to the flight selection page."""
6532 self._flightSelectionPage.addFlight(bookedFlight)
6533
6534 def reflyFlight(self, bookedFlight):
6535 """Add the given booked flight to the flight selection page."""
6536 self._removePendingFlight(bookedFlight)
6537 self._flightSelectionPage._reflyFlight(bookedFlight)
6538
6539 def deleteFlight(self, bookedFlight):
6540 """Remove the given flight from the pending flight list."""
6541 self._removePendingFlight(bookedFlight)
6542 self._flightSelectionPage._updatePending()
6543
6544 def cancelFlight(self, reloadCallback):
6545 """Cancel the flight.
6546
6547 If it is an entry exam flight, we go back to the student page.
6548 Otherwise we reload the flights."""
6549 if self.entranceExam:
6550 self.reset(None)
6551 self.jumpPage("student")
6552 else:
6553 self.reloadFlights(reloadCallback)
6554
6555 def cruiseLevelChanged(self):
6556 """Called when the cruise level is changed."""
6557 return self.gui.cruiseLevelChanged()
6558
6559 def metarChanged(self, metar, originator):
6560 """Called when a METER is changed on on of the pages.
6561
6562 originator is the page that originated the changed. It will be used to
6563 determine which METAR (departure or arrival) has changed."""
6564 metar = metar.upper()
6565 if originator in [self._departureBriefingPage, self._takeoffPage]:
6566 self.departureMETARChanged(metar, originator)
6567 else:
6568 self.arrivalMETARChanged(metar, originator)
6569
6570 def departureMETARChanged(self, metar, originator):
6571 """Called when the departure METAR has been edited on one of the
6572 pages.
6573
6574 originator is the page that originated the change. It will not be
6575 called to set the METAR, while others will be."""
6576 for page in [self._departureBriefingPage, self._takeoffPage]:
6577 if page is not originator:
6578 page.changeMETAR(metar)
6579
6580 def arrivalMETARChanged(self, metar, originator):
6581 """Called when the arrival METAR has been edited on one of the
6582 pages.
6583
6584 originator is the page that originated the change. It will not be
6585 called to set the METAR, while others will be."""
6586 for page in [self._arrivalBriefingPage, self._landingPage]:
6587 if page is not originator:
6588 page.changeMETAR(metar)
6589
6590 def _removePendingFlight(self, flight):
6591 """Remove the given pending flight from the login result."""
6592 for flights in [self._loginResult.reportedFlights,
6593 self._loginResult.rejectedFlights]:
6594 for f in flights:
6595 if f.id==flight.id:
6596 flights.remove(f)
6597 return
6598
6599 def _loginResultCallback(self, returned, result):
6600 """The login result callback, called in the web handler's thread."""
6601 GObject.idle_add(self._handleLoginResult, returned, result)
6602
6603 def _handleLoginResult(self, returned, result):
6604 """Handle the login result."""
6605 self.gui.endBusy()
6606 isReload = self._loginResult is not None
6607 if returned:
6608 if result.loggedIn:
6609 self._loginResult = result
6610 self.gui.loginSuccessful()
6611 else:
6612 if isReload:
6613 message = xstr("reload_failed")
6614 else:
6615 message = xstr("login_entranceExam_invalid"
6616 if self.entranceExam else
6617 xstr("login_invalid"))
6618 dialog = Gtk.MessageDialog(parent = self.gui.mainWindow,
6619 type = Gtk.MessageType.ERROR,
6620 message_format = message)
6621 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
6622 dialog.set_title(WINDOW_TITLE_BASE)
6623 if isReload:
6624 secondary = xstr("reload_failed_sec")
6625 else:
6626 secondary = xstr("login_entranceExam_invalid_sec"
6627 if self.entranceExam else
6628 xstr("login_invalid_sec"))
6629 dialog.format_secondary_markup(secondary)
6630 dialog.run()
6631 dialog.hide()
6632 else:
6633 message = xstr("reload_failconn") if isReload \
6634 else xstr("login_failconn")
6635 dialog = Gtk.MessageDialog(parent = self.gui.mainWindow,
6636 type = Gtk.MessageType.ERROR,
6637 message_format = message)
6638 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
6639 dialog.set_title(WINDOW_TITLE_BASE)
6640 secondary = xstr("reload_failconn_sec") if isReload \
6641 else xstr("login_failconn_sec")
6642 dialog.format_secondary_markup(secondary)
6643
6644 dialog.run()
6645 dialog.hide()
6646
6647 callback = self._loginCallback
6648 self._loginCallback = None
6649 callback(returned, result)
6650
6651 def getFleet(self, callback, force = False):
6652 """Get the fleet via the GUI and call the given callback."""
6653 self._fleetCallback = callback
6654 self.gui.getFleet(callback = self._fleetRetrieved, force = force)
6655
6656 def _fleetRetrieved(self, fleet):
6657 """Callback for the fleet retrieval."""
6658 self._fleet = fleet
6659 if self._fleetCallback is not None:
6660 self._fleetCallback(fleet)
6661 self._fleetCallback = None
6662
6663 def updatePlane(self, callback, tailNumber, status, gateNumber = None):
6664 """Update the given plane's gate information."""
6665 self.gui.updatePlane(tailNumber, status, gateNumber = gateNumber,
6666 callback = callback)
6667
6668 def updateRTO(self):
6669 """Update the RTO state.
6670
6671 The RTO checkbox will be enabled if the flight has an RTO state and the
6672 comments field contains some text."""
6673 flight = self.gui.flight
6674 rtoEnabled = flight is not None and flight.hasRTO and \
6675 self.gui.hasComments
6676 self._takeoffPage.setRTOEnabled(rtoEnabled)
6677
6678 def commentsChanged(self):
6679 """Called when the comments have changed."""
6680 self.updateRTO()
6681 self._finishPage.updateButtons()
6682
6683 def delayCodesChanged(self):
6684 """Called when the delay codes have changed."""
6685 self._finishPage.updateButtons()
6686
6687 def faultExplanationsChanged(self):
6688 """Called when the faults and their explanations have changed."""
6689 self._finishPage.updateButtons()
6690
6691 def rtoToggled(self, indicated):
6692 """Called when the RTO indication has changed."""
6693 self.gui.rtoToggled(indicated)
6694
6695 def _connectSimulator(self, simulatorType):
6696 """Connect to the simulator."""
6697 self.gui.connectSimulator(self._bookedFlight, simulatorType)
6698
6699 def _arrivalMETARCallback(self, returned, result):
6700 """Called when the METAR of the arrival airport is retrieved."""
6701 GObject.idle_add(self._handleArrivalMETAR, returned, result)
6702
6703 def _handleArrivalMETAR(self, returned, result):
6704 """Called when the METAR of the arrival airport is retrieved."""
6705 icao = self._bookedFlight.arrivalICAO
6706 if returned and icao in result.metars:
6707 metar = result.metars[icao]
6708 if metar!="":
6709 self._arrivalBriefingPage.setMETAR(metar)
6710
6711 def _getIndexOf(self, pageID):
6712 """Get the index for the given page ID.
6713
6714 It is an assertion failure if the ID is not found."""
6715 for index in range(0, len(self._pages)):
6716 page = self._pages[index]
6717 if page.id==pageID:
6718 return index
6719 assert False
6720
6721#-----------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.