Index: src/mlx/gui/flight.py
===================================================================
--- src/mlx/gui/flight.py	(revision 810)
+++ src/mlx/gui/flight.py	(revision 811)
@@ -2,4 +2,5 @@
 from mlx.gui.common import *
 import mlx.gui.cef as cef
+from mlx.gui.flightlist import ColumnDescriptor, FlightList
 
 import mlx.const as const
@@ -436,4 +437,11 @@
 class FlightSelectionPage(Page):
     """The page to select the flight."""
+    columnDescriptors = [
+        ColumnDescriptor("callsign", xstr("flightsel_no")),
+        ColumnDescriptor("departureTime", xstr("flightsel_deptime")),
+        ColumnDescriptor("departureICAO", xstr("flightsel_from")),
+        ColumnDescriptor("arrivalICAO", xstr("flightsel_to"))
+    ]
+
     def __init__(self, wizard):
         """Construct the flight selection page."""
@@ -444,46 +452,12 @@
                                                   help, completedHelp = completedHelp)
 
-
-        self._listStore = gtk.ListStore(str, str, str, str)
-        self._flightList = gtk.TreeView(self._listStore)
-        column = gtk.TreeViewColumn(xstr("flightsel_no"), gtk.CellRendererText(),
-                                    text = 1)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_deptime"), gtk.CellRendererText(),
-                                    text = 0)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_from"), gtk.CellRendererText(),
-                                    text = 2)
-        column.set_expand(True)
-        self._flightList.append_column(column)
-        column = gtk.TreeViewColumn(xstr("flightsel_to"), gtk.CellRendererText(),
-                                    text = 3)
-        column.set_expand(True)
-        self._flightList.append_column(column)
+        self._flightList = FlightList(FlightSelectionPage.columnDescriptors,
+                                      popupMenuProducer =
+                                      self._createListPopupMenu,
+                                      widthRequest = 400)
         self._flightList.connect("row-activated", self._rowActivated)
-        self._flightList.connect("button-press-event", self._listButtonPressed)
-
-        self._flightListPopupMenu = None
-
-        flightSelection = self._flightList.get_selection()
-        flightSelection.connect("changed", self._selectionChanged)
-
-        scrolledWindow = gtk.ScrolledWindow()
-        scrolledWindow.add(self._flightList)
-        scrolledWindow.set_size_request(400, -1)
-        # FIXME: these should be constants in common.py
-        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC,
-                                  gtk.PolicyType.AUTOMATIC if pygobject
-                                  else gtk.POLICY_AUTOMATIC)
-        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
-                                       else gtk.SHADOW_IN)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.0, xscale = 0.0, yscale = 1.0)
-        alignment.add(scrolledWindow)
-
-        self.setMainWidget(alignment)
+        self._flightList.connect("selection-changed", self._selectionChanged)
+
+        self.setMainWidget(self._flightList)
 
         self._saveButton = self.addButton(xstr("flightsel_save"),
@@ -547,5 +521,5 @@
         """Rebuild the flights from the login result."""
         self._flights = []
-        self._listStore.clear()
+        self._flightList.clear()
         if self._wizard.loggedIn:
             for flight in self._wizard.loginResult.flights:
@@ -555,8 +529,5 @@
         """Add the given file to the list of flights."""
         self._flights.append(flight)
-        self._listStore.append([str(flight.departureTime),
-                                flight.callsign,
-                                flight.departureICAO,
-                                flight.arrivalICAO])
+        self._flightList.addFlight(flight)
 
     def _saveClicked(self, button):
@@ -608,7 +579,7 @@
                 self._buildFlights()
 
-    def _selectionChanged(self, selection):
+    def _selectionChanged(self, flightList, index):
         """Called when the selection is changed."""
-        selected = selection.count_selected_rows()==1
+        selected = index is not None
         self._saveButton.set_sensitive(selected)
         self._button.set_sensitive(selected)
@@ -649,5 +620,5 @@
             self._flightSelected()
 
-    def _rowActivated(self, flightList, path, column):
+    def _rowActivated(self, flightList, index):
         """Called when a row is activated."""
         if not self._completed:
@@ -664,27 +635,6 @@
     def _getSelectedFlight(self):
         """Get the currently selected flight."""
-        selection = self._flightList.get_selection()
-        (listStore, iter) = selection.get_selected()
-        path = listStore.get_path(iter)
-        [index] = path.get_indices() if pygobject else path
-
+        index = self._flightList.selectedIndex
         return self._flights[index]
-
-    def _listButtonPressed(self, widget, event):
-        """Called when a mouse button is pressed on the flight list."""
-        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
-            return
-
-        (path, _, _, _) = self._flightList.get_path_at_pos(int(event.x),
-                                                           int(event.y))
-        selection = self._flightList.get_selection()
-        selection.unselect_all()
-        selection.select_path(path)
-
-        menu = self._getListPopupMenu()
-        if pygobject:
-            menu.popup(None, None, None, None, event.button, event.time)
-        else:
-            menu.popup(None, None, None, event.button, event.time)
 
     def _updateDepartureGate(self):
@@ -786,28 +736,25 @@
         return dialog
 
-    def _getListPopupMenu(self):
+    def _createListPopupMenu(self):
         """Get the flight list popup menu."""
-        if self._flightListPopupMenu is None:
-            menu = gtk.Menu()
-
-            menuItem = gtk.MenuItem()
-            menuItem.set_label(xstr("flightsel_popup_select"))
-            menuItem.set_use_underline(True)
-            menuItem.connect("activate", self._popupSelect)
-            menuItem.show()
-
-            menu.append(menuItem)
-
-            menuItem = gtk.MenuItem()
-            menuItem.set_label(xstr("flightsel_popup_save"))
-            menuItem.set_use_underline(True)
-            menuItem.connect("activate", self._popupSave)
-            menuItem.show()
-
-            menu.append(menuItem)
-
-            self._flightListPopupMenu = menu
-
-        return self._flightListPopupMenu
+        menu = gtk.Menu()
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("flightsel_popup_select"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupSelect)
+        menuItem.show()
+
+        menu.append(menuItem)
+
+        menuItem = gtk.MenuItem()
+        menuItem.set_label(xstr("flightsel_popup_save"))
+        menuItem.set_use_underline(True)
+        menuItem.connect("activate", self._popupSave)
+        menuItem.show()
+
+        menu.append(menuItem)
+
+        return menu
 
     def _popupSelect(self, menuItem):
Index: src/mlx/gui/flightlist.py
===================================================================
--- src/mlx/gui/flightlist.py	(revision 811)
+++ src/mlx/gui/flightlist.py	(revision 811)
@@ -0,0 +1,158 @@
+# A widget which is a generic list of flights
+
+#-----------------------------------------------------------------------------
+
+from mlx.gui.common import *
+
+#-----------------------------------------------------------------------------
+
+class ColumnDescriptor(object):
+    """A descriptor for a column in the list."""
+    def __init__(self, attribute, heading, type = str,
+                 convertFn = None, renderer = gtk.CellRendererText(),
+                 extraColumnAttributes = None):
+        """Construct the descriptor."""
+        self._attribute = attribute
+        self._heading = heading
+        self._type = type
+        self._convertFn = convertFn
+        self._renderer = renderer
+        self._extraColumnAttributes = extraColumnAttributes
+
+    def appendType(self, types):
+        """Append the type of this column to the given list of types."""
+        types.append(self._type)
+
+    def getViewColumn(self, index):
+        """Get a new column object for a tree view.
+
+        @param index is the 0-based index of the column."""
+        if self._extraColumnAttributes is None:
+            if isinstance(self._renderer, gtk.CellRendererText):
+                extraColumnAttributes = {"text" : index}
+            else:
+                extraColumnAttributes = {}
+        else:
+            extraColumnAttributes = self._extraColumnAttributes
+
+        column = gtk.TreeViewColumn(self._heading, self._renderer,
+                                    text = index)
+        column.set_expand(True)
+
+        return column
+
+    def getValueFrom(self, flight):
+        """Get the value from the given flight."""
+        value = getattr(flight, self._attribute)
+        return self._type(value) if self._convertFn is None \
+            else self._convertFn(value)
+
+#-----------------------------------------------------------------------------
+
+class FlightList(gtk.Alignment):
+    """Construct the flight list.
+
+    This is a complete widget with a scroll window. It is alignment centered
+    horizontally and expandable vertically."""
+    def __init__(self, columnDescriptors, popupMenuProducer = None,
+                 widthRequest = None):
+        """Construct the flight list with the given column descriptors."""
+
+        self._columnDescriptors = columnDescriptors
+        self._popupMenuProducer = popupMenuProducer
+        self._popupMenu = None
+
+        types = []
+        for columnDescriptor in self._columnDescriptors:
+            columnDescriptor.appendType(types)
+
+        self._model = gtk.ListStore(*types)
+        self._view = gtk.TreeView(self._model)
+
+        index = 0
+        for columnDescriptor in self._columnDescriptors:
+            column = columnDescriptor.getViewColumn(index)
+            self._view.append_column(column)
+            index += 1
+
+        self._view.connect("row-activated", self._rowActivated)
+        self._view.connect("button-press-event", self._buttonPressEvent)
+
+        selection = self._view.get_selection()
+        selection.connect("changed", self._selectionChanged)
+
+        scrolledWindow = gtk.ScrolledWindow()
+        scrolledWindow.add(self._view)
+        if widthRequest is not None:
+            scrolledWindow.set_size_request(widthRequest, -1)
+        # FIXME: these should be constants in common.py
+        scrolledWindow.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC,
+                                  gtk.PolicyType.AUTOMATIC if pygobject
+                                  else gtk.POLICY_AUTOMATIC)
+        scrolledWindow.set_shadow_type(gtk.ShadowType.IN if pygobject
+                                       else gtk.SHADOW_IN)
+
+        super(FlightList, self).__init__(xalign = 0.5, yalign = 0.0,
+                                         xscale = 0.0, yscale = 1.0)
+        self.add(scrolledWindow)
+
+    @property
+    def selectedIndex(self):
+        """Get the index of the selected entry, if any."""
+        selection = self._view.get_selection()
+        (model, iter) = selection.get_selected()
+        if iter is None:
+            return None
+        else:
+            path = model.get_path(iter)
+            [index] = path.get_indices() if pygobject else path
+            return index
+
+    def clear(self):
+        """Clear the model."""
+        self._model.clear()
+
+    def addFlight(self, flight):
+        """Add the given booked flight."""
+        values = []
+        for columnDescriptor in self._columnDescriptors:
+            values.append(columnDescriptor.getValueFrom(flight))
+        self._model.append(values)
+
+    def _rowActivated(self, flightList, path, column):
+        """Called when a row is selected."""
+        self.emit("row-activated", self.selectedIndex)
+
+    def _buttonPressEvent(self, widget, event):
+        """Called when a mouse button is pressed or released."""
+        if event.type!=EVENT_BUTTON_PRESS or event.button!=3:
+            return
+
+        (path, _, _, _) = self._view.get_path_at_pos(int(event.x),
+                                                     int(event.y))
+        selection = self._view.get_selection()
+        selection.unselect_all()
+        selection.select_path(path)
+
+        if self._popupMenu is None:
+            self._popupMenu = self._popupMenuProducer()
+        menu = self._popupMenu
+        if pygobject:
+            menu.popup(None, None, None, None, event.button, event.time)
+        else:
+            menu.popup(None, None, None, event.button, event.time)
+
+    def _selectionChanged(self, selection):
+        """Called when the selection has changed."""
+        self.emit("selection-changed", self.selectedIndex)
+
+#-------------------------------------------------------------------------------
+
+gobject.signal_new("row-activated", FlightList, gobject.SIGNAL_RUN_FIRST,
+                   None, (int,))
+
+gobject.signal_new("selection-changed", FlightList, gobject.SIGNAL_RUN_FIRST,
+                   None, (object,))
+
+#-----------------------------------------------------------------------------
