source: src/mlx/gui/flight.py@ 1109:986abbce32ef

python3
Last change on this file since 1109:986abbce32ef was 1106:27adf311dd37, checked in by István Váradi <ivaradi@…>, 14 months ago

The flight contains if its departure gate is taxi-through

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