Ignore:
Timestamp:
06/18/17 11:17:51 (7 years ago)
Author:
István Váradi <ivaradi@…>
Branch:
default
Phase:
public
Message:

Flight booking works (re #304).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/mlx/gui/timetable.py

    r858 r859  
    55from mlx.gui.common import *
    66from flightlist import ColumnDescriptor
     7from mlx.rpc import ScheduledFlight
    78
    89import mlx.const as const
    910
    1011import datetime
     12import random
    1113
    1214#-----------------------------------------------------------------------------
     
    3840                         "%02d:%02d" % (duration/3600,
    3941                                        (duration%3600)/60)),
    40         ColumnDescriptor("spec", xstr("timetable_vip"), type = bool,
     42        ColumnDescriptor("type", xstr("timetable_vip"), type = bool,
    4143                         renderer = _getVIPRenderer(),
    4244                         sortable = True,
    43                          convertFn = lambda spec, flight: spec==1)
     45                         convertFn = lambda type, flight:
     46                             type==ScheduledFlight.TYPE_VIP)
    4447    ]
     48
     49    columnOrdering = ["callsign", "aircraftType",
     50                      "date", "departureTime", "arrivalTime",
     51                      "departureICAO", "arrivalICAO", "duration", "type"]
    4552
    4653    @staticmethod
     
    4855        """Determine if the given flight is selected by the given
    4956        filtering conditions."""
    50         return ((regularEnabled and flight.spec==0) or \
    51                 (vipEnabled and flight.spec==1)) and \
     57        return ((regularEnabled and flight.type==ScheduledFlight.TYPE_NORMAL) or \
     58                (vipEnabled and flight.type==ScheduledFlight.TYPE_VIP)) and \
    5259               flight.aircraftType in aircraftTypes
    5360
     
    7582        self._view = gtk.TreeView(self._model)
    7683
     84        self._tooltips = gtk.Tooltips()
     85        self._tooltips.disable()
     86        self._view.connect("motion-notify-event", self._updateTooltip)
     87
    7788        flightPairIndexColumn = gtk.TreeViewColumn()
    7889        flightPairIndexColumn.set_visible(False)
     
    8394            column = columnDescriptor.getViewColumn(index)
    8495            self._view.append_column(column)
     96            self._model.set_sort_func(index, self._compareFlights,
     97                                      columnDescriptor.attribute)
    8598            index += 1
    8699
     
    110123
    111124    @property
     125    def selectedIndexes(self):
     126        """Get the indexes of the selected entries, if any.
     127
     128        The indexes are sorted."""
     129        selection = self._view.get_selection()
     130        (model, rows) = selection.get_selected_rows()
     131
     132        indexes = [self._getIndexForPath(path) for path in rows]
     133        indexes.sort()
     134        return indexes
     135
     136    @property
    112137    def hasFlightPairs(self):
    113138        """Determine if the timetable contains any flights."""
     
    125150
    126151        self._flightPairs = flightPairs
     152
     153    def getFlightPair(self, index):
     154        """Get the flight pair with the given index."""
     155        return self._flightPairs[index]
    127156
    128157    def updateList(self, regularEnabled, vipEnabled, types):
     
    140169            index += 1
    141170
     171    def _getIndexForPath(self, path):
     172        """Get the index for the given path."""
     173        iter = self._model.get_iter(path)
     174        return self._model.get_value(iter, 0)
     175
    142176    def _rowActivated(self, flightList, path, column):
    143177        """Called when a row is selected."""
    144         print "_rowActivated"
     178        self.emit("row-activated", self._getIndexForPath(path))
    145179
    146180    def _buttonPressEvent(self, widget, event):
    147181        """Called when a mouse button is pressed or released."""
    148         print "_buttonPressEvent", event
     182        if event.type!=EVENT_BUTTON_PRESS or event.button!=3 or \
     183           self._popupMenuProducer is None:
     184            return
     185
     186        (path, _, _, _) = self._view.get_path_at_pos(int(event.x),
     187                                                     int(event.y))
     188        selection = self._view.get_selection()
     189        selection.unselect_all()
     190        selection.select_path(path)
     191
     192        if self._popupMenu is None:
     193            self._popupMenu = self._popupMenuProducer()
     194        menu = self._popupMenu
     195        if pygobject:
     196            menu.popup(None, None, None, None, event.button, event.time)
     197        else:
     198            menu.popup(None, None, None, event.button, event.time)
    149199
    150200    def _selectionChanged(self, selection):
    151201        """Called when the selection has changed."""
    152         print "_selectionChanged"
     202        self.emit("selection-changed", self.selectedIndexes)
     203
     204    def _compareFlights(self, model, iter1, iter2, mainColumn):
     205        """Compare the flights at the given iterators according to the given
     206        main column."""
     207        index1 = self._model.get_value(iter1, 0)
     208        index2 = self._model.get_value(iter2, 0)
     209
     210        flightPair1 = self._flightPairs[index1]
     211        flightPair2 = self._flightPairs[index2]
     212
     213        result = flightPair1.compareBy(flightPair2, mainColumn)
     214        if result==0:
     215            for column in Timetable.columnOrdering:
     216                if column!=mainColumn:
     217                    result = flightPair1.compareBy(flightPair2, column)
     218                    if result!=0:
     219                        break
     220        return result
     221
     222    def _updateTooltip(self, widget, event):
     223        """Update the tooltip for the position of the given event."""
     224        try:
     225            (path, col, x, y) = widget.get_path_at_pos( int(event.x), int(event.y))
     226            index = self._getIndexForPath(path)
     227
     228            flight = self._flightPairs[index].flight0
     229            comment = flight.comment
     230            date = flight.date
     231
     232            if comment or date!=const.defaultDate:
     233                text = ""
     234                if comment:
     235                    text = comment
     236                if date!=const.defaultDate:
     237                    if text:
     238                        text += "; "
     239                    text += date.strftime("%Y-%m-%d")
     240
     241                self._tooltips.set_tip(widget, text)
     242                self._tooltips.enable()
     243            else:
     244                self._tooltips.set_tip(widget, "")
     245                self._tooltips.disable()
     246        except Exception, e:
     247            print e
     248            self._tooltips.set_tip(widget, "")
     249            self._tooltips.disable()
    153250
    154251#-----------------------------------------------------------------------------
     
    207304#-----------------------------------------------------------------------------
    208305
     306class BookDialog(gtk.Dialog):
     307    """The dialog box to select additional data for a booking."""
     308    def __init__(self, timetableWindow, flightPair, planes):
     309        """Construct the dialog box."""
     310        super(BookDialog, self).__init__(title = WINDOW_TITLE_BASE +
     311                                         " - " +
     312                                         xstr("timetable_book_title"),
     313                                         parent = timetableWindow)
     314        contentArea = self.get_content_area()
     315
     316        frame = gtk.Frame(xstr("timetable_book_frame_title"))
     317        frame.set_size_request(600, -1)
     318
     319        mainAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
     320                                      xscale = 0.0, yscale = 0.0)
     321        mainAlignment.set_padding(padding_top = 16, padding_bottom = 12,
     322                                  padding_left = 8, padding_right = 8)
     323
     324        table = gtk.Table(6, 2)
     325        table.set_row_spacings(8)
     326        table.set_col_spacings(16)
     327
     328        row = 0
     329        label = gtk.Label()
     330        label.set_markup(xstr("timetable_book_callsign"))
     331        label.set_alignment(0.0, 0.5)
     332        table.attach(label, 0, 1, row, row + 1)
     333
     334        text = flightPair.flight0.callsign
     335        if flightPair.flight1 is not None:
     336            text += " / " + flightPair.flight1.callsign
     337        label = gtk.Label(text)
     338        label.set_alignment(0.0, 0.5)
     339        table.attach(label, 1, 2, row, row + 1)
     340
     341        row += 1
     342
     343        label = gtk.Label()
     344        label.set_markup(xstr("timetable_book_from_to"))
     345        label.set_alignment(0.0, 0.5)
     346        table.attach(label, 0, 1, row, row + 1)
     347
     348        text = flightPair.flight0.departureICAO + " - " + \
     349               flightPair.flight0.arrivalICAO
     350        if flightPair.flight1 is not None:
     351            text += " - " + flightPair.flight1.arrivalICAO
     352        label = gtk.Label(text)
     353        label.set_alignment(0.0, 0.5)
     354        table.attach(label, 1, 2, row, row + 1)
     355
     356        row += 1
     357
     358        if flightPair.flight0.type==ScheduledFlight.TYPE_VIP and \
     359           flightPair.flight0.date!=const.defaultDate:
     360            label = gtk.Label()
     361            label.set_markup(xstr("timetable_book_flightDate"))
     362            label.set_use_underline(True)
     363            label.set_alignment(0.0, 0.5)
     364            table.attach(label, 0, 1, row, row + 1)
     365
     366            self._flightDate = gtk.Button()
     367            self._flightDate.connect("clicked", self._flightDateClicked)
     368            self._flightDate.set_tooltip_text(xstr("timetable_book_flightDate_tooltip"))
     369            label.set_mnemonic_widget(self._flightDate)
     370
     371            table.attach(self._flightDate, 1, 2, row, row + 1)
     372
     373            self._calendarWindow = calendarWindow = CalendarWindow()
     374            calendarWindow.set_transient_for(self)
     375            calendarWindow.connect("delete-event", self._calendarWindowDeleted)
     376            calendarWindow.connect("date-selected", self._calendarWindowDateSelected)
     377
     378            self._setDate(flightPair.flight0.date)
     379
     380            row += 1
     381        else:
     382            self._flightDate = None
     383            self._calendarWindow = None
     384
     385        label = gtk.Label()
     386        label.set_markup(xstr("timetable_book_dep_arr"))
     387        label.set_alignment(0.0, 0.5)
     388        table.attach(label, 0, 1, row, row + 1)
     389
     390        text = str(flightPair.flight0.departureTime) + " - " + \
     391               str(flightPair.flight0.arrivalTime)
     392        if flightPair.flight1 is not None:
     393            text += " / " + str(flightPair.flight1.departureTime) + " - " + \
     394                    str(flightPair.flight1.arrivalTime)
     395        label = gtk.Label(text)
     396        label.set_alignment(0.0, 0.5)
     397        table.attach(label, 1, 2, row, row + 1)
     398
     399        row += 1
     400
     401        label = gtk.Label()
     402        label.set_markup(xstr("timetable_book_duration"))
     403        label.set_alignment(0.0, 0.5)
     404        table.attach(label, 0, 1, row, row + 1)
     405
     406
     407        duration = flightPair.flight0.duration
     408        text = "%02d:%02d" % (duration/3600, (duration%3600)/60)
     409        if flightPair.flight1 is not None:
     410            duration = flightPair.flight0.duration
     411            text += " / %02d:%02d" % (duration/3600, (duration%3600)/60)
     412        label = gtk.Label(text)
     413        label.set_alignment(0.0, 0.5)
     414        table.attach(label, 1, 2, row, row + 1)
     415
     416        row += 2
     417
     418        label = gtk.Label()
     419        label.set_markup(xstr("timetable_book_tailNumber"))
     420        label.set_alignment(0.0, 0.5)
     421        table.attach(label, 0, 1, row, row + 1)
     422
     423        self._planes = planes
     424        tailNumbersModel = gtk.ListStore(str)
     425        for plane in planes:
     426            tailNumbersModel.append((plane.tailNumber,))
     427
     428        self._tailNumber = gtk.ComboBox(model = tailNumbersModel)
     429        renderer = gtk.CellRendererText()
     430        self._tailNumber.pack_start(renderer, True)
     431        self._tailNumber.add_attribute(renderer, "text", 0)
     432        self._tailNumber.set_tooltip_text(xstr("timetable_book_tailNumber_tooltip"))
     433        self._tailNumber.set_active(random.randint(0, len(planes)-1))
     434
     435        table.attach(self._tailNumber, 1, 2, row, row + 1)
     436
     437        mainAlignment.add(table)
     438
     439        frame.add(mainAlignment)
     440        contentArea.pack_start(frame, True, True, 4)
     441
     442        self.add_button(xstr("button_cancel"), RESPONSETYPE_CANCEL)
     443
     444        self._okButton = self.add_button(xstr("button_book"), RESPONSETYPE_OK)
     445        self._okButton.set_use_underline(True)
     446        self._okButton.set_can_default(True)
     447
     448    @property
     449    def plane(self):
     450        """Get the currently selected plane."""
     451        return self._planes[self._tailNumber.get_active()]
     452
     453    @property
     454    def date(self):
     455        """Get the flight date, if selected."""
     456        return None if self._calendarWindow is None \
     457          else self._calendarWindow.getDate()
     458
     459    def _setDate(self, date):
     460        """Set the date to the given one."""
     461        self._flightDate.set_label(date.strftime("%Y-%m-%d"))
     462        self._calendarWindow.setDate(date)
     463
     464    def _flightDateClicked(self, button):
     465        """Called when the flight date button is clicked."""
     466        self._calendarWindow.set_position(gtk.WIN_POS_MOUSE)
     467        self.set_focus(self._calendarWindow)
     468        self._calendarWindow.show_all()
     469
     470    def _calendarWindowDeleted(self, window, event):
     471        """Called when the flight date window is deleted."""
     472        self._calendarWindow.hide()
     473
     474    def _calendarWindowDateSelected(self, window):
     475        """Called when the flight date window is deleted."""
     476        self._calendarWindow.hide()
     477        date = window.getDate()
     478        self._flightDate.set_label(date.strftime("%Y-%m-%d"))
     479
     480#-----------------------------------------------------------------------------
     481
    209482class TimetableWindow(gtk.Window):
    210483    """The window to display the timetable."""
     
    255528
    256529        self._flightDate = gtk.Button()
     530        self._flightDate.connect("clicked", self._flightDateClicked)
    257531        self._flightDate.connect("clicked", self._flightDateClicked)
    258532        self._flightDate.set_tooltip_text(xstr("timetable_flightdate_tooltip"))
     
    317591        vbox.pack_start(filterAlignment, False, False, 2)
    318592
    319         self._timetable = Timetable()
     593        self._timetable = Timetable(popupMenuProducer =
     594                                        self._createTimetablePopupMenu)
     595        self._timetable.connect("row-activated", self._rowActivated)
     596        self._timetable.connect("selection-changed", self._selectionChanged)
    320597        vbox.pack_start(self._timetable, True, True, 2)
    321598
    322         alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
     599        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5,
    323600                                  xscale = 0.0, yscale = 0.0)
    324         self._closeButton = gtk.Button(xstr("button_ok"))
     601        buttonBox = gtk.HBox()
     602
     603        self._bookButton = gtk.Button(xstr("button_book"))
     604        self._bookButton.set_use_underline(True)
     605        self._bookButton.set_can_default(True)
     606        self._bookButton.connect("clicked", self._bookClicked)
     607        self._bookButton.set_sensitive(False)
     608        buttonBox.pack_start(self._bookButton, False, False, 4);
     609
     610        self._closeButton = gtk.Button(xstr("button_close"))
     611        self._closeButton.set_use_underline(True)
    325612        self._closeButton.connect("clicked", self._closeClicked)
    326         alignment.add(self._closeButton)
     613        buttonBox.pack_start(self._closeButton, False, False, 4);
     614
     615        alignment.add(buttonBox)
    327616        vbox.pack_start(alignment, False, False, 2)
    328617
     
    337626
    338627        self.setDate(datetime.date.today())
     628
     629        self._flightPairToBook = None
    339630
    340631    @property
     
    423714                                   self.isVIPEnabled,
    424715                                   aircraftTypes)
     716
     717    def _bookClicked(self, button):
     718        """Called when the book button has been clicked."""
     719        self._book(self._timetable.getFlightPair(self._timetable.selectedIndexes[0]))
     720
     721    def _rowActivated(self, timetable, index):
     722        """Called when a row has been activated (e.g. double-clicked) in the
     723        timetable."""
     724        self._book(self._timetable.getFlightPair(index))
     725
     726    def _selectionChanged(self, timetable, indexes):
     727        """Called when the selection has changed.
     728
     729        It sets the sensitivity of the book button based on whether a row is
     730        selected or not."""
     731        self._bookButton.set_sensitive(len(indexes)>0)
     732
     733    def _book(self, flightPair):
     734        """Try to book the given flight pair."""
     735        self._flightPairToBook = flightPair
     736        self._gui.getFleet(callback = self._continueBook,
     737                           busyCallback = self._busyCallback)
     738
     739    def _busyCallback(self, busy):
     740        """Called when the busy state has changed."""
     741        self.set_sensitive(not busy)
     742
     743    def _continueBook(self, fleet):
     744        """Continue booking, once the fleet is available."""
     745        flightPair = self._flightPairToBook
     746        aircraftType = flightPair.flight0.aircraftType
     747        planes = [plane for plane in fleet
     748                  if plane.aircraftType == aircraftType]
     749        planes.sort(cmp = lambda p1, p2: cmp(p1.tailNumber, p2.tailNumber))
     750
     751        dialog = BookDialog(self, flightPair, planes)
     752        dialog.show_all()
     753        result = dialog.run()
     754        dialog.hide()
     755        if result==RESPONSETYPE_OK:
     756            flightIDs = [flightPair.flight0.id]
     757            if flightPair.flight1 is not None:
     758                flightIDs.append(flightPair.flight1.id)
     759
     760            date = dialog.date
     761            if date is None:
     762                date = self._calendarWindow.getDate()
     763
     764            self._gui.bookFlights(self._bookFlightsCallback,
     765                                  flightIDs, date, dialog.plane.tailNumber,
     766                                  busyCallback = self._busyCallback)
     767
     768    def _bookFlightsCallback(self, returned, result):
     769        """Called when the booking has finished."""
     770        if returned:
     771            dialog = gtk.MessageDialog(parent = self,
     772                                       type = MESSAGETYPE_INFO,
     773                                       message_format = xstr("bookflights_successful"))
     774            dialog.format_secondary_markup(xstr("bookflights_successful_secondary"))
     775        else:
     776            dialog = gtk.MessageDialog(parent = self,
     777                                       type = MESSAGETYPE_ERROR,
     778                                       message_format = xstr("bookflights_failed"))
     779            dialog.format_secondary_markup(xstr("bookflights_failed_secondary"))
     780
     781        dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
     782        dialog.set_title(WINDOW_TITLE_BASE)
     783
     784        dialog.run()
     785        dialog.hide()
     786
     787    def _createTimetablePopupMenu(self):
     788        """Get the popuop menu for the timetable."""
     789        menu = gtk.Menu()
     790
     791        menuItem = gtk.MenuItem()
     792        menuItem.set_label(xstr("timetable_popup_book"))
     793        menuItem.set_use_underline(True)
     794        menuItem.connect("activate", self._popupBook)
     795        menuItem.show()
     796
     797        menu.append(menuItem)
     798
     799        return menu
     800
     801    def _popupBook(self, menuItem):
     802        """Try to book the given flight pair."""
     803        index = self._timetable.selectedIndexes[0]
     804        self._book(self._timetable.getFlightPair(index))
Note: See TracChangeset for help on using the changeset viewer.