1 | # Module for handling the time table and booking flights
|
---|
2 |
|
---|
3 | #-----------------------------------------------------------------------------
|
---|
4 |
|
---|
5 | from mlx.gui.common import *
|
---|
6 | from flightlist import ColumnDescriptor
|
---|
7 |
|
---|
8 | import mlx.const as const
|
---|
9 |
|
---|
10 | import datetime
|
---|
11 |
|
---|
12 | #-----------------------------------------------------------------------------
|
---|
13 |
|
---|
14 | class Timetable(gtk.Alignment):
|
---|
15 | """The widget for the time table."""
|
---|
16 | def _getVIPRenderer():
|
---|
17 | """Get the renderer for the VIP column."""
|
---|
18 | renderer = gtk.CellRendererToggle()
|
---|
19 | renderer.set_activatable(True)
|
---|
20 | return renderer
|
---|
21 |
|
---|
22 | defaultColumnDescriptors = [
|
---|
23 | ColumnDescriptor("callsign", xstr("timetable_no"),
|
---|
24 | sortable = True, defaultSortable = True),
|
---|
25 | ColumnDescriptor("aircraftType", xstr("timetable_type"),
|
---|
26 | sortable = True,
|
---|
27 | convertFn = lambda aircraftType, flight:
|
---|
28 | aircraftNames[aircraftType]),
|
---|
29 | ColumnDescriptor("departureICAO", xstr("timetable_from"),
|
---|
30 | sortable = True),
|
---|
31 | ColumnDescriptor("arrivalICAO", xstr("timetable_to"), sortable = True),
|
---|
32 | ColumnDescriptor("departureTime", xstr("timetable_dep"),
|
---|
33 | sortable = True),
|
---|
34 | ColumnDescriptor("arrivalTime", xstr("timetable_arr"), sortable = True),
|
---|
35 | ColumnDescriptor("duration", xstr("timetable_duration"),
|
---|
36 | sortable = True,
|
---|
37 | convertFn = lambda duration, flight:
|
---|
38 | "%02d:%02d" % (duration/3600,
|
---|
39 | (duration%3600)/60)),
|
---|
40 | ColumnDescriptor("spec", xstr("timetable_vip"), type = bool,
|
---|
41 | renderer = _getVIPRenderer(),
|
---|
42 | sortable = True,
|
---|
43 | convertFn = lambda spec, flight: spec==1)
|
---|
44 | ]
|
---|
45 |
|
---|
46 | @staticmethod
|
---|
47 | def isFlightSelected(flight, regularEnabled, vipEnabled, aircraftTypes):
|
---|
48 | """Determine if the given flight is selected by the given
|
---|
49 | filtering conditions."""
|
---|
50 | return ((regularEnabled and flight.spec==0) or \
|
---|
51 | (vipEnabled and flight.spec==1)) and \
|
---|
52 | flight.aircraftType in aircraftTypes
|
---|
53 |
|
---|
54 | def __init__(self, columnDescriptors = defaultColumnDescriptors,
|
---|
55 | popupMenuProducer = None):
|
---|
56 | """Construct the time table."""
|
---|
57 | # FIXME: this is very similar to flightlist.FlightList
|
---|
58 | self._columnDescriptors = columnDescriptors
|
---|
59 | self._popupMenuProducer = popupMenuProducer
|
---|
60 | self._popupMenu = None
|
---|
61 |
|
---|
62 | types = [int]
|
---|
63 | defaultSortableIndex = None
|
---|
64 | for columnDescriptor in self._columnDescriptors:
|
---|
65 | if columnDescriptor.defaultSortable:
|
---|
66 | defaultSortableIndex = len(types)
|
---|
67 | columnDescriptor.appendType(types)
|
---|
68 |
|
---|
69 | self._model = gtk.ListStore(*types)
|
---|
70 | if defaultSortableIndex is not None:
|
---|
71 | sortOrder = SORT_DESCENDING \
|
---|
72 | if self._columnDescriptors[defaultSortableIndex-1]._defaultDescending \
|
---|
73 | else SORT_ASCENDING
|
---|
74 | self._model.set_sort_column_id(defaultSortableIndex, sortOrder)
|
---|
75 | self._view = gtk.TreeView(self._model)
|
---|
76 |
|
---|
77 | flightPairIndexColumn = gtk.TreeViewColumn()
|
---|
78 | flightPairIndexColumn.set_visible(False)
|
---|
79 | self._view.append_column(flightPairIndexColumn)
|
---|
80 |
|
---|
81 | index = 1
|
---|
82 | for columnDescriptor in self._columnDescriptors:
|
---|
83 | column = columnDescriptor.getViewColumn(index)
|
---|
84 | self._view.append_column(column)
|
---|
85 | index += 1
|
---|
86 |
|
---|
87 | self._view.connect("row-activated", self._rowActivated)
|
---|
88 | self._view.connect("button-press-event", self._buttonPressEvent)
|
---|
89 |
|
---|
90 | selection = self._view.get_selection()
|
---|
91 | selection.connect("changed", self._selectionChanged)
|
---|
92 |
|
---|
93 | scrolledWindow = gtk.ScrolledWindow()
|
---|
94 | scrolledWindow.add(self._view)
|
---|
95 | scrolledWindow.set_size_request(800, -1)
|
---|
96 |
|
---|
97 | # FIXME: these should be constants in common.py
|
---|
98 | scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
|
---|
99 | else gtk.POLICY_AUTOMATIC,
|
---|
100 | gtk.PolicyType.AUTOMATIC if pygobject
|
---|
101 | else gtk.POLICY_AUTOMATIC)
|
---|
102 | scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
|
---|
103 | else gtk.SHADOW_IN)
|
---|
104 |
|
---|
105 | super(Timetable, self).__init__(xalign = 0.5, yalign = 0.0,
|
---|
106 | xscale = 0.0, yscale = 1.0)
|
---|
107 | self.add(scrolledWindow)
|
---|
108 |
|
---|
109 | self._flightPairs = []
|
---|
110 |
|
---|
111 | @property
|
---|
112 | def hasFlightPairs(self):
|
---|
113 | """Determine if the timetable contains any flights."""
|
---|
114 | return len(self._flightPairs)>0
|
---|
115 |
|
---|
116 | def clear(self):
|
---|
117 | """Clear the flight pairs."""
|
---|
118 | self._model.clear()
|
---|
119 | self._flightPairs = []
|
---|
120 |
|
---|
121 | def setFlightPairs(self, flightPairs):
|
---|
122 | """Setup the table contents from the given list of
|
---|
123 | rpc.ScheduledFlightPair objects."""
|
---|
124 | self.clear()
|
---|
125 |
|
---|
126 | self._flightPairs = flightPairs
|
---|
127 |
|
---|
128 | def updateList(self, regularEnabled, vipEnabled, types):
|
---|
129 | """Update the actual list according to the given filter values."""
|
---|
130 | index = 0
|
---|
131 | self._model.clear()
|
---|
132 | for flightPair in self._flightPairs:
|
---|
133 | flight = flightPair.flight0
|
---|
134 | if Timetable.isFlightSelected(flight, regularEnabled, vipEnabled,
|
---|
135 | types):
|
---|
136 | values = [index]
|
---|
137 | for columnDescriptor in self._columnDescriptors:
|
---|
138 | values.append(columnDescriptor.getValueFrom(flight))
|
---|
139 | self._model.append(values)
|
---|
140 | index += 1
|
---|
141 |
|
---|
142 | def _rowActivated(self, flightList, path, column):
|
---|
143 | """Called when a row is selected."""
|
---|
144 | print "_rowActivated"
|
---|
145 |
|
---|
146 | def _buttonPressEvent(self, widget, event):
|
---|
147 | """Called when a mouse button is pressed or released."""
|
---|
148 | print "_buttonPressEvent", event
|
---|
149 |
|
---|
150 | def _selectionChanged(self, selection):
|
---|
151 | """Called when the selection has changed."""
|
---|
152 | print "_selectionChanged"
|
---|
153 |
|
---|
154 | #-----------------------------------------------------------------------------
|
---|
155 |
|
---|
156 | class CalendarWindow(gtk.Window):
|
---|
157 | """A window for a calendar."""
|
---|
158 | def __init__(self):
|
---|
159 | """Construct the window."""
|
---|
160 | super(CalendarWindow, self).__init__()
|
---|
161 |
|
---|
162 | self.set_decorated(False)
|
---|
163 | self.set_modal(True)
|
---|
164 | self.connect("key-press-event", self._keyPressed)
|
---|
165 |
|
---|
166 | mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
167 | xscale = 1.0, yscale = 1.0)
|
---|
168 | #mainAlignment.set_padding(padding_top = 0, padding_bottom = 12,
|
---|
169 | # padding_left = 8, padding_right = 8)
|
---|
170 |
|
---|
171 | self._calendar = gtk.Calendar()
|
---|
172 | self._calendar.connect("day-selected-double-click", self._daySelectedDoubleClick)
|
---|
173 | mainAlignment.add(self._calendar)
|
---|
174 |
|
---|
175 | self.add(mainAlignment)
|
---|
176 |
|
---|
177 | def setDate(self, date):
|
---|
178 | """Set the current date to the given one."""
|
---|
179 | self._calendar.select_month(date.month-1, date.year)
|
---|
180 | self._calendar.select_day(date.day)
|
---|
181 |
|
---|
182 | def getDate(self):
|
---|
183 | """Get the currently selected date."""
|
---|
184 | (year, monthM1, day) = self._calendar.get_date()
|
---|
185 | return datetime.date(year, monthM1+1, day)
|
---|
186 |
|
---|
187 | def _daySelectedDoubleClick(self, calendar):
|
---|
188 | """Called when a date is double clicked."""
|
---|
189 | self.emit("date-selected")
|
---|
190 |
|
---|
191 | def _keyPressed(self, window, event):
|
---|
192 | """Called when a key is pressed in the window.
|
---|
193 |
|
---|
194 | If the Escape key is pressed, 'delete-event' is emitted to close the
|
---|
195 | window."""
|
---|
196 | keyName = gdk.keyval_name(event.keyval)
|
---|
197 | if keyName =="Escape":
|
---|
198 | self.emit("delete-event", None)
|
---|
199 | return True
|
---|
200 | elif keyName =="Return":
|
---|
201 | self.emit("date-selected")
|
---|
202 | return True
|
---|
203 |
|
---|
204 | gobject.signal_new("date-selected", CalendarWindow, gobject.SIGNAL_RUN_FIRST,
|
---|
205 | None, ())
|
---|
206 |
|
---|
207 | #-----------------------------------------------------------------------------
|
---|
208 |
|
---|
209 | class TimetableWindow(gtk.Window):
|
---|
210 | """The window to display the timetable."""
|
---|
211 | typeFamilies = [
|
---|
212 | const.AIRCRAFT_FAMILY_B737NG,
|
---|
213 | const.AIRCRAFT_FAMILY_DH8D,
|
---|
214 | const.AIRCRAFT_FAMILY_B767,
|
---|
215 |
|
---|
216 | const.AIRCRAFT_FAMILY_B737CL,
|
---|
217 | const.AIRCRAFT_FAMILY_CRJ2,
|
---|
218 | const.AIRCRAFT_FAMILY_F70,
|
---|
219 |
|
---|
220 | const.AIRCRAFT_FAMILY_T134,
|
---|
221 | const.AIRCRAFT_FAMILY_T154
|
---|
222 | ]
|
---|
223 |
|
---|
224 | def __init__(self, gui):
|
---|
225 | super(TimetableWindow, self).__init__()
|
---|
226 |
|
---|
227 | self._gui = gui
|
---|
228 | self.set_title(WINDOW_TITLE_BASE + " - " + xstr("timetable_title"))
|
---|
229 | self.set_size_request(-1, 600)
|
---|
230 | self.set_transient_for(gui.mainWindow)
|
---|
231 | self.set_modal(True)
|
---|
232 | self.connect("key-press-event", self._keyPressed)
|
---|
233 |
|
---|
234 | mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
235 | xscale = 1.0, yscale = 1.0)
|
---|
236 | mainAlignment.set_padding(padding_top = 0, padding_bottom = 12,
|
---|
237 | padding_left = 8, padding_right = 8)
|
---|
238 |
|
---|
239 | vbox = gtk.VBox()
|
---|
240 |
|
---|
241 | filterAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
242 | xscale = 1.0, yscale = 1.0)
|
---|
243 |
|
---|
244 | filterFrame = gtk.Frame(xstr("timetable_filter"))
|
---|
245 |
|
---|
246 | filterVBox = gtk.VBox()
|
---|
247 |
|
---|
248 | topAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
249 | xscale = 0.0, yscale = 0.0)
|
---|
250 | topHBox = gtk.HBox()
|
---|
251 |
|
---|
252 | label = gtk.Label(xstr("timetable_flightdate"))
|
---|
253 | label.set_use_underline(True)
|
---|
254 | topHBox.pack_start(label, False, False, 4)
|
---|
255 |
|
---|
256 | self._flightDate = gtk.Button()
|
---|
257 | self._flightDate.connect("clicked", self._flightDateClicked)
|
---|
258 | self._flightDate.set_tooltip_text(xstr("timetable_flightdate_tooltip"))
|
---|
259 | label.set_mnemonic_widget(self._flightDate)
|
---|
260 | topHBox.pack_start(self._flightDate, False, False, 4)
|
---|
261 |
|
---|
262 | filler = gtk.Alignment()
|
---|
263 | filler.set_size_request(48, 2)
|
---|
264 | topHBox.pack_start(filler, False, True, 0)
|
---|
265 |
|
---|
266 | self._regularFlights = gtk.CheckButton(xstr("timetable_show_regular"))
|
---|
267 | self._regularFlights.set_use_underline(True)
|
---|
268 | self._regularFlights.set_tooltip_text(xstr("timetable_show_regular_tooltip"))
|
---|
269 | self._regularFlights.set_active(True)
|
---|
270 | self._regularFlights.connect("toggled", self._filterChanged)
|
---|
271 | topHBox.pack_start(self._regularFlights, False, False, 8)
|
---|
272 |
|
---|
273 | self._vipFlights = gtk.CheckButton(xstr("timetable_show_vip"))
|
---|
274 | self._vipFlights.set_use_underline(True)
|
---|
275 | self._vipFlights.set_tooltip_text(xstr("timetable_show_vip_tooltip"))
|
---|
276 | self._vipFlights.set_active(True)
|
---|
277 | self._vipFlights.connect("toggled", self._filterChanged)
|
---|
278 | topHBox.pack_start(self._vipFlights, False, False, 8)
|
---|
279 |
|
---|
280 | topAlignment.add(topHBox)
|
---|
281 |
|
---|
282 | filterVBox.pack_start(topAlignment, False, False, 4)
|
---|
283 |
|
---|
284 | separator = gtk.HSeparator()
|
---|
285 | filterVBox.pack_start(separator, False, False, 4)
|
---|
286 |
|
---|
287 | typeAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
288 | xscale = 0.0, yscale = 0.0)
|
---|
289 |
|
---|
290 | numColumns = 4
|
---|
291 | numRows = (len(TimetableWindow.typeFamilies)+numColumns-1)/numColumns
|
---|
292 |
|
---|
293 | typeTable = gtk.Table(numRows, numColumns)
|
---|
294 | typeTable.set_col_spacings(8)
|
---|
295 | row = 0
|
---|
296 | column = 0
|
---|
297 | self._typeFamilyButtons = {}
|
---|
298 | for typeFamily in TimetableWindow.typeFamilies:
|
---|
299 | checkButton = gtk.CheckButton(aircraftFamilyNames[typeFamily])
|
---|
300 | checkButton.set_active(True)
|
---|
301 | checkButton.connect("toggled", self._filterChanged)
|
---|
302 | self._typeFamilyButtons[typeFamily] = checkButton
|
---|
303 |
|
---|
304 | typeTable.attach(checkButton, column, column + 1, row, row+1)
|
---|
305 |
|
---|
306 | column += 1
|
---|
307 | if column>=numColumns:
|
---|
308 | row += 1
|
---|
309 | column = 0
|
---|
310 |
|
---|
311 | typeAlignment.add(typeTable)
|
---|
312 | filterVBox.pack_start(typeAlignment, False, False, 4)
|
---|
313 |
|
---|
314 | filterFrame.add(filterVBox)
|
---|
315 |
|
---|
316 | filterAlignment.add(filterFrame)
|
---|
317 | vbox.pack_start(filterAlignment, False, False, 2)
|
---|
318 |
|
---|
319 | self._timetable = Timetable()
|
---|
320 | vbox.pack_start(self._timetable, True, True, 2)
|
---|
321 |
|
---|
322 | alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
|
---|
323 | xscale = 0.0, yscale = 0.0)
|
---|
324 | self._closeButton = gtk.Button(xstr("button_ok"))
|
---|
325 | self._closeButton.connect("clicked", self._closeClicked)
|
---|
326 | alignment.add(self._closeButton)
|
---|
327 | vbox.pack_start(alignment, False, False, 2)
|
---|
328 |
|
---|
329 | mainAlignment.add(vbox)
|
---|
330 |
|
---|
331 | self._calendarWindow = calendarWindow = CalendarWindow()
|
---|
332 | calendarWindow.set_transient_for(self)
|
---|
333 | calendarWindow.connect("delete-event", self._calendarWindowDeleted)
|
---|
334 | calendarWindow.connect("date-selected", self._calendarWindowDateSelected)
|
---|
335 |
|
---|
336 | self.add(mainAlignment)
|
---|
337 |
|
---|
338 | self.setDate(datetime.date.today())
|
---|
339 |
|
---|
340 | @property
|
---|
341 | def hasFlightPairs(self):
|
---|
342 | """Determine if there any flight pairs displayed in the window."""
|
---|
343 | return self._timetable.hasFlightPairs
|
---|
344 |
|
---|
345 | @property
|
---|
346 | def isRegularEnabled(self):
|
---|
347 | """Determine if regular flights are enabled."""
|
---|
348 | return self._regularFlights.get_active()!=0
|
---|
349 |
|
---|
350 | @property
|
---|
351 | def isVIPEnabled(self):
|
---|
352 | """Determine if VIP flights are enabled."""
|
---|
353 | return self._vipFlights.get_active()!=0
|
---|
354 |
|
---|
355 | def setTypes(self, aircraftTypes):
|
---|
356 | """Enable/disable the type family checkboxes according to the given
|
---|
357 | list of types."""
|
---|
358 | typeFamilies = set()
|
---|
359 | for aircraftType in aircraftTypes:
|
---|
360 | typeFamilies.add(const.aircraftType2Family(aircraftType))
|
---|
361 |
|
---|
362 | for (typeFamily, checkButton) in self._typeFamilyButtons.iteritems():
|
---|
363 | checkButton.set_sensitive(typeFamily in typeFamilies)
|
---|
364 |
|
---|
365 | def clear(self):
|
---|
366 | """Clear all flight pairs."""
|
---|
367 | self._timetable.clear()
|
---|
368 |
|
---|
369 | def setFlightPairs(self, flightPairs):
|
---|
370 | """Set the flight pairs."""
|
---|
371 | self._timetable.setFlightPairs(flightPairs)
|
---|
372 | self._updateList()
|
---|
373 |
|
---|
374 | def setDate(self, date):
|
---|
375 | """Set the date to the given one."""
|
---|
376 | self._flightDate.set_label(date.strftime("%Y-%m-%d"))
|
---|
377 | self._calendarWindow.setDate(date)
|
---|
378 |
|
---|
379 | def _closeClicked(self, button):
|
---|
380 | """Called when the Close button is clicked.
|
---|
381 |
|
---|
382 | A 'delete-event' is emitted to close the window."""
|
---|
383 | self.emit("delete-event", None)
|
---|
384 |
|
---|
385 | def _flightDateClicked(self, button):
|
---|
386 | """Called when the flight date button is clicked."""
|
---|
387 | self._calendarWindow.set_position(gtk.WIN_POS_MOUSE)
|
---|
388 | self.set_focus(self._calendarWindow)
|
---|
389 | self._calendarWindow.show_all()
|
---|
390 |
|
---|
391 | def _calendarWindowDeleted(self, window, event):
|
---|
392 | """Called when the flight date window is deleted."""
|
---|
393 | self._calendarWindow.hide()
|
---|
394 |
|
---|
395 | def _calendarWindowDateSelected(self, window):
|
---|
396 | """Called when the flight date window is deleted."""
|
---|
397 | self._calendarWindow.hide()
|
---|
398 | date = window.getDate()
|
---|
399 | self._flightDate.set_label(date.strftime("%Y-%m-%d"))
|
---|
400 | self._gui.updateTimeTable(date)
|
---|
401 |
|
---|
402 | def _filterChanged(self, checkButton):
|
---|
403 | """Called when the filter conditions have changed."""
|
---|
404 | self._updateList()
|
---|
405 |
|
---|
406 | def _keyPressed(self, window, event):
|
---|
407 | """Called when a key is pressed in the window.
|
---|
408 |
|
---|
409 | If the Escape key is pressed, 'delete-event' is emitted to close the
|
---|
410 | window."""
|
---|
411 | if gdk.keyval_name(event.keyval) == "Escape":
|
---|
412 | self.emit("delete-event", None)
|
---|
413 | return True
|
---|
414 |
|
---|
415 | def _updateList(self):
|
---|
416 | """Update the timetable list."""
|
---|
417 | aircraftTypes = []
|
---|
418 | for (aircraftFamily, button) in self._typeFamilyButtons.iteritems():
|
---|
419 | if button.get_active():
|
---|
420 | aircraftTypes += const.aircraftFamily2Types[aircraftFamily]
|
---|
421 |
|
---|
422 | self._timetable.updateList(self.isRegularEnabled,
|
---|
423 | self.isVIPEnabled,
|
---|
424 | aircraftTypes)
|
---|