source: src/mlx/gui/flight.py@ 59:a3e0b8455dc8

Last change on this file since 59:a3e0b8455dc8 was 59:a3e0b8455dc8, checked in by István Váradi <ivaradi@…>, 12 years ago

Implemented better connection and connection failure handling.

File size: 26.0 KB
Line 
1# The flight handling "wizard"
2
3from mlx.gui.common import *
4
5import mlx.const as const
6import mlx.fs as fs
7from mlx.logger import Logger
8from mlx.flight import Flight
9from mlx.acft import Aircraft
10
11#-----------------------------------------------------------------------------
12
13class Page(gtk.Alignment):
14 """A page in the flight wizard."""
15 def __init__(self, wizard, title, help):
16 """Construct the page."""
17 super(Page, self).__init__(xalign = 0.0, yalign = 0.0,
18 xscale = 1.0, yscale = 1.0)
19 self.set_padding(padding_top = 4, padding_bottom = 4,
20 padding_left = 12, padding_right = 12)
21
22 frame = gtk.Frame()
23 self.add(frame)
24
25 style = self.get_style() if pygobject else self.rc_get_style()
26
27 self._vbox = gtk.VBox()
28 self._vbox.set_homogeneous(False)
29 frame.add(self._vbox)
30
31 eventBox = gtk.EventBox()
32 eventBox.modify_bg(0, style.bg[3])
33
34 alignment = gtk.Alignment(xalign = 0.0, xscale = 0.0)
35
36 label = gtk.Label(title)
37 label.modify_fg(0, style.fg[3])
38 label.modify_font(pango.FontDescription("bold 24"))
39 alignment.set_padding(padding_top = 4, padding_bottom = 4,
40 padding_left = 6, padding_right = 0)
41
42 alignment.add(label)
43 eventBox.add(alignment)
44
45 self._vbox.pack_start(eventBox, False, False, 0)
46
47 table = gtk.Table(3, 1)
48 table.set_homogeneous(True)
49
50 alignment = gtk.Alignment(xalign = 0.0, yalign = 0.0,
51 xscale = 1.0, yscale = 1.0)
52 alignment.set_padding(padding_top = 16, padding_bottom = 16,
53 padding_left = 16, padding_right = 16)
54 alignment.add(table)
55 self._vbox.pack_start(alignment, True, True, 0)
56
57 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0,
58 xscale = 0, yscale = 0.0)
59 alignment.set_padding(padding_top = 0, padding_bottom = 16,
60 padding_left = 0, padding_right = 0)
61
62 label = gtk.Label(help)
63 label.set_justify(gtk.Justification.CENTER if pygobject
64 else gtk.JUSTIFY_CENTER)
65 alignment.add(label)
66 table.attach(alignment, 0, 1, 0, 1)
67
68 self._mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
69 xscale = 1.0, yscale = 1.0)
70 table.attach(self._mainAlignment, 0, 1, 1, 3)
71
72 buttonAlignment = gtk.Alignment(xalign = 1.0, xscale=0.0, yscale = 0.0)
73 buttonAlignment.set_padding(padding_top = 4, padding_bottom = 10,
74 padding_left = 16, padding_right = 16)
75
76 self._buttonBox = gtk.HButtonBox()
77 self._defaultButton = None
78 buttonAlignment.add(self._buttonBox)
79
80 self._vbox.pack_start(buttonAlignment, False, False, 0)
81
82 self._wizard = wizard
83
84 def setMainWidget(self, widget):
85 """Set the given widget as the main one."""
86 self._mainAlignment.add(widget)
87
88 def addButton(self, label, default = False):
89 """Add a button with the given label.
90
91 Return the button object created."""
92 button = gtk.Button(label)
93 self._buttonBox.add(button)
94 button.set_use_underline(True)
95 if default:
96 button.set_can_default(True)
97 self._defaultButton = button
98 return button
99
100 def activate(self):
101 """Called when this page becomes active.
102
103 This default implementation does nothing."""
104 pass
105
106 def grabDefault(self):
107 """If the page has a default button, make it the default one."""
108 if self._defaultButton is not None:
109 self._defaultButton.grab_default()
110
111#-----------------------------------------------------------------------------
112
113class LoginPage(Page):
114 """The login page."""
115 def __init__(self, wizard):
116 """Construct the login page."""
117 help = "Enter your MAVA pilot's ID and password to\n" \
118 "log in to the MAVA website and download\n" \
119 "your booked flights."
120 super(LoginPage, self).__init__(wizard, "Login", help)
121
122 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
123 xscale = 0.0, yscale = 0.0)
124
125 table = gtk.Table(2, 3)
126 table.set_row_spacings(4)
127 table.set_col_spacings(32)
128 alignment.add(table)
129 self.setMainWidget(alignment)
130
131 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
132 label = gtk.Label("Pilot _ID:")
133 label.set_use_underline(True)
134 labelAlignment.add(label)
135 table.attach(labelAlignment, 0, 1, 0, 1)
136
137 self._pilotID = gtk.Entry()
138 self._pilotID.connect("changed", self._setLoginButton)
139 self._pilotID.set_tooltip_text("Enter your MAVA pilot's ID. This "
140 "usually starts with a "
141 "'P' followed by 3 digits.")
142 table.attach(self._pilotID, 1, 2, 0, 1)
143 label.set_mnemonic_widget(self._pilotID)
144
145 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
146 label = gtk.Label("_Password:")
147 label.set_use_underline(True)
148 labelAlignment.add(label)
149 table.attach(labelAlignment, 0, 1, 1, 2)
150
151 self._password = gtk.Entry()
152 self._password.set_visibility(False)
153 self._password.connect("changed", self._setLoginButton)
154 self._password.set_tooltip_text("Enter the password for your pilot's ID")
155 table.attach(self._password, 1, 2, 1, 2)
156 label.set_mnemonic_widget(self._password)
157
158 self._rememberButton = gtk.CheckButton("_Remember password")
159 self._rememberButton.set_use_underline(True)
160 self._rememberButton.set_tooltip_text("If checked, your password will "
161 "be stored, so that you should "
162 "not have to enter it every time. "
163 "Note, however, that the password "
164 "is stored as text, and anybody "
165 "who can access your files will "
166 "be able to read it.")
167 table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
168
169 self._loginButton = self.addButton("_Login", default = True)
170 self._loginButton.set_sensitive(False)
171 self._loginButton.connect("clicked", self._loginClicked)
172 self._loginButton.set_tooltip_text("Click to log in.")
173
174 config = self._wizard.gui.config
175 self._pilotID.set_text(config.pilotID)
176 self._password.set_text(config.password)
177 self._rememberButton.set_active(config.rememberPassword)
178
179 def _setLoginButton(self, entry):
180 """Set the login button's sensitivity.
181
182 The button is sensitive only if both the pilot ID and the password
183 fields contain values."""
184 self._loginButton.set_sensitive(self._pilotID.get_text()!="" and
185 self._password.get_text()!="")
186
187 def _loginClicked(self, button):
188 """Called when the login button was clicked."""
189 self._wizard.gui.beginBusy("Logging in...")
190 self._wizard.gui.webHandler.login(self._loginResultCallback,
191 self._pilotID.get_text(),
192 self._password.get_text())
193
194 def _loginResultCallback(self, returned, result):
195 """The login result callback, called in the web handler's thread."""
196 gobject.idle_add(self._handleLoginResult, returned, result)
197
198 def _handleLoginResult(self, returned, result):
199 """Handle the login result."""
200 self._wizard.gui.endBusy()
201 if returned:
202 if result.loggedIn:
203 config = self._wizard.gui.config
204
205 config.pilotID = self._pilotID.get_text()
206
207 rememberPassword = self._rememberButton.get_active()
208 config.password = self._password.get_text() if rememberPassword \
209 else ""
210
211 config.rememberPassword = rememberPassword
212
213 config.save()
214 self._wizard._loginResult = result
215 self._wizard.nextPage()
216 else:
217 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
218 buttons = BUTTONSTYPE_OK,
219 message_format =
220 "Invalid pilot's ID or password.")
221 dialog.format_secondary_markup("Check the ID and try to reenter"
222 " the password.")
223 dialog.run()
224 dialog.hide()
225 else:
226 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
227 buttons = BUTTONSTYPE_OK,
228 message_format =
229 "Failed to connect to the MAVA website.")
230 dialog.format_secondary_markup("Try again in a few minutes.")
231 dialog.run()
232 dialog.hide()
233
234#-----------------------------------------------------------------------------
235
236class FlightSelectionPage(Page):
237 """The page to select the flight."""
238 def __init__(self, wizard):
239 """Construct the flight selection page."""
240 super(FlightSelectionPage, self).__init__(wizard, "Flight selection",
241 "Select the flight you want "
242 "to perform.")
243
244
245 self._listStore = gtk.ListStore(str, str, str, str)
246 self._flightList = gtk.TreeView(self._listStore)
247 column = gtk.TreeViewColumn("Flight no.", gtk.CellRendererText(),
248 text = 1)
249 column.set_expand(True)
250 self._flightList.append_column(column)
251 column = gtk.TreeViewColumn("Departure time [UTC]", gtk.CellRendererText(),
252 text = 0)
253 column.set_expand(True)
254 self._flightList.append_column(column)
255 column = gtk.TreeViewColumn("From", gtk.CellRendererText(),
256 text = 2)
257 column.set_expand(True)
258 self._flightList.append_column(column)
259 column = gtk.TreeViewColumn("To", gtk.CellRendererText(),
260 text = 3)
261 column.set_expand(True)
262 self._flightList.append_column(column)
263
264 flightSelection = self._flightList.get_selection()
265 flightSelection.connect("changed", self._selectionChanged)
266
267 scrolledWindow = gtk.ScrolledWindow()
268 scrolledWindow.add(self._flightList)
269 scrolledWindow.set_size_request(400, -1)
270 scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
271 else gtk.POLICY_AUTOMATIC,
272 gtk.PolicyType.ALWAYS if pygobject
273 else gtk.POLICY_ALWAYS)
274
275 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
276 alignment.add(scrolledWindow)
277
278 self.setMainWidget(alignment)
279
280 self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
281 self._button.set_use_stock(True)
282 self._button.set_sensitive(False)
283 self._button.connect("clicked", self._forwardClicked)
284
285 self._activated = False
286
287 def activate(self):
288 """Fill the flight list."""
289 if not self._activated:
290 for flight in self._wizard.loginResult.flights:
291 self._listStore.append([str(flight.departureTime),
292 flight.callsign,
293 flight.departureICAO,
294 flight.arrivalICAO])
295 self._activated = True
296
297 def _selectionChanged(self, selection):
298 """Called when the selection is changed."""
299 self._button.set_sensitive(selection.count_selected_rows()==1)
300
301 def _forwardClicked(self, button):
302 """Called when the forward button was clicked."""
303 selection = self._flightList.get_selection()
304 (listStore, iter) = selection.get_selected()
305 path = listStore.get_path(iter)
306 [index] = path.get_indices() if pygobject else path
307
308 flight = self._wizard.loginResult.flights[index]
309 self._wizard._bookedFlight = flight
310
311 self._updateDepartureGate()
312
313 def _updateDepartureGate(self):
314 """Update the departure gate for the booked flight."""
315 flight = self._wizard._bookedFlight
316 if flight.departureICAO=="LHBP":
317 self._wizard._getFleet(self._fleetRetrieved)
318 else:
319 self._wizard.jumpPage(2)
320
321 def _fleetRetrieved(self, fleet):
322 """Called when the fleet has been retrieved."""
323 if fleet is None:
324 self._wizard.jumpPage(2)
325 else:
326 plane = fleet[self._wizard._bookedFlight.tailNumber]
327 if plane is None:
328 self._wizard.jumpPage(2)
329
330 if plane.gateNumber is not None and \
331 not fleet.isGateConflicting(plane):
332 self._wizard._departureGate = plane.gateNumber
333 self._wizard.jumpPage(2)
334 else:
335 self._wizard.nextPage()
336
337#-----------------------------------------------------------------------------
338
339class GateSelectionPage(Page):
340 """Page to select a free gate at LHBP.
341
342 This page should be displayed only if we have fleet information!."""
343 def __init__(self, wizard):
344 """Construct the gate selection page."""
345 help = "The airplane's gate position is invalid.\n\n" \
346 "Select the gate from which you\n" \
347 "would like to begin the flight."
348 super(GateSelectionPage, self).__init__(wizard,
349 "LHBP gate selection",
350 help)
351
352 self._listStore = gtk.ListStore(str)
353 self._gateList = gtk.TreeView(self._listStore)
354 column = gtk.TreeViewColumn(None, gtk.CellRendererText(),
355 text = 0)
356 column.set_expand(True)
357 self._gateList.append_column(column)
358 self._gateList.set_headers_visible(False)
359
360 gateSelection = self._gateList.get_selection()
361 gateSelection.connect("changed", self._selectionChanged)
362
363 scrolledWindow = gtk.ScrolledWindow()
364 scrolledWindow.add(self._gateList)
365 scrolledWindow.set_size_request(50, -1)
366 scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
367 else gtk.POLICY_AUTOMATIC,
368 gtk.PolicyType.ALWAYS if pygobject
369 else gtk.POLICY_ALWAYS)
370
371 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
372 alignment.add(scrolledWindow)
373
374 self.setMainWidget(alignment)
375
376 self._button = self.addButton(gtk.STOCK_GO_FORWARD, default = True)
377 self._button.set_use_stock(True)
378 self._button.set_sensitive(False)
379 self._button.connect("clicked", self._forwardClicked)
380
381 def activate(self):
382 """Fill the gate list."""
383 self._listStore.clear()
384 occupiedGateNumbers = self._wizard._fleet.getOccupiedGateNumbers()
385 for gateNumber in const.lhbpGateNumbers:
386 if gateNumber not in occupiedGateNumbers:
387 self._listStore.append([gateNumber])
388
389 def _selectionChanged(self, selection):
390 """Called when the selection is changed."""
391 self._button.set_sensitive(selection.count_selected_rows()==1)
392
393 def _forwardClicked(self, button):
394 """Called when the forward button is clicked."""
395 selection = self._gateList.get_selection()
396 (listStore, iter) = selection.get_selected()
397 (gateNumber,) = listStore.get(iter, 0)
398
399 self._wizard._departureGate = gateNumber
400
401 #self._wizard._updatePlane(self._planeUpdated,
402 # self._wizard._bookedFlight.tailNumber,
403 # const.PLANE_HOME,
404 # gateNumber)
405 self._wizard.nextPage()
406
407 def _planeUpdated(self, success):
408 """Callback for the plane updating call."""
409 if success is None or success:
410 self._wizard.nextPage()
411 else:
412 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
413 buttons = BUTTONSTYPE_OK,
414 message_format = "Gate conflict detected again")
415 dialog.format_secondary_markup("Try to select a different gate.")
416 dialog.run()
417 dialog.hide()
418
419 self._wizard._getFleet(self._fleetRetrieved)
420
421 def _fleetRetrieved(self, fleet):
422 """Called when the fleet has been retrieved."""
423 if fleet is None:
424 self._wizard.nextPage()
425 else:
426 self.activate()
427
428#-----------------------------------------------------------------------------
429
430class ConnectPage(Page):
431 """Page which displays the departure airport and gate (if at LHBP)."""
432 def __init__(self, wizard):
433 """Construct the connect page."""
434 help = "The flight begins at the airport given below.\n" \
435 "Park your aircraft there, at the gate below, if given.\n\n" \
436 "Then press the Connect button to connect to the simulator."
437 super(ConnectPage, self).__init__(wizard,
438 "Connect to the simulator",
439 help)
440
441 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
442 xscale = 0.0, yscale = 0.0)
443
444 table = gtk.Table(2, 2)
445 table.set_row_spacings(4)
446 table.set_col_spacings(16)
447 table.set_homogeneous(True)
448 alignment.add(table)
449 self.setMainWidget(alignment)
450
451 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
452 label = gtk.Label("ICAO code:")
453 labelAlignment.add(label)
454 table.attach(labelAlignment, 0, 1, 0, 1)
455
456 labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
457 self._departureICAO = gtk.Label()
458 self._departureICAO.set_width_chars(5)
459 self._departureICAO.set_alignment(0.0, 0.5)
460 labelAlignment.add(self._departureICAO)
461 table.attach(labelAlignment, 1, 2, 0, 1)
462
463 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
464 label = gtk.Label("Gate:")
465 label.set_use_underline(True)
466 labelAlignment.add(label)
467 table.attach(labelAlignment, 0, 1, 1, 2)
468
469 labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
470 self._departureGate = gtk.Label()
471 self._departureGate.set_width_chars(5)
472 self._departureGate.set_alignment(0.0, 0.5)
473 labelAlignment.add(self._departureGate)
474 table.attach(labelAlignment, 1, 2, 1, 2)
475
476
477 self._button = self.addButton("_Connect", default = True)
478 self._button.set_use_underline(True)
479 self._button.connect("clicked", self._connectClicked)
480
481 def activate(self):
482 """Setup the deprature information."""
483 icao = self._wizard._bookedFlight.departureICAO
484 self._departureICAO.set_markup("<b>" + icao + "</b>")
485 gate = self._wizard._departureGate
486 if gate!="-":
487 gate = "<b>" + gate + "</b>"
488 self._departureGate.set_markup(gate)
489
490 def _connectClicked(self, button):
491 """Called when the Connect button is pressed."""
492 self._wizard._connectSimulator()
493
494#-----------------------------------------------------------------------------
495
496class PayloadPage(Page):
497 """Page to allow setting up the payload."""
498 def __init__(self, wizard):
499 """Construct the page."""
500 help = "The briefing contains the weights below.\n" \
501 "Setup the cargo weight and check if the simulator\n" \
502 "reports the expected Zero Fuel Weight."
503 super(PayloadPage, self).__init__(wizard, "Payload", help)
504
505 button = gtk.Button("_Query ZFW")
506 button.connect("clicked", self._zfwRequested)
507 self.setMainWidget(button)
508
509 def _zfwRequested(self, button):
510 """Called when the ZFW is requested from the simulator."""
511 self._wizard.gui.simulator.requestZFW(self._handleZFW)
512
513 def _handleZFW(self, zfw):
514 """Called when the ZFW value is retrieved."""
515 print "ZFW", zfw
516
517#-----------------------------------------------------------------------------
518
519class Wizard(gtk.VBox):
520 """The flight wizard."""
521 def __init__(self, gui):
522 """Construct the wizard."""
523 super(Wizard, self).__init__()
524
525 self.gui = gui
526
527 self._pages = []
528 self._currentPage = None
529
530 self._pages.append(LoginPage(self))
531 self._pages.append(FlightSelectionPage(self))
532 self._pages.append(GateSelectionPage(self))
533 self._pages.append(ConnectPage(self))
534 self._pages.append(PayloadPage(self))
535
536 maxWidth = 0
537 maxHeight = 0
538 for page in self._pages:
539 page.show_all()
540 pageSizeRequest = page.size_request()
541 width = pageSizeRequest.width if pygobject else pageSizeRequest[0]
542 height = pageSizeRequest.height if pygobject else pageSizeRequest[1]
543 maxWidth = max(maxWidth, width)
544 maxHeight = max(maxHeight, height)
545 maxWidth += 16
546 maxHeight += 32
547 self.set_size_request(maxWidth, maxHeight)
548
549 self._initialize()
550
551 @property
552 def loginResult(self):
553 """Get the login result."""
554 return self._loginResult
555
556 def setCurrentPage(self, index):
557 """Set the current page to the one with the given index."""
558 assert index < len(self._pages)
559
560 if self._currentPage is not None:
561 self.remove(self._pages[self._currentPage])
562
563 self._currentPage = index
564 self.add(self._pages[index])
565 self._pages[index].activate()
566 self.show_all()
567
568 def nextPage(self):
569 """Go to the next page."""
570 self.jumpPage(1)
571
572 def jumpPage(self, count):
573 """Go to the page which is 'count' pages after the current one."""
574 self.setCurrentPage(self._currentPage + count)
575 self.grabDefault()
576
577 def grabDefault(self):
578 """Make the default button of the current page the default."""
579 self._pages[self._currentPage].grabDefault()
580
581 def connected(self, fsType, descriptor):
582 """Called when the connection could be made to the simulator."""
583 self.nextPage()
584
585 def connectionFailed(self):
586 """Called when the connection could not be made to the simulator."""
587 self._initialize()
588
589 def disconnected(self):
590 """Called when we have disconnected from the simulator."""
591 self._initialize()
592
593 def _initialize(self):
594 """Initialize the wizard."""
595 self._fleet = None
596 self._fleetCallback = None
597 self._updatePlaneCallback = None
598
599 self._loginResult = None
600 self._bookedFlight = None
601 self._departureGate = "-"
602
603 self.setCurrentPage(0)
604
605 def _getFleet(self, callback, force = False):
606 """Get the fleet, if needed.
607
608 callback is function that will be called, when the feet is retrieved,
609 or the retrieval fails. It should have a single argument that will
610 receive the fleet object on success, None otherwise.
611 """
612 if self._fleet is not None and not force:
613 callback(self._fleet)
614
615 self.gui.beginBusy("Retrieving fleet...")
616 self._fleetCallback = callback
617 self.gui.webHandler.getFleet(self._fleetResultCallback)
618
619 def _fleetResultCallback(self, returned, result):
620 """Called when the fleet has been queried."""
621 gobject.idle_add(self._handleFleetResult, returned, result)
622
623 def _handleFleetResult(self, returned, result):
624 """Handle the fleet result."""
625 self.gui.endBusy()
626 if returned:
627 self._fleet = result.fleet
628 else:
629 self._fleet = None
630
631 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
632 buttons = BUTTONSTYPE_OK,
633 message_format =
634 "Failed to retrieve the information on "
635 "the fleet.")
636 dialog.run()
637 dialog.hide()
638
639 self._fleetCallback(self._fleet)
640
641 def _updatePlane(self, callback, tailNumber, status, gateNumber = None):
642 """Update the given plane's gate information."""
643 self.gui.beginBusy("Updating plane status...")
644 self._updatePlaneCallback = callback
645 self.gui.webHandler.updatePlane(self._updatePlaneResultCallback,
646 tailNumber, status, gateNumber)
647
648 def _updatePlaneResultCallback(self, returned, result):
649 """Callback for the plane updating operation."""
650 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
651
652 def _handleUpdatePlaneResult(self, returned, result):
653 """Handle the result of a plane update operation."""
654 self.gui.endBusy()
655 if returned:
656 success = result.success
657 else:
658 success = None
659
660 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
661 buttons = BUTTONSTYPE_OK,
662 message_format =
663 "Failed to update the statuis of "
664 "the airplane.")
665 dialog.run()
666 dialog.hide()
667
668 self._updatePlaneCallback(success)
669
670 def _connectSimulator(self):
671 """Connect to the simulator."""
672 self.gui.connectSimulator(self._bookedFlight.aircraftType)
673
674#-----------------------------------------------------------------------------
675
Note: See TracBrowser for help on using the repository browser.