source: src/mlx/gui/flightlist.py@ 822:08829c45f0a9

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

A column descriptor can be marked as the default sortable one instead of hardwiring column 2 (re #307).

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