/*
* Copyright 2011 Instituto Superior Tecnico
*
* https://fenix-ashes.ist.utl.pt/
*
* This file is part of the vaadin-framework.
*
* The vaadin-framework Infrastructure is free software: you can
* redistribute it and/or modify it under the terms of the GNU Lesser General
* Public License as published by the Free Software Foundation, either version
* 3 of the License, or (at your option) any later version.*
*
* vaadin-framework is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with vaadin-framework. If not, see <http://www.gnu.org/licenses/>.
*
*/
package pt.ist.vaadinframework.ui;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import pt.ist.fenixframework.Atomic;
import pt.ist.vaadinframework.VaadinResourceConstants;
import pt.ist.vaadinframework.VaadinResources;
import com.vaadin.data.Container;
import com.vaadin.data.Container.Filterable;
import com.vaadin.data.Container.Indexed;
import com.vaadin.data.Container.ItemSetChangeEvent;
import com.vaadin.data.Container.ItemSetChangeListener;
import com.vaadin.data.Container.ItemSetChangeNotifier;
import com.vaadin.data.Container.Sortable;
import com.vaadin.data.Container.Viewer;
import com.vaadin.data.Item;
import com.vaadin.data.Property.ValueChangeEvent;
import com.vaadin.data.Property.ValueChangeListener;
import com.vaadin.data.util.IndexedContainer;
import com.vaadin.data.util.filter.SimpleStringFilter;
import com.vaadin.data.validator.IntegerValidator;
import com.vaadin.event.FieldEvents.TextChangeEvent;
import com.vaadin.event.FieldEvents.TextChangeListener;
import com.vaadin.terminal.ThemeResource;
import com.vaadin.ui.Alignment;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.Component;
import com.vaadin.ui.ComponentContainer;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Panel;
import com.vaadin.ui.Select;
import com.vaadin.ui.TextField;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.BaseTheme;
import com.vaadin.ui.themes.Reindeer;
public class PaginatedSorterViewer extends GridLayout implements Viewer, VaadinResourceConstants {
interface PageChangeListener extends Serializable {
public void pageChanged(PageChangeEvent event);
}
public class PageChangeEvent implements Serializable {
final PaginatedSorterViewer viewer;
public PageChangeEvent(PaginatedSorterViewer table) {
this.viewer = table;
}
public PaginatedSorterViewer getTable() {
return viewer;
}
public int getCurrentPage() {
return viewer.getCurrentPage();
}
public int getTotalAmountOfPages() {
return viewer.getTotalAmountOfPages();
}
}
public class PageLengthControl extends HorizontalLayout {
public PageLengthControl(int pageLength) {
setSpacing(true);
addComponent(new Label(VaadinResources.getString(COMMONS_ITEMS_PER_PAGE_LABEL) + ":"));
Select lengthSelect = new Select();
addComponent(lengthSelect);
lengthSelect.addItem(5);
lengthSelect.addItem(10);
lengthSelect.addItem(25);
lengthSelect.addItem(50);
lengthSelect.addItem(100);
lengthSelect.setImmediate(true);
lengthSelect.setNullSelectionAllowed(false);
lengthSelect.setWidth("50px");
lengthSelect.select(pageLength);
lengthSelect.addListener(new ValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent event) {
setPageLength((Integer) event.getProperty().getValue());
}
});
setPageLength(pageLength);
PaginatedSorterViewer.this.addListener(new PageChangeListener() {
@Override
public void pageChanged(PageChangeEvent event) {
setVisible(event.getTotalAmountOfPages() != 1);
}
});
}
}
public class PageChangerControl extends HorizontalLayout {
private final TextField pageSelection;
private final Label totalPagesLabel;
public PageChangerControl() {
setSpacing(true);
final Button first = new Button("<<", new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
setCurrentPage(0);
}
});
first.addStyleName(BaseTheme.BUTTON_LINK);
addComponent(first);
final Button previous = new Button("<", new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
previousPage();
}
});
previous.addStyleName(BaseTheme.BUTTON_LINK);
addComponent(previous);
addComponent(new Label(VaadinResources.getString(COMMONS_PAGE_LABEL) + ":"));
pageSelection = new TextField();
pageSelection.setImmediate(true);
pageSelection.setWidth(20, UNITS_PIXELS);
pageSelection.setValue(String.valueOf(getCurrentPage()));
pageSelection.addValidator(new IntegerValidator(null));
pageSelection.addStyleName(Reindeer.TEXTFIELD_SMALL);
pageSelection.addListener(new ValueChangeListener() {
@Override
public void valueChange(ValueChangeEvent event) {
if (pageSelection.isValid() && pageSelection.getValue() != null) {
setCurrentPage(Integer.valueOf(pageSelection.getValue().toString()));
}
}
});
addComponent(pageSelection);
addComponent(new Label("/"));
totalPagesLabel = new Label();
addComponent(totalPagesLabel);
final Button next = new Button(">", new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
nextPage();
}
});
next.addStyleName(BaseTheme.BUTTON_LINK);
addComponent(next);
final Button last = new Button(">>", new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
setCurrentPage(getTotalAmountOfPages());
}
});
last.addStyleName(BaseTheme.BUTTON_LINK);
addComponent(last);
PaginatedSorterViewer.this.addListener(new PageChangeListener() {
@Override
public void pageChanged(PageChangeEvent event) {
pageSelection.setValue(event.getCurrentPage());
pageSelection.setWidth(String.valueOf(event.getTotalAmountOfPages()).length() * 8 + 4, UNITS_PIXELS);
totalPagesLabel.setValue(event.getTotalAmountOfPages());
setVisible(event.getTotalAmountOfPages() != 1);
}
});
}
}
public class GroupControl extends Button {
private boolean ascending;
private final Object propertyId;
public GroupControl(final Object propertyId, boolean initialAscending, String label) {
this.ascending = initialAscending;
this.propertyId = propertyId;
setCaption(label);
addStyleName(BaseTheme.BUTTON_LINK);
addListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
if (GroupControl.this.equals(currentGrouper)) {
ascending = !ascending;
} else {
currentGrouper.unselect();
}
currentGrouper = GroupControl.this;
forceGroup();
}
});
forceGroup();
}
private void unselect() {
setIcon(null);
}
private void forceGroup() {
if (ascending) {
setIcon(new ThemeResource("../runo/icons/16/arrow-up.png"));
} else {
setIcon(new ThemeResource("../runo/icons/16/arrow-down.png"));
}
content.setGroupPropertyId(propertyId);
if (currentSorter == null) {
sort(new Object[] { propertyId }, new boolean[] { ascending });
} else {
sort(new Object[] { propertyId, currentSorter.propertyId }, new boolean[] { ascending, currentSorter.ascending });
}
}
}
public class SorterControl extends Button {
private boolean ascending;
private final Object propertyId;
public SorterControl(final Object propertyId, boolean initialAscending, String label) {
this.ascending = initialAscending;
this.propertyId = propertyId;
setCaption(label);
addStyleName(BaseTheme.BUTTON_LINK);
addListener(new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
if (SorterControl.this.equals(currentSorter)) {
ascending = !ascending;
} else {
currentSorter.unselect();
}
currentSorter = SorterControl.this;
forceSort();
}
});
forceSort();
}
private void unselect() {
setIcon(null);
}
private void forceSort() {
if (ascending) {
setIcon(new ThemeResource("../runo/icons/16/arrow-up.png"));
} else {
setIcon(new ThemeResource("../runo/icons/16/arrow-down.png"));
}
if (currentGrouper == null) {
sort(new Object[] { propertyId }, new boolean[] { ascending });
} else {
sort(new Object[] { currentGrouper.propertyId, propertyId },
new boolean[] { currentGrouper.ascending, ascending });
}
}
}
public class FilterControl extends TextField {
public FilterControl(final Object propertyId, String label) {
setTextChangeEventMode(TextChangeEventMode.LAZY);
setTextChangeTimeout(200);
setInputPrompt(label);
addStyleName(Reindeer.TEXTFIELD_SMALL);
addListener(new TextChangeListener() {
@Override
public void textChange(TextChangeEvent event) {
filter(propertyId, event.getText());
}
});
}
}
public static interface ContentViewerFactory extends Serializable {
public Viewer makeViewer();
}
public static class GroupWrapper extends VerticalLayout implements Viewer, ItemSetChangeListener {
private Object groupPropertyId;
private final ContentViewerFactory factory;
private Container container;
public GroupWrapper(ContentViewerFactory factory) {
this.factory = factory;
setSpacing(true);
}
public void setGroupPropertyId(Object groupPropertyId) {
this.groupPropertyId = groupPropertyId;
}
public Object getGroupPropertyId() {
return groupPropertyId;
}
@Override
public void setContainerDataSource(Container newDataSource) {
if (container != newDataSource) {
if (container != null) {
if (container instanceof Container.ItemSetChangeNotifier) {
((Container.ItemSetChangeNotifier) container).removeListener(this);
}
}
// Assigns new data source
container = newDataSource;
// Adds listeners
if (container != null) {
if (container instanceof Container.ItemSetChangeNotifier) {
((Container.ItemSetChangeNotifier) container).addListener(this);
}
}
refreshComponents(container);
}
}
@Override
public Container getContainerDataSource() {
return container;
}
@Override
public void containerItemSetChange(ItemSetChangeEvent event) {
this.container = event.getContainer();
refreshComponents(event.getContainer());
}
@Atomic
private void refreshComponents(Container container) {
removeAllComponents();
if (groupPropertyId != null) {
Map<Object, Container> groups = new HashMap<Object, Container>();
ArrayList<Object> ids = new ArrayList<Object>();
for (Object itemId : container.getItemIds()) {
Object value = container.getContainerProperty(itemId, groupPropertyId).getValue();
if (!groups.containsKey(value)) {
ids.add(value);
groups.put(value, cloneContainerStructure(container));
}
Item item = groups.get(value).addItem(itemId);
for (Object property : container.getContainerPropertyIds()) {
item.getItemProperty(property).setValue(container.getContainerProperty(itemId, property).getValue());
}
}
// if (groups.size() > 1) {
for (Object groupId : ids) {
Panel section = new Panel(groupId != null ? groupId.toString() : null);
addComponent(section);
Viewer viewer = factory.makeViewer();
viewer.setContainerDataSource(groups.get(groupId));
section.addComponent((Component) viewer);
}
// } else if (groups.size() == 1) {
// Viewer viewer = factory.makeViewer();
// viewer.setContainerDataSource(groups.values().iterator().next());
// addComponent((Component) viewer);
// }
} else {
Viewer viewer = factory.makeViewer();
viewer.setContainerDataSource(container);
addComponent((Component) viewer);
}
}
}
static class ControlVisibilityListener implements ComponentAttachListener, ComponentDetachListener {
@Override
public void componentDetachedFromContainer(ComponentDetachEvent event) {
determineVisibility(event.getContainer());
}
@Override
public void componentAttachedToContainer(ComponentAttachEvent event) {
determineVisibility(event.getContainer());
}
public static void determineVisibility(ComponentContainer controls) {
controls.setVisible(controls.getComponentIterator().hasNext());
}
}
private GroupWrapper content;
private GroupControl currentGrouper;
private SorterControl currentSorter;
private PageLengthControl pageLengthControl;
private PageChangerControl pageChangerControl;
private Indexed realContainer;
private IndexedContainer shownContainer;
private int index = 0;
private int pageLength = 0;
private List<PageChangeListener> listeners = null;
public PaginatedSorterViewer(ContentViewerFactory factory) {
super(3, 3);
setWidth(100, UNITS_PERCENTAGE);
setSpacing(true);
this.content = new GroupWrapper(factory);
Component topleft = makeControlsLayout();
addComponent(topleft);
setComponentAlignment(topleft, Alignment.MIDDLE_LEFT);
Component topcenter = makeControlsLayout();
addComponent(topcenter);
setComponentAlignment(topcenter, Alignment.MIDDLE_CENTER);
Component topright = makeControlsLayout();
addComponent(topright);
setComponentAlignment(topright, Alignment.MIDDLE_RIGHT);
addComponent(content, 0, 1, 2, 1);
Component bottomleft = makeControlsLayout();
addComponent(bottomleft);
setComponentAlignment(bottomleft, Alignment.MIDDLE_LEFT);
Component bottomcenter = makeControlsLayout();
addComponent(bottomcenter);
setComponentAlignment(bottomcenter, Alignment.MIDDLE_CENTER);
Component bottomright = makeControlsLayout();
addComponent(bottomright);
setComponentAlignment(bottomright, Alignment.MIDDLE_RIGHT);
}
private static Component makeControlsLayout() {
HorizontalLayout layout = new HorizontalLayout();
layout.setSpacing(true);
layout.setVisible(false);
ControlVisibilityListener controlVisibilityListener = new ControlVisibilityListener();
layout.addListener((ComponentAttachListener) controlVisibilityListener);
layout.addListener((ComponentDetachListener) controlVisibilityListener);
return layout;
}
public void setContentViewerFactory(ContentViewerFactory factory) {
removeComponent(content);
content = new GroupWrapper(factory);
addComponent(content, 0, 1, 2, 1);
setContainerDataSource(getContainerDataSource());
}
public void setPagination() {
setPagination(25);
}
public void setPagination(int pageLength) {
setPagination(pageLength, Alignment.BOTTOM_LEFT, Alignment.BOTTOM_RIGHT);
}
public void setPagination(int pageLength, Alignment pageLengthPosition, Alignment pageChangerPosition) {
pageLengthControl = new PageLengthControl(pageLength);
addControls(pageLengthControl, pageLengthPosition);
pageChangerControl = new PageChangerControl();
addControls(pageChangerControl, pageChangerPosition);
}
public void setGrouper(Object[] grouperIds, boolean[] initialAscending, String[] grouperLabels) {
setGrouper(Alignment.TOP_LEFT, grouperIds, initialAscending, grouperLabels);
}
public void setGrouper(Alignment position, Object[] grouperIds, boolean[] initialAscending, String[] grouperLabels) {
HorizontalLayout controls = new HorizontalLayout();
controls.setSpacing(true);
if (grouperIds.length > 0) {
controls.setVisible(true);
controls.addComponent(new Label(VaadinResources.getString(COMMONS_GROUPBY_LABEL) + ":"));
currentGrouper = new GroupControl(grouperIds[0], initialAscending[0], grouperLabels[0]);
controls.addComponent(currentGrouper);
for (int i = 1; i < grouperIds.length; i++) {
controls.addComponent(new Label("|"));
controls.addComponent(new GroupControl(grouperIds[i], initialAscending[i], grouperLabels[i]));
}
} else {
controls.removeAllComponents();
controls.setVisible(false);
}
addControls(controls, position);
}
public void setSorter(Object[] sorterIds, boolean[] initialAscending, String[] sorterLabels) {
setSorter(Alignment.TOP_LEFT, sorterIds, initialAscending, sorterLabels);
}
public void setSorter(Alignment position, Object[] sorterIds, boolean[] initialAscending, String[] sorterLabels) {
HorizontalLayout controls = new HorizontalLayout();
controls.setSpacing(true);
if (sorterIds.length > 0) {
controls.setVisible(true);
controls.addComponent(new Label(VaadinResources.getString(COMMONS_SORTBY_LABEL) + ":"));
currentSorter = new SorterControl(sorterIds[0], initialAscending[0], sorterLabels[0]);
controls.addComponent(currentSorter);
for (int i = 1; i < sorterIds.length; i++) {
controls.addComponent(new Label("|"));
controls.addComponent(new SorterControl(sorterIds[i], initialAscending[i], sorterLabels[i]));
}
} else {
controls.removeAllComponents();
controls.setVisible(false);
}
addControls(controls, position);
}
public void setFilter(Object[] filterIds, String[] filterLabels) {
setFilter(Alignment.TOP_RIGHT, filterIds, filterLabels);
}
public void setFilter(Alignment position, Object[] filterIds, String[] filterLabels) {
HorizontalLayout controls = new HorizontalLayout();
controls.setSpacing(true);
if (filterIds.length > 0) {
controls.setVisible(true);
controls.addComponent(new Label(VaadinResources.getString(COMMONS_FILTERBY_LABEL) + ":"));
for (int i = 0; i < filterIds.length; i++) {
controls.addComponent(new FilterControl(filterIds[i], filterLabels[i]));
}
} else {
controls.removeAllComponents();
controls.setVisible(false);
}
addControls(controls, position);
}
private void addControls(Component controls, Alignment position) {
HorizontalLayout controlBar;
int x = position.isLeft() ? 0 : (position.isCenter() ? 1 : 2);
int y = position.isTop() ? 0 : 2;
controlBar = (HorizontalLayout) getComponent(x, y);
controlBar.addComponent(controls);
}
@Override
public void setContainerDataSource(Container newDataSource) {
if (newDataSource == null) {
setVisible(false);
return;
}
setVisible(true);
if (!(newDataSource instanceof Container.Indexed)) {
throw new IllegalArgumentException("Can only use containers that implement Container.Indexed");
}
if (!(newDataSource instanceof Container.ItemSetChangeNotifier)) {
throw new IllegalArgumentException("Can only use containers that implement Container.ItemSetChangeNotifier");
}
this.realContainer = (Indexed) newDataSource;
if (currentGrouper != null) {
currentGrouper.forceGroup();
}
if (currentSorter != null) {
currentSorter.forceSort();
}
// if (pageChangerControl != null) {
// pageChangerControl.update();
// }
cutShownContainer();
((ItemSetChangeNotifier) realContainer).addListener(new ItemSetChangeListener() {
@Override
public void containerItemSetChange(ItemSetChangeEvent event) {
cutShownContainer();
}
});
}
@Override
public Container getContainerDataSource() {
return realContainer;
}
public int getPageLength() {
return pageLength;
}
private void cutShownContainer() {
if (realContainer != null) {
if (index < 0) {
index = 0;
}
if (index > realContainer.size() - 1) {
int pages = 0;
if (getPageLength() != 0) {
pages = (int) Math.floor(0.0 + (realContainer.size() - 1) / getPageLength());
}
index = pages * getPageLength();
}
if (shownContainer == null) {
shownContainer = cloneContainerStructure(realContainer);
}
shownContainer.removeListener(content);
shownContainer.removeAllItems();
if (realContainer.size() != 0) {
Object itemId = realContainer.getIdByIndex(index);
addShownItem(itemId);
int lastIndex = getPageLength() == 0 ? realContainer.size() : getPageLength();
for (int i = 1; i < lastIndex; i++) {
itemId = realContainer.nextItemId(itemId);
if (itemId == null) {
break;
}
addShownItem(itemId);
}
}
if (listeners != null) {
PageChangeEvent event = new PageChangeEvent(this);
for (PageChangeListener listener : listeners) {
listener.pageChanged(event);
}
}
shownContainer.addListener(content);
content.containerItemSetChange(new ItemSetChangeEvent() {
@Override
public Container getContainer() {
return shownContainer;
}
});
}
}
private void addShownItem(Object itemId) {
Item realItem = realContainer.getItem(itemId);
Item shownItem = shownContainer.addItem(realItem);
for (Object property : realContainer.getContainerPropertyIds()) {
shownItem.getItemProperty(property).setValue(realItem.getItemProperty(property).getValue());
}
}
public void setPageLength(int pageLength) {
if (pageLength >= 0 && getPageLength() != pageLength) {
this.pageLength = pageLength;
cutShownContainer();
}
}
public void nextPage() {
index += getPageLength();
cutShownContainer();
}
public void previousPage() {
index -= getPageLength();
cutShownContainer();
}
public int getCurrentPage() {
double pageLength = getPageLength();
int page = (int) Math.floor(index / pageLength) + 1;
if (page < 1) {
page = 1;
}
return page;
}
public void setCurrentPage(int page) {
int newIndex = (page - 1) * getPageLength();
if (newIndex < 0) {
newIndex = 0;
}
if (newIndex != index) {
index = newIndex;
cutShownContainer();
}
}
public int getTotalAmountOfPages() {
int size = realContainer.size();
double pageLength = getPageLength();
int pageCount = (int) Math.ceil(size / pageLength);
if (pageCount < 1) {
pageCount = 1;
}
return pageCount;
}
public void addListener(PageChangeListener listener) {
if (listeners == null) {
listeners = new ArrayList<PageChangeListener>();
}
listeners.add(listener);
}
public void removeListener(PageChangeListener listener) {
if (listeners == null) {
listeners = new ArrayList<PageChangeListener>();
}
listeners.remove(listener);
}
public void sort(Object[] propertyId, boolean[] ascending) throws UnsupportedOperationException {
if (realContainer != null) {
if (realContainer instanceof Sortable) {
((Sortable) realContainer).sort(propertyId, ascending);
} else if (realContainer != null) {
throw new UnsupportedOperationException("Underlying Data does not allow sorting");
}
}
}
public void filter(Object propertyId, String filterString) {
if (realContainer != null) {
if (realContainer instanceof Filterable) {
Filterable filterable = (Filterable) realContainer;
filterable.removeAllContainerFilters();
filterable.addContainerFilter(new SimpleStringFilter(propertyId, filterString, true, false));
} else if (realContainer != null) {
throw new UnsupportedOperationException("Underlying Data does not allow sorting");
}
}
}
protected static IndexedContainer cloneContainerStructure(Container container) {
IndexedContainer cloned = new IndexedContainer();
for (Object propertyId : container.getContainerPropertyIds()) {
cloned.addContainerProperty(propertyId, container.getType(propertyId), null);
}
return cloned;
}
}