source: src/mlx/gui/flightlist.py@ 821:158bfb360027

Last change on this file since 821:158bfb360027 was 821:158bfb360027, checked in by István Váradi <ivaradi@…>, 8 years ago

A flight can be marked for reflying (re #307).

File size: 14.2 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 """Construct the descriptor."""
17 self._attribute = attribute
18 self._heading = heading
19 self._type = type
20 self._convertFn = convertFn
21 self._renderer = renderer
22 self._extraColumnAttributes = extraColumnAttributes
23 self._sortable = sortable
24
25 def appendType(self, types):
26 """Append the type of this column to the given list of types."""
27 types.append(self._type)
28
29 def getViewColumn(self, index):
30 """Get a new column object for a tree view.
31
32 @param index is the 0-based index of the column."""
33 if self._extraColumnAttributes is None:
34 if isinstance(self._renderer, gtk.CellRendererText):
35 extraColumnAttributes = {"text" : index}
36 else:
37 extraColumnAttributes = {}
38 else:
39 extraColumnAttributes = self._extraColumnAttributes
40
41 column = gtk.TreeViewColumn(self._heading, self._renderer,
42 text = index)
43 column.set_expand(True)
44 if self._sortable:
45 column.set_sort_column_id(index)
46 column.set_sort_indicator(True)
47
48 return column
49
50 def getValueFrom(self, flight):
51 """Get the value from the given flight."""
52 value = getattr(flight, self._attribute)
53 return self._type(value) if self._convertFn is None \
54 else self._convertFn(value, flight)
55
56#-----------------------------------------------------------------------------
57
58class FlightList(gtk.Alignment):
59 """Construct the flight list.
60
61 This is a complete widget with a scroll window. It is alignment centered
62 horizontally and expandable vertically."""
63
64 defaultColumnDescriptors = [
65 ColumnDescriptor("callsign", xstr("flightsel_no")),
66 ColumnDescriptor("departureTime", xstr("flightsel_deptime"),
67 sortable = True),
68 ColumnDescriptor("departureICAO", xstr("flightsel_from"),
69 sortable = True),
70 ColumnDescriptor("arrivalICAO", xstr("flightsel_to"), sortable = True)
71 ]
72
73 def __init__(self, columnDescriptors = defaultColumnDescriptors,
74 popupMenuProducer = None, widthRequest = None):
75 """Construct the flight list with the given column descriptors."""
76
77 self._columnDescriptors = columnDescriptors
78 self._popupMenuProducer = popupMenuProducer
79 self._popupMenu = None
80
81 types = [int]
82 for columnDescriptor in self._columnDescriptors:
83 columnDescriptor.appendType(types)
84
85 self._model = gtk.ListStore(*types)
86 self._model.set_sort_column_id(2, SORT_ASCENDING)
87 self._view = gtk.TreeView(self._model)
88
89 flightIndexColumn = gtk.TreeViewColumn()
90 flightIndexColumn.set_visible(False)
91 self._view.append_column(flightIndexColumn)
92
93 index = 1
94 for columnDescriptor in self._columnDescriptors:
95 column = columnDescriptor.getViewColumn(index)
96 self._view.append_column(column)
97 index += 1
98
99 self._view.connect("row-activated", self._rowActivated)
100 self._view.connect("button-press-event", self._buttonPressEvent)
101
102 selection = self._view.get_selection()
103 selection.connect("changed", self._selectionChanged)
104
105 scrolledWindow = gtk.ScrolledWindow()
106 scrolledWindow.add(self._view)
107 if widthRequest is not None:
108 scrolledWindow.set_size_request(widthRequest, -1)
109 # FIXME: these should be constants in common.py
110 scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
111 else gtk.POLICY_AUTOMATIC,
112 gtk.PolicyType.AUTOMATIC if pygobject
113 else gtk.POLICY_AUTOMATIC)
114 scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
115 else gtk.SHADOW_IN)
116
117 super(FlightList, self).__init__(xalign = 0.5, yalign = 0.0,
118 xscale = 0.0, yscale = 1.0)
119 self.add(scrolledWindow)
120
121 @property
122 def selectedIndex(self):
123 """Get the index of the selected entry, if any."""
124 selection = self._view.get_selection()
125 (model, iter) = selection.get_selected()
126 if iter is None:
127 return None
128 else:
129 index = model.get_value(iter, 0)
130 return index
131
132 @property
133 def hasFlights(self):
134 """Determine if there are any flights in the list."""
135 return self._model.get_iter_root() is not None
136
137 def clear(self):
138 """Clear the model."""
139 self._model.clear()
140
141 def addFlight(self, flight):
142 """Add the given booked flight."""
143 values = [self._model.iter_n_children(None)]
144 for columnDescriptor in self._columnDescriptors:
145 values.append(columnDescriptor.getValueFrom(flight))
146 self._model.append(values)
147
148 def removeFlight(self, index):
149 """Remove the flight with the given index."""
150 model = self._model
151 idx = 0
152 iter = model.get_iter_first()
153 while iter is not None:
154 nextIter = model.iter_next(iter)
155 if model.get_value(iter, 0)==index:
156 model.remove(iter)
157 else:
158 model.set_value(iter, 0, idx)
159 idx += 1
160 iter = nextIter
161
162 def _rowActivated(self, flightList, path, column):
163 """Called when a row is selected."""
164 self.emit("row-activated", self.selectedIndex)
165
166 def _buttonPressEvent(self, widget, event):
167 """Called when a mouse button is pressed or released."""
168 if event.type!=EVENT_BUTTON_PRESS or event.button!=3 or \
169 self._popupMenuProducer is None:
170 return
171
172 (path, _, _, _) = self._view.get_path_at_pos(int(event.x),
173 int(event.y))
174 selection = self._view.get_selection()
175 selection.unselect_all()
176 selection.select_path(path)
177
178 if self._popupMenu is None:
179 self._popupMenu = self._popupMenuProducer()
180 menu = self._popupMenu
181 if pygobject:
182 menu.popup(None, None, None, None, event.button, event.time)
183 else:
184 menu.popup(None, None, None, event.button, event.time)
185
186 def _selectionChanged(self, selection):
187 """Called when the selection has changed."""
188 self.emit("selection-changed", self.selectedIndex)
189
190#-------------------------------------------------------------------------------
191
192gobject.signal_new("row-activated", FlightList, gobject.SIGNAL_RUN_FIRST,
193 None, (int,))
194
195gobject.signal_new("selection-changed", FlightList, gobject.SIGNAL_RUN_FIRST,
196 None, (object,))
197
198#-----------------------------------------------------------------------------
199
200class PendingFlightsFrame(gtk.Frame):
201 """A frame for a list of pending (reported or rejected) flights.
202
203 It contains the list and the buttons available."""
204 def getAircraft(tailNumber, bookedFlight):
205 """Get the aircraft from the given booked flight.
206
207 This is the tail number followed by the ICAO code of the aircraft's
208 type."""
209 return tailNumber + \
210 " (" + const.icaoCodes[bookedFlight.aircraftType] + ")"
211
212 columnDescriptors = [
213 ColumnDescriptor("callsign", xstr("flightsel_no")),
214 ColumnDescriptor("departureTime", xstr("flightsel_deptime"),
215 sortable = True),
216 ColumnDescriptor("departureICAO", xstr("flightsel_from"),
217 sortable = True),
218 ColumnDescriptor("arrivalICAO", xstr("flightsel_to"),
219 sortable = True),
220 ColumnDescriptor("tailNumber", xstr("pendflt_acft"),
221 convertFn = getAircraft)
222 ]
223
224 def __init__(self, which, wizard):
225 """Construct the frame with the given title."""
226 super(PendingFlightsFrame, self).__init__(xstr("pendflt_title_" + which))
227
228 self._which = which
229 self._wizard = wizard
230
231 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
232 alignment.set_padding(padding_top = 2, padding_bottom = 8,
233 padding_left = 4, padding_right = 4)
234
235 hbox = gtk.HBox()
236
237 self._flights = []
238 self._flightList = FlightList(columnDescriptors =
239 PendingFlightsFrame.columnDescriptors,
240 widthRequest = 500)
241 self._flightList.connect("selection-changed", self._selectionChanged)
242
243 hbox.pack_start(self._flightList, True, True, 4)
244
245 buttonBox = gtk.VBox()
246
247 self._editButton = gtk.Button(xstr("pendflt_edit_" + which))
248 self._editButton.set_sensitive(False)
249 buttonBox.pack_start(self._editButton, False, False, 2)
250
251 self._reflyButton = gtk.Button(xstr("pendflt_refly_" + which))
252 self._reflyButton.set_sensitive(False)
253 self._reflyButton.connect("clicked", self._reflyClicked)
254 buttonBox.pack_start(self._reflyButton, False, False, 2)
255
256 self._deleteButton = gtk.Button(xstr("pendflt_delete_" + which))
257 self._deleteButton.set_sensitive(False)
258 buttonBox.pack_start(self._deleteButton, False, False, 2)
259
260 hbox.pack_start(buttonBox, False, False, 4)
261
262 alignment.add(hbox)
263 self.add(alignment)
264
265 @property
266 def hasFlights(self):
267 """Determine if there are any flights in the list."""
268 return self._flightList.hasFlights
269
270 def clear(self):
271 """Clear the lists."""
272 self._flights = []
273 self._flightList.clear()
274
275 def addFlight(self, flight):
276 """Add a flight to the list."""
277 self._flights.append(flight)
278 self._flightList.addFlight(flight)
279
280 def _selectionChanged(self, flightList, selectedIndex):
281 """Called when the selection in the list has changed."""
282 sensitive = selectedIndex is not None
283 self._editButton.set_sensitive(sensitive)
284 self._reflyButton.set_sensitive(sensitive)
285 self._deleteButton.set_sensitive(sensitive)
286
287 def _reflyClicked(self, button):
288 """Called when the Refly button is clicked."""
289 gui = self._wizard.gui
290 gui.beginBusy(xstr("pendflt_refly_busy"))
291 self.set_sensitive(False)
292
293 flight = self._flights[self._flightList.selectedIndex]
294 gui.webHandler.reflyFlights(self._reflyResultCallback, [flight.id])
295
296 def _reflyResultCallback(self, returned, result):
297 """Called when the refly result is available."""
298 gobject.idle_add(self._handleReflyResult, returned, result)
299
300 def _handleReflyResult(self, returned, result):
301 """Handle the refly result."""
302
303 self.set_sensitive(True)
304 gui = self._wizard.gui
305 gui.endBusy()
306
307 print "PendingFlightsFrame._handleReflyResult", returned, result
308
309 if returned:
310 index = self._flightList.selectedIndex
311
312 flight = self._flights[index]
313
314 self._flightList.removeFlight(index)
315 del self._flights[index]
316
317 self._wizard.reflyFlight(flight)
318
319
320#-----------------------------------------------------------------------------
321
322class PendingFlightsWindow(gtk.Window):
323 """The window to display the lists of the pending (reported or rejected)
324 flights."""
325 def __init__(self, wizard):
326 """Construct the window"""
327 super(PendingFlightsWindow, self).__init__()
328
329 gui = wizard.gui
330
331 self.set_title(WINDOW_TITLE_BASE + " - " + xstr("pendflt_title"))
332 self.set_size_request(-1, 450)
333 self.set_transient_for(gui.mainWindow)
334 self.set_modal(True)
335
336 mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
337 xscale = 1.0, yscale = 1.0)
338 mainAlignment.set_padding(padding_top = 0, padding_bottom = 12,
339 padding_left = 8, padding_right = 8)
340
341 vbox = gtk.VBox()
342
343 self._reportedFrame = PendingFlightsFrame("reported", wizard)
344 vbox.pack_start(self._reportedFrame, True, True, 2)
345
346 self._rejectedFrame = PendingFlightsFrame("rejected", wizard)
347 vbox.pack_start(self._rejectedFrame, True, True, 2)
348
349 alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
350 xscale = 0.0, yscale = 0.0)
351 self._closeButton = gtk.Button(xstr("button_ok"))
352 self._closeButton.connect("clicked", self._closeClicked)
353 alignment.add(self._closeButton)
354 vbox.pack_start(alignment, False, False, 2)
355
356 mainAlignment.add(vbox)
357
358 self.add(mainAlignment)
359
360 self.connect("key-press-event", self._keyPressed)
361
362 @property
363 def hasFlights(self):
364 """Determine if the window has any flights."""
365 return self._reportedFrame.hasFlights or self._rejectedFrame.hasFlights
366
367 def clear(self):
368 """Clear the lists."""
369 self._reportedFrame.clear()
370 self._rejectedFrame.clear()
371
372 def addReportedFlight(self, flight):
373 """Add a reported flight."""
374 self._reportedFrame.addFlight(flight)
375
376 def addRejectedFlight(self, flight):
377 """Add a rejected flight."""
378 self._rejectedFrame.addFlight(flight)
379
380 def _closeClicked(self, button):
381 """Called when the Close button is clicked.
382
383 A 'delete-event' is emitted to close the window."""
384 self.emit("delete-event", None)
385
386 def _keyPressed(self, window, event):
387 """Called when a key is pressed in the window.
388
389 If the Escape key is pressed, 'delete-event' is emitted to close the
390 window."""
391 if gdk.keyval_name(event.keyval) == "Escape":
392 self.emit("delete-event", None)
393 return True
394
395#-----------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.