source: src/mlx/gui/flightlist.py@ 850:daa89a700477

Last change on this file since 850:daa89a700477 was 830:d0e4741cda76, checked in by István Váradi <ivaradi@…>, 8 years ago

A PIREP for a pending flight can be viewed (re #307)

File size: 18.3 KB
Line 
1# A widget which is a generic list of flights
2
3#-----------------------------------------------------------------------------
4
5from mlx.gui.common import *
6
7import mlx.const as const
8
9#-----------------------------------------------------------------------------
10
11class ColumnDescriptor(object):
12 """A descriptor for a column in the list."""
13 def __init__(self, attribute, heading, type = str,
14 convertFn = None, renderer = gtk.CellRendererText(),
15 extraColumnAttributes = None, sortable = False,
16 defaultSortable = False):
17 """Construct the descriptor."""
18 self._attribute = attribute
19 self._heading = heading
20 self._type = type
21 self._convertFn = convertFn
22 self._renderer = renderer
23 self._extraColumnAttributes = extraColumnAttributes
24 self._sortable = sortable
25 self._defaultSortable = defaultSortable
26
27 @property
28 def defaultSortable(self):
29 """Determine if this column is the default sortable one."""
30 return self._defaultSortable
31
32 def appendType(self, types):
33 """Append the type of this column to the given list of types."""
34 types.append(self._type)
35
36 def getViewColumn(self, index):
37 """Get a new column object for a tree view.
38
39 @param index is the 0-based index of the column."""
40 if self._extraColumnAttributes is None:
41 if isinstance(self._renderer, gtk.CellRendererText):
42 extraColumnAttributes = {"text" : index}
43 else:
44 extraColumnAttributes = {}
45 else:
46 extraColumnAttributes = self._extraColumnAttributes
47
48 column = gtk.TreeViewColumn(self._heading, self._renderer,
49 text = index)
50 column.set_expand(True)
51 if self._sortable:
52 column.set_sort_column_id(index)
53 column.set_sort_indicator(True)
54
55 return column
56
57 def getValueFrom(self, flight):
58 """Get the value from the given flight."""
59 value = getattr(flight, self._attribute)
60 return self._type(value) if self._convertFn is None \
61 else self._convertFn(value, flight)
62
63#-----------------------------------------------------------------------------
64
65class FlightList(gtk.Alignment):
66 """Construct the flight list.
67
68 This is a complete widget with a scroll window. It is alignment centered
69 horizontally and expandable vertically."""
70
71 defaultColumnDescriptors = [
72 ColumnDescriptor("callsign", xstr("flightsel_no")),
73 ColumnDescriptor("departureTime", xstr("flightsel_deptime"),
74 sortable = True, defaultSortable = True),
75 ColumnDescriptor("departureICAO", xstr("flightsel_from"),
76 sortable = True),
77 ColumnDescriptor("arrivalICAO", xstr("flightsel_to"), sortable = True)
78 ]
79
80 def __init__(self, columnDescriptors = defaultColumnDescriptors,
81 popupMenuProducer = None, widthRequest = None,
82 multiSelection = False):
83 """Construct the flight list with the given column descriptors."""
84
85 self._columnDescriptors = columnDescriptors
86 self._popupMenuProducer = popupMenuProducer
87 self._popupMenu = None
88
89 types = [int]
90 defaultSortableIndex = None
91 for columnDescriptor in self._columnDescriptors:
92 if columnDescriptor.defaultSortable:
93 defaultSortableIndex = len(types)
94 columnDescriptor.appendType(types)
95
96 self._model = gtk.ListStore(*types)
97 if defaultSortableIndex is not None:
98 self._model.set_sort_column_id(defaultSortableIndex,
99 SORT_ASCENDING)
100 self._view = gtk.TreeView(self._model)
101
102 flightIndexColumn = gtk.TreeViewColumn()
103 flightIndexColumn.set_visible(False)
104 self._view.append_column(flightIndexColumn)
105
106 index = 1
107 for columnDescriptor in self._columnDescriptors:
108 column = columnDescriptor.getViewColumn(index)
109 self._view.append_column(column)
110 index += 1
111
112 self._view.connect("row-activated", self._rowActivated)
113 self._view.connect("button-press-event", self._buttonPressEvent)
114
115 selection = self._view.get_selection()
116 selection.connect("changed", self._selectionChanged)
117 if multiSelection:
118 selection.set_mode(SELECTION_MULTIPLE)
119
120 scrolledWindow = gtk.ScrolledWindow()
121 scrolledWindow.add(self._view)
122 if widthRequest is not None:
123 scrolledWindow.set_size_request(widthRequest, -1)
124 # FIXME: these should be constants in common.py
125 scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
126 else gtk.POLICY_AUTOMATIC,
127 gtk.PolicyType.AUTOMATIC if pygobject
128 else gtk.POLICY_AUTOMATIC)
129 scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
130 else gtk.SHADOW_IN)
131
132 super(FlightList, self).__init__(xalign = 0.5, yalign = 0.0,
133 xscale = 0.0, yscale = 1.0)
134 self.add(scrolledWindow)
135
136 @property
137 def selectedIndexes(self):
138 """Get the indexes of the selected entries, if any.
139
140 The indexes are sorted."""
141 selection = self._view.get_selection()
142 (model, rows) = selection.get_selected_rows()
143
144 indexes = [self._getIndexForPath(path) for path in rows]
145 indexes.sort()
146 return indexes
147
148 @property
149 def hasFlights(self):
150 """Determine if there are any flights in the list."""
151 return self._model.get_iter_root() is not None
152
153 def clear(self):
154 """Clear the model."""
155 self._model.clear()
156
157 def addFlight(self, flight):
158 """Add the given booked flight."""
159 values = [self._model.iter_n_children(None)]
160 for columnDescriptor in self._columnDescriptors:
161 values.append(columnDescriptor.getValueFrom(flight))
162 self._model.append(values)
163
164 def removeFlights(self, indexes):
165 """Remove the flights with the given indexes."""
166 model = self._model
167 idx = 0
168 iter = model.get_iter_first()
169 while iter is not None:
170 nextIter = model.iter_next(iter)
171 if model.get_value(iter, 0) in indexes:
172 model.remove(iter)
173 else:
174 model.set_value(iter, 0, idx)
175 idx += 1
176 iter = nextIter
177
178 def _getIndexForPath(self, path):
179 """Get the index for the given path."""
180 iter = self._model.get_iter(path)
181 return self._model.get_value(iter, 0)
182
183 def _rowActivated(self, flightList, path, column):
184 """Called when a row is selected."""
185 self.emit("row-activated", self._getIndexForPath(path))
186
187 def _buttonPressEvent(self, widget, event):
188 """Called when a mouse button is pressed or released."""
189 if event.type!=EVENT_BUTTON_PRESS or event.button!=3 or \
190 self._popupMenuProducer is None:
191 return
192
193 (path, _, _, _) = self._view.get_path_at_pos(int(event.x),
194 int(event.y))
195 selection = self._view.get_selection()
196 selection.unselect_all()
197 selection.select_path(path)
198
199 if self._popupMenu is None:
200 self._popupMenu = self._popupMenuProducer()
201 menu = self._popupMenu
202 if pygobject:
203 menu.popup(None, None, None, None, event.button, event.time)
204 else:
205 menu.popup(None, None, None, event.button, event.time)
206
207 def _selectionChanged(self, selection):
208 """Called when the selection has changed."""
209 self.emit("selection-changed", self.selectedIndexes)
210
211#-------------------------------------------------------------------------------
212
213gobject.signal_new("row-activated", FlightList, gobject.SIGNAL_RUN_FIRST,
214 None, (int,))
215
216gobject.signal_new("selection-changed", FlightList, gobject.SIGNAL_RUN_FIRST,
217 None, (object,))
218
219#-----------------------------------------------------------------------------
220
221class PendingFlightsFrame(gtk.Frame):
222 """A frame for a list of pending (reported or rejected) flights.
223
224 It contains the list and the buttons available."""
225 def getAircraft(tailNumber, bookedFlight):
226 """Get the aircraft from the given booked flight.
227
228 This is the tail number followed by the ICAO code of the aircraft's
229 type."""
230 return tailNumber + \
231 " (" + const.icaoCodes[bookedFlight.aircraftType] + ")"
232
233 columnDescriptors = [
234 ColumnDescriptor("callsign", xstr("flightsel_no")),
235 ColumnDescriptor("departureTime", xstr("flightsel_deptime"),
236 sortable = True, defaultSortable = True),
237 ColumnDescriptor("departureICAO", xstr("flightsel_from"),
238 sortable = True),
239 ColumnDescriptor("arrivalICAO", xstr("flightsel_to"),
240 sortable = True),
241 ColumnDescriptor("tailNumber", xstr("pendflt_acft"),
242 convertFn = getAircraft)
243 ]
244
245 def __init__(self, which, wizard, window, pirepEditable = False):
246 """Construct the frame with the given title."""
247 super(PendingFlightsFrame, self).__init__(xstr("pendflt_title_" + which))
248
249 self._which = which
250 self._wizard = wizard
251 self._window = window
252 self._pirepEditable = pirepEditable
253
254 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
255 alignment.set_padding(padding_top = 2, padding_bottom = 8,
256 padding_left = 4, padding_right = 4)
257
258 hbox = gtk.HBox()
259
260 self._flights = []
261 self._flightList = FlightList(columnDescriptors =
262 PendingFlightsFrame.columnDescriptors,
263 widthRequest = 500, multiSelection = True)
264 self._flightList.connect("selection-changed", self._selectionChanged)
265
266 hbox.pack_start(self._flightList, True, True, 4)
267
268 buttonBox = gtk.VBox()
269
270 self._editButton = gtk.Button(xstr("pendflt_" +
271 ("edit" if pirepEditable else
272 "view") + "_" + which))
273 self._editButton.set_sensitive(False)
274 self._editButton.connect("clicked", self._editClicked)
275 buttonBox.pack_start(self._editButton, False, False, 2)
276
277 self._reflyButton = gtk.Button(xstr("pendflt_refly_" + which))
278 self._reflyButton.set_sensitive(False)
279 self._reflyButton.connect("clicked", self._reflyClicked)
280 buttonBox.pack_start(self._reflyButton, False, False, 2)
281
282 self._deleteButton = gtk.Button(xstr("pendflt_delete_" + which))
283 self._deleteButton.set_sensitive(False)
284 self._deleteButton.connect("clicked", self._deleteClicked)
285 buttonBox.pack_start(self._deleteButton, False, False, 2)
286
287 hbox.pack_start(buttonBox, False, False, 4)
288
289 alignment.add(hbox)
290 self.add(alignment)
291
292 @property
293 def hasFlights(self):
294 """Determine if there are any flights in the list."""
295 return self._flightList.hasFlights
296
297 def clear(self):
298 """Clear the lists."""
299 self._flights = []
300 self._flightList.clear()
301
302 def addFlight(self, flight):
303 """Add a flight to the list."""
304 self._flights.append(flight)
305 self._flightList.addFlight(flight)
306
307 def _selectionChanged(self, flightList, selectedIndexes):
308 """Called when the selection in the list has changed."""
309 self._editButton.set_sensitive(len(selectedIndexes)==1)
310 self._reflyButton.set_sensitive(len(selectedIndexes)>0)
311 self._deleteButton.set_sensitive(len(selectedIndexes)>0)
312
313 def _editClicked(self, button):
314 """Called when the Edit button is clicked."""
315 gui = self._wizard.gui
316 gui.beginBusy(xstr("pendflt_pirep_busy"))
317 self.set_sensitive(False)
318
319 indexes = self._flightList.selectedIndexes
320 assert(len(indexes)==1)
321
322 flightID = self._flights[indexes[0]].id
323 gui.webHandler.getPIREP(self._pirepResultCallback, flightID)
324
325 def _pirepResultCallback(self, returned, result):
326 """Called when the PIREP query result is available."""
327 gobject.idle_add(self._handlePIREPResult, returned, result)
328
329 def _handlePIREPResult(self, returned, result):
330 """Handle the refly result."""
331
332 self.set_sensitive(True)
333 gui = self._wizard.gui
334 gui.endBusy()
335
336 if returned:
337 if self._pirepEditable:
338 gui.editPIREP(result.pirep)
339 else:
340 gui.viewPIREP(result.pirep)
341
342 def _reflyClicked(self, button):
343 """Called when the Refly button is clicked."""
344 if askYesNo(xstr("pendflt_refly_question"), parent = self._window):
345 gui = self._wizard.gui
346 gui.beginBusy(xstr("pendflt_refly_busy"))
347 self.set_sensitive(False)
348
349 flightIDs = [self._flights[i].id
350 for i in self._flightList.selectedIndexes]
351 gui.webHandler.reflyFlights(self._reflyResultCallback, flightIDs)
352
353 def _reflyResultCallback(self, returned, result):
354 """Called when the refly result is available."""
355 gobject.idle_add(self._handleReflyResult, returned, result)
356
357 def _handleReflyResult(self, returned, result):
358 """Handle the refly result."""
359
360 self.set_sensitive(True)
361 gui = self._wizard.gui
362 gui.endBusy()
363
364 print "PendingFlightsFrame._handleReflyResult", returned, result
365
366 if returned:
367 indexes = self._flightList.selectedIndexes
368
369 flights = [self._flights[index] for index in indexes]
370
371 self._flightList.removeFlights(indexes)
372 for index in indexes[::-1]:
373 del self._flights[index]
374
375 for flight in flights:
376 self._wizard.reflyFlight(flight)
377 self._window.checkFlights()
378 else:
379 communicationErrorDialog()
380
381 def _deleteClicked(self, button):
382 """Called when the Delete button is clicked."""
383 if askYesNo(xstr("flight_delete_question"), parent = self._window):
384 gui = self._wizard.gui
385 gui.beginBusy(xstr("pendflt_refly_busy"))
386 self.set_sensitive(False)
387
388 flightIDs = [self._flights[i].id
389 for i in self._flightList.selectedIndexes]
390 gui.webHandler.deleteFlights(self._deleteResultCallback, flightIDs)
391
392 def _deleteResultCallback(self, returned, result):
393 """Called when the deletion result is available."""
394 gobject.idle_add(self._handleDeleteResult, returned, result)
395
396 def _handleDeleteResult(self, returned, result):
397 """Handle the delete result."""
398
399 self.set_sensitive(True)
400 gui = self._wizard.gui
401 gui.endBusy()
402
403 print "PendingFlightsFrame._handleDeleteResult", returned, result
404
405 if returned:
406 indexes = self._flightList.selectedIndexes
407
408 flights = [self._flights[index] for index in indexes]
409
410 self._flightList.removeFlights(indexes)
411 for index in indexes[::-1]:
412 del self._flights[index]
413
414 for flight in flights:
415 self._wizard.deleteFlight(flight)
416 self._window.checkFlights()
417 else:
418 communicationErrorDialog()
419
420#-----------------------------------------------------------------------------
421
422class PendingFlightsWindow(gtk.Window):
423 """The window to display the lists of the pending (reported or rejected)
424 flights."""
425 def __init__(self, wizard):
426 """Construct the window"""
427 super(PendingFlightsWindow, self).__init__()
428
429 gui = wizard.gui
430
431 self.set_title(WINDOW_TITLE_BASE + " - " + xstr("pendflt_title"))
432 self.set_size_request(-1, 450)
433 self.set_transient_for(gui.mainWindow)
434 self.set_modal(True)
435
436 mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
437 xscale = 1.0, yscale = 1.0)
438 mainAlignment.set_padding(padding_top = 0, padding_bottom = 12,
439 padding_left = 8, padding_right = 8)
440
441 vbox = gtk.VBox()
442
443 self._reportedFrame = PendingFlightsFrame("reported", wizard, self,
444 True)
445 vbox.pack_start(self._reportedFrame, True, True, 2)
446
447 self._rejectedFrame = PendingFlightsFrame("rejected", wizard, self)
448 vbox.pack_start(self._rejectedFrame, True, True, 2)
449
450 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
451 xscale = 0.0, yscale = 0.0)
452 self._closeButton = gtk.Button(xstr("button_ok"))
453 self._closeButton.connect("clicked", self._closeClicked)
454 alignment.add(self._closeButton)
455 vbox.pack_start(alignment, False, False, 2)
456
457 mainAlignment.add(vbox)
458
459 self.add(mainAlignment)
460
461 self.connect("key-press-event", self._keyPressed)
462
463 @property
464 def hasFlights(self):
465 """Determine if the window has any flights."""
466 return self._reportedFrame.hasFlights or self._rejectedFrame.hasFlights
467
468 def clear(self):
469 """Clear the lists."""
470 self._reportedFrame.clear()
471 self._rejectedFrame.clear()
472
473 def addReportedFlight(self, flight):
474 """Add a reported flight."""
475 self._reportedFrame.addFlight(flight)
476
477 def addRejectedFlight(self, flight):
478 """Add a rejected flight."""
479 self._rejectedFrame.addFlight(flight)
480
481 def checkFlights(self):
482 """Check if there are any flights in any of the lists, and close the
483 window if not."""
484 if not self.hasFlights:
485 self.emit("delete-event", None)
486
487 def _closeClicked(self, button):
488 """Called when the Close button is clicked.
489
490 A 'delete-event' is emitted to close the window."""
491 self.emit("delete-event", None)
492
493 def _keyPressed(self, window, event):
494 """Called when a key is pressed in the window.
495
496 If the Escape key is pressed, 'delete-event' is emitted to close the
497 window."""
498 if gdk.keyval_name(event.keyval) == "Escape":
499 self.emit("delete-event", None)
500 return True
501
502#-----------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.