[858] | 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)
|
---|