source: src/mlx/gui/flight.py@ 53:46b91acfddfc

Last change on this file since 53:46b91acfddfc was 51:f0f99ac21935, checked in by István Váradi <ivaradi@…>, 13 years ago

Fleet retrieval and gate selection works, started new connection handling and the fsuipc simulator

File size: 25.2 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
406 def _planeUpdated(self, success):
407 """Callback for the plane updating call."""
408 if success is None or success:
409 self._wizard.nextPage()
410 else:
411 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
412 buttons = BUTTONSTYPE_OK,
413 message_format = "Gate conflict detected again")
414 dialog.format_secondary_markup("Try to select a different gate.")
415 dialog.run()
416 dialog.hide()
417
418 self._wizard._getFleet(self._fleetRetrieved)
419
420 def _fleetRetrieved(self, fleet):
421 """Called when the fleet has been retrieved."""
422 if fleet is None:
423 self._wizard.nextPage()
424 else:
425 self.activate()
426
427#-----------------------------------------------------------------------------
428
429class ConnectPage(Page):
430 """Page which displays the departure airport and gate (if at LHBP)."""
431 def __init__(self, wizard):
432 """Construct the connect page."""
433 help = "The flight begins at the airport given below.\n" \
434 "Park your aircraft there, at the gate below, if given.\n\n" \
435 "Then press the Connect button to connect to the simulator."
436 super(ConnectPage, self).__init__(wizard,
437 "Connect to the simulator",
438 help)
439
440 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
441 xscale = 0.0, yscale = 0.0)
442
443 table = gtk.Table(2, 2)
444 table.set_row_spacings(4)
445 table.set_col_spacings(16)
446 table.set_homogeneous(True)
447 alignment.add(table)
448 self.setMainWidget(alignment)
449
450 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
451 label = gtk.Label("ICAO code:")
452 labelAlignment.add(label)
453 table.attach(labelAlignment, 0, 1, 0, 1)
454
455 labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
456 self._departureICAO = gtk.Label()
457 self._departureICAO.set_width_chars(5)
458 self._departureICAO.set_alignment(0.0, 0.5)
459 labelAlignment.add(self._departureICAO)
460 table.attach(labelAlignment, 1, 2, 0, 1)
461
462 labelAlignment = gtk.Alignment(xalign=1.0, xscale=0.0)
463 label = gtk.Label("Gate:")
464 label.set_use_underline(True)
465 labelAlignment.add(label)
466 table.attach(labelAlignment, 0, 1, 1, 2)
467
468 labelAlignment = gtk.Alignment(xalign=0.0, xscale=0.0)
469 self._departureGate = gtk.Label()
470 self._departureGate.set_width_chars(5)
471 self._departureGate.set_alignment(0.0, 0.5)
472 labelAlignment.add(self._departureGate)
473 table.attach(labelAlignment, 1, 2, 1, 2)
474
475
476 self._button = self.addButton("_Connect", default = True)
477 self._button.set_use_underline(True)
478 self._button.connect("clicked", self._connectClicked)
479
480 def activate(self):
481 """Setup the deprature information."""
482 icao = self._wizard._bookedFlight.departureICAO
483 self._departureICAO.set_markup("<b>" + icao + "</b>")
484 gate = self._wizard._departureGate
485 if gate!="-":
486 gate = "<b>" + gate + "</b>"
487 self._departureGate.set_markup(gate)
488
489 def _connectClicked(self, button):
490 """Called when the Connect button is pressed."""
491 self._wizard._connectSimulator()
492
493#-----------------------------------------------------------------------------
494
495class Wizard(gtk.VBox):
496 """The flight wizard."""
497 def __init__(self, gui):
498 """Construct the wizard."""
499 super(Wizard, self).__init__()
500
501 self.gui = gui
502
503 self._pages = []
504 self._currentPage = None
505
506 self._pages.append(LoginPage(self))
507 self._pages.append(FlightSelectionPage(self))
508 self._pages.append(GateSelectionPage(self))
509 self._pages.append(ConnectPage(self))
510
511 maxWidth = 0
512 maxHeight = 0
513 for page in self._pages:
514 page.show_all()
515 pageSizeRequest = page.size_request()
516 width = pageSizeRequest.width if pygobject else pageSizeRequest[0]
517 height = pageSizeRequest.height if pygobject else pageSizeRequest[1]
518 maxWidth = max(maxWidth, width)
519 maxHeight = max(maxHeight, height)
520 maxWidth += 16
521 maxHeight += 32
522 self.set_size_request(maxWidth, maxHeight)
523
524 self._fleet = None
525 self._fleetCallback = None
526 self._updatePlaneCallback = None
527
528 self._loginResult = None
529 self._bookedFlight = None
530 self._departureGate = "-"
531
532 self._logger = Logger(output = gui)
533 self._flight = None
534 self._simulator = None
535
536 self.setCurrentPage(0)
537
538 @property
539 def loginResult(self):
540 """Get the login result."""
541 return self._loginResult
542
543 def setCurrentPage(self, index):
544 """Set the current page to the one with the given index."""
545 assert index < len(self._pages)
546
547 if self._currentPage is not None:
548 self.remove(self._pages[self._currentPage])
549
550 self._currentPage = index
551 self.add(self._pages[index])
552 self._pages[index].activate()
553 self.show_all()
554
555 def nextPage(self):
556 """Go to the next page."""
557 self.jumpPage(1)
558
559 def jumpPage(self, count):
560 """Go to the page which is 'count' pages after the current one."""
561 self.setCurrentPage(self._currentPage + count)
562 self.grabDefault()
563
564 def grabDefault(self):
565 """Make the default button of the current page the default."""
566 self._pages[self._currentPage].grabDefault()
567
568 def _getFleet(self, callback, force = False):
569 """Get the fleet, if needed.
570
571 callback is function that will be called, when the feet is retrieved,
572 or the retrieval fails. It should have a single argument that will
573 receive the fleet object on success, None otherwise.
574 """
575 if self._fleet is not None and not force:
576 callback(self._fleet)
577
578 self.gui.beginBusy("Retrieving fleet...")
579 self._fleetCallback = callback
580 self.gui.webHandler.getFleet(self._fleetResultCallback)
581
582 def _fleetResultCallback(self, returned, result):
583 """Called when the fleet has been queried."""
584 gobject.idle_add(self._handleFleetResult, returned, result)
585
586 def _handleFleetResult(self, returned, result):
587 """Handle the fleet result."""
588 self.gui.endBusy()
589 if returned:
590 self._fleet = result.fleet
591 else:
592 self._fleet = None
593
594 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
595 buttons = BUTTONSTYPE_OK,
596 message_format =
597 "Failed to retrieve the information on "
598 "the fleet.")
599 dialog.run()
600 dialog.hide()
601
602 self._fleetCallback(self._fleet)
603
604 def _updatePlane(self, callback, tailNumber, status, gateNumber = None):
605 """Update the given plane's gate information."""
606 self.gui.beginBusy("Updating plane status...")
607 self._updatePlaneCallback = callback
608 self.gui.webHandler.updatePlane(self._updatePlaneResultCallback,
609 tailNumber, status, gateNumber)
610
611 def _updatePlaneResultCallback(self, returned, result):
612 """Callback for the plane updating operation."""
613 gobject.idle_add(self._handleUpdatePlaneResult, returned, result)
614
615 def _handleUpdatePlaneResult(self, returned, result):
616 """Handle the result of a plane update operation."""
617 self.gui.endBusy()
618 if returned:
619 success = result.success
620 else:
621 success = None
622
623 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
624 buttons = BUTTONSTYPE_OK,
625 message_format =
626 "Failed to update the statuis of "
627 "the airplane.")
628 dialog.run()
629 dialog.hide()
630
631 self._updatePlaneCallback(success)
632
633 def _connectSimulator(self):
634 """Connect to the simulator."""
635 self._logger.reset()
636 self._flight = Flight(self.gui._logger, self.gui)
637
638 self._flight.aircraftType = self._bookedFlight.aircraftType
639 aircraft = self._flight.aircraft = Aircraft.create(self._flight)
640 self._flight.aircraft._checkers.append(self.gui)
641
642 self._flight.cruiseAltitude = -1
643 self._flight.zfw = -1
644
645 if self._simulator is None:
646 self._simulator = fs.createSimulator(const.SIM_MSFS9, self.gui)
647
648 self._flight.simulator = self._simulator
649 self._simulator.connect(aircraft)
650 #self._simulator.startMonitoring()
651
652#-----------------------------------------------------------------------------
653
Note: See TracBrowser for help on using the repository browser.