/** * Copyright (C) 2011 Brian Ferris <bdferris@onebusaway.org> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.onebusaway.webapp.gwt.oba_application.view; import java.util.ArrayList; import java.util.List; import org.onebusaway.transit_data.model.oba.LocalSearchResult; import org.onebusaway.webapp.gwt.common.MapOverlayManager; import org.onebusaway.webapp.gwt.common.widgets.DivPanel; import org.onebusaway.webapp.gwt.common.widgets.DivWidget; import org.onebusaway.webapp.gwt.common.widgets.SpanPanel; import org.onebusaway.webapp.gwt.common.widgets.SpanWidget; import org.onebusaway.webapp.gwt.oba_application.control.CommonControl; import org.onebusaway.webapp.gwt.oba_application.control.StateEvent; import org.onebusaway.webapp.gwt.oba_application.control.StateEventListener; import org.onebusaway.webapp.gwt.oba_application.control.state.PlacesChangedState; import org.onebusaway.webapp.gwt.oba_application.control.state.SearchCompleteState; import org.onebusaway.webapp.gwt.oba_application.control.state.SearchProgressState; import org.onebusaway.webapp.gwt.oba_application.control.state.SearchStartedState; import org.onebusaway.webapp.gwt.oba_application.control.state.SelectedPlaceChangedState; import org.onebusaway.webapp.gwt.oba_application.control.state.State; import org.onebusaway.webapp.gwt.oba_application.control.state.TripPlansState; import org.onebusaway.webapp.gwt.oba_application.model.PagedResultsModel; import org.onebusaway.webapp.gwt.oba_application.model.TimedLocalSearchResult; import org.onebusaway.webapp.gwt.oba_application.resources.OneBusAwayCssResource; import org.onebusaway.webapp.gwt.oba_application.resources.OneBusAwayStandardResources; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.dom.client.ClickHandler; import com.google.gwt.maps.client.MapWidget; import com.google.gwt.maps.client.event.MarkerClickHandler; import com.google.gwt.maps.client.geom.LatLng; import com.google.gwt.maps.client.geom.LatLngBounds; import com.google.gwt.maps.client.geom.Point; import com.google.gwt.maps.client.geom.Size; import com.google.gwt.maps.client.overlay.Icon; import com.google.gwt.maps.client.overlay.Marker; import com.google.gwt.maps.client.overlay.MarkerOptions; import com.google.gwt.maps.client.overlay.Overlay; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.user.client.ui.Anchor; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.Grid; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.Widget; public class ResultsTablePresenter implements StateEventListener { private static OneBusAwayCssResource _css = OneBusAwayStandardResources.INSTANCE.getCss(); private static final String ROW_LABELS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; private PagedResultsModel _model; private CommonControl _control; private MapOverlayManager _mapOverlayManager; private FlowPanel _panel; private Widget _filterWidget; private FlowPanel _tablePanel = new FlowPanel(); private FlowPanel _progressPanel = new FlowPanel(); //private ProgressBar _progressBar = new ProgressBar(0, 1); private List<Overlay> _overlays = new ArrayList<Overlay>(); private boolean _minimized = false; public ResultsTablePresenter() { initializeWidget(); } public void setResultsModel(PagedResultsModel model) { _model = model; } public void setMapOverlayManager(MapOverlayManager manager) { _mapOverlayManager = manager; } public void setControl(CommonControl control) { _control = control; } public void setResultsFilterWidget(Widget widget) { _filterWidget = widget; } public Widget getWidget() { if (_panel == null) initializeWidget(); return _panel; } public void handleUpdate(StateEvent event) { State state = event.getState(); if (state instanceof SearchStartedState) { _progressPanel.setVisible(true); //_progressBar.setProgress(0); clear(); } else if (state instanceof SearchProgressState) { SearchProgressState sps = (SearchProgressState) state; //_progressBar.setProgress(sps.getPercentComplete()); } else if (state instanceof SearchCompleteState) { _progressPanel.setVisible(false); } else if (state instanceof PlacesChangedState) { PlacesChangedState pcs = (PlacesChangedState) state; PagedResultsModel model = pcs.getModel(); if (model.getSelectedResult() == null) _minimized = false; refresh(); } else if (state instanceof SelectedPlaceChangedState) { SelectedPlaceChangedState pcs = (SelectedPlaceChangedState) state; if (pcs.getSelectedResult() == null) { _minimized = false; refresh(); } } else if (state instanceof TripPlansState) { _minimized = true; refresh(); } } private void initializeWidget() { _panel = new FlowPanel(); _panel.addStyleName(_css.ResultsTableWidget()); //_progressBar.setTextFormatter(new SearchProgressTextFormatter()); //_progressPanel.add(_progressBar); _progressPanel.setVisible(false); _panel.add(_progressPanel); _tablePanel.addStyleName(_css.ResultsTableWidgetTablePanel()); _panel.add(_tablePanel); } private void refresh() { clear(); if (_minimized) { DivPanel panel = new DivPanel(); panel.addStyleName(_css.ResultsTableWidgetMinimized()); _tablePanel.add(panel); Anchor showAllEntries = new Anchor("Show all results..."); showAllEntries.addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { _control.clearActiveSearchResult(); } }); panel.add(showAllEntries); constructAttributionPanel(); } else { _tablePanel.add(_filterWidget); List<TimedLocalSearchResult> entries = _model.getActiveEntries(); Grid grid = new Grid(entries.size() + 1, 4); grid.addStyleName(_css.ResultsTableWidgetResultsTable()); grid.setText(0, 0, ""); grid.setWidget(0, 1, getTableHeaderWidget("Name", EResultsSort.NAME)); grid.setWidget(0, 2, getTableHeaderWidget("Rating", EResultsSort.RATING)); grid.setWidget(0, 3, getTableHeaderWidget("Distance", EResultsSort.DISTANCE)); grid.getRowFormatter().addStyleName(0, "ResultsTableWidget-ResultsTable-HeaderRow"); grid.getCellFormatter().addStyleName(0, 1, "ResultsTableWidget-ResultsTable-HeaderRow-Name"); grid.getCellFormatter().addStyleName(0, 2, "ResultsTableWidget-ResultsTable-HeaderRow-Rating"); grid.getCellFormatter().addStyleName(0, 3, "ResultsTableWidget-ResultsTable-HeaderRow-Distance"); int index = 0; LatLngBounds bounds = LatLngBounds.newInstance(); for (TimedLocalSearchResult tlsr : entries) { LocalSearchResult entry = tlsr.getLocalSearchResult(); int tableRow = index + 1; EntryClickHandler handler = new EntryClickHandler(tlsr); String labelPre = index < ROW_LABELS.length() ? (ROW_LABELS.charAt(index) + "") : ""; grid.setWidget(tableRow, 0, new SpanWidget(labelPre)); grid.getCellFormatter().addStyleName(tableRow, 0, "ResultsTableWidget-ResultsTable-LabelColumn"); Anchor name = new Anchor(entry.getName()); name.addClickHandler(handler); grid.setWidget(tableRow, 1, name); grid.getCellFormatter().addStyleName(tableRow, 1, "ResultsTableWidget-ResultsTable-NameColumn"); if (entry.getRatingUrlSmall() != null) { Image image = new Image(entry.getRatingUrlSmall()); grid.setWidget(tableRow, 2, image); } else { String rating = entry.getRating() + "/" + entry.getMaxRating(); grid.setText(tableRow, 2, rating); } grid.getCellFormatter().addStyleName(tableRow, 2, "ResultsTableWidget-ResultsTable-RatingColumn"); int minutes = tlsr.getTime() / 60; String minLabel = minutes + " " + (minutes == 1 ? " min" : " mins"); grid.setText(tableRow, 3, minLabel); grid.getCellFormatter().addStyleName(tableRow, 3, "ResultsTableWidget-ResultsTable-TimeColumn"); grid.getRowFormatter().addStyleName(tableRow, "ResultsTableWidget-ResultsTable-ResultRow"); Marker marker = getMarker(index, entry); marker.addMarkerClickHandler(handler); _mapOverlayManager.addOverlay(marker, 10, 20); _overlays.add(marker); index++; bounds.extend(marker.getLatLng()); } if (!bounds.isEmpty()) { MapWidget map = _mapOverlayManager.getMapWidget(); LatLngBounds currentView = map.getBounds(); if (!currentView.containsBounds(bounds)) { _mapOverlayManager.setCenterAndZoom(bounds); } } _tablePanel.add(grid); constructItemNavigationPanel(entries); constructAttributionPanel(); } } private void clear() { for (Overlay overlay : _overlays) _mapOverlayManager.removeOverlay(overlay); _overlays.clear(); _tablePanel.clear(); } private Widget getTableHeaderWidget(String name, final EResultsSort sortType) { Anchor widget = new Anchor(name); widget.addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { _model.setSortMode(sortType); } }); if (_model.getSortMode().equals(sortType)) { SpanPanel panel = new SpanPanel(); panel.add(widget); OneBusAwayStandardResources resources = OneBusAwayStandardResources.INSTANCE; ImageResource resource = _model.isReverseSort() ? resources.getTriangleUp() : resources.getTriangleDown(); Image img = new Image(resource.getURL()); panel.add(img); return panel; } return widget; } private Marker getMarker(int index, LocalSearchResult entry) { ImageResource resource = getMarkerResource(index); Icon icon = Icon.newInstance(resource.getURL()); icon.setIconSize(Size.newInstance(24, 31)); icon.setIconAnchor(Point.newInstance(12, 31)); MarkerOptions opts = MarkerOptions.newInstance(); opts.setClickable(true); opts.setIcon(icon); LatLng point = LatLng.newInstance(entry.getLat(), entry.getLon()); return new Marker(point, opts); } private void constructItemNavigationPanel(List<TimedLocalSearchResult> entries) { int totalSize = _model.getTotalSize(); int pageSize = _model.getPageSize(); int pageIndex = _model.getPageIndex(); int totalPages = (totalSize + 9) / pageSize; int fromIndex = pageIndex * pageSize + 1; int toIndex = fromIndex + entries.size() - 1; if (totalPages <= 1) return; Grid elements = new Grid(1, 3); elements.addStyleName(_css.ResultsTableWidgetNavigationWidget()); String itemCountStyle = "ItemCount"; DivWidget itemCount = new DivWidget("<span class=\"" + itemCountStyle + "\">" + fromIndex + "</span> to <span class=\"" + itemCountStyle + "\">" + toIndex + "</span> of <span class=\"" + itemCountStyle + "\">" + totalSize + "</span>"); itemCount.addStyleName(_css.ItemCountPanel()); elements.setWidget(0, 0, itemCount); FlowPanel pagesPanel = new FlowPanel(); pagesPanel.addStyleName(_css.PagesPanel()); pagesPanel.add(new SpanWidget("Go to page ")); int lastPage = -1; for (int i = 0; i < totalPages; i++) { final int pIndex = i; boolean display = (i == 0) || (i == totalPages - 1) || (Math.abs(i - pageIndex) < 3); if (display) { if (lastPage != -1) { String value = pIndex - lastPage == 1 ? "|" : "..."; pagesPanel.add(new SpanWidget(value)); } String label = Integer.toString(pIndex + 1); Widget page = (i == pageIndex) ? new SpanWidget(label) : new Anchor(label); pagesPanel.add(page); page.addStyleName(_css.PageCount()); if (i == pageIndex) { page.addStyleName(_css.CurrentPage()); } else { ((Anchor) page).addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { _model.setPageIndex(pIndex); } }); } lastPage = pIndex; } } elements.setWidget(0, 1, pagesPanel); FlowPanel prevNextPagePanel = new FlowPanel(); prevNextPagePanel.addStyleName(_css.PrevNextPanel()); if (pageIndex > 0) { Anchor prev = new Anchor("Previous"); prevNextPagePanel.add(prev); if (pageIndex > 0) { prev.addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { int pi = _model.getPageIndex(); _model.setPageIndex(pi - 1); } }); } } if (pageIndex > 0 && pageIndex < totalPages - 1) prevNextPagePanel.add(new SpanWidget("|")); if (pageIndex < totalPages - 1) { Anchor next = new Anchor("Next"); prevNextPagePanel.add(next); if (pageIndex + 1 < totalPages) { next.addClickHandler(new ClickHandler() { public void onClick(ClickEvent arg0) { int pi = _model.getPageIndex(); _model.setPageIndex(pi + 1); } }); } } elements.setWidget(0, 2, prevNextPagePanel); _tablePanel.add(elements); } private void constructAttributionPanel() { DivPanel yelpAttribution = new DivPanel(); yelpAttribution.addStyleName(_css.OneBusAwayYelpPanel()); Anchor anchor = new Anchor( "<img src=\"http://static.px.yelp.com/static/20090202/i/new/developers/reviewsFromYelpRED.gif\"/>", true, "http://www.yelp.com/"); anchor.addStyleName(_css.OneBusAwayYelpLink()); yelpAttribution.add(anchor); _tablePanel.add(yelpAttribution); } /** * Man this is an ugly function, but kind of a necessity with the GWT * ResourceBundle method * * @param index * @return */ private ImageResource getMarkerResource(int index) { OneBusAwayStandardResources r = OneBusAwayStandardResources.INSTANCE; switch (index) { case 0: return r.getMarkerA(); case 1: return r.getMarkerB(); case 2: return r.getMarkerC(); case 3: return r.getMarkerD(); case 4: return r.getMarkerE(); case 5: return r.getMarkerF(); case 6: return r.getMarkerG(); case 7: return r.getMarkerH(); case 8: return r.getMarkerI(); case 9: return r.getMarkerJ(); case 10: return r.getMarkerK(); case 11: return r.getMarkerL(); case 12: return r.getMarkerM(); case 13: return r.getMarkerN(); case 14: return r.getMarkerO(); case 15: return r.getMarkerP(); case 16: return r.getMarkerQ(); case 17: return r.getMarkerR(); case 18: return r.getMarkerS(); case 19: return r.getMarkerT(); case 20: return r.getMarkerU(); case 21: return r.getMarkerV(); case 22: return r.getMarkerW(); case 23: return r.getMarkerX(); case 24: return r.getMarkerY(); case 25: return r.getMarkerZ(); default: return r.getMarker(); } } private class EntryClickHandler implements MarkerClickHandler, ClickHandler { private TimedLocalSearchResult _result; public EntryClickHandler(TimedLocalSearchResult result) { _result = result; } public void onClick(MarkerClickEvent event) { _control.setActiveSearchResult(_result); } public void onClick(ClickEvent arg0) { _control.setActiveSearchResult(_result); } } /* private class SearchProgressTextFormatter extends TextFormatter { @Override protected String getText(ProgressBar bar, double curProgress) { return "Searching..."; } } */ }