net.sourceforge.vaticanfetcher.util.gui.viewer.PagedTableViewer.java Source code

Java tutorial

Introduction

Here is the source code for net.sourceforge.vaticanfetcher.util.gui.viewer.PagedTableViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2010, 2011 Tran Nam Quang.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Tran Nam Quang - initial API and implementation
 *******************************************************************************/

package net.sourceforge.vaticanfetcher.util.gui.viewer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import net.sourceforge.vaticanfetcher.util.Event;
import net.sourceforge.vaticanfetcher.util.Util;
import net.sourceforge.vaticanfetcher.util.annotations.Immutable;
import net.sourceforge.vaticanfetcher.util.annotations.MutableCopy;
import net.sourceforge.vaticanfetcher.util.annotations.NotNull;
import net.sourceforge.vaticanfetcher.util.annotations.Nullable;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

/**
 * Supports pagination
 * 
 * @author Tran Nam Quang
 */
public abstract class PagedTableViewer<E> {

    public static abstract class ElementInfo<E> {
        protected boolean isChecked(E element) {
            return true;
        }
    }

    public static abstract class Column<E> {
        private String label;
        private final int orientation;
        private final Event<String> evtLabelChanged = new Event<String>();

        public Column(@NotNull String label) {
            this(label, SWT.LEFT);
        }

        public Column(@NotNull String label, int orientation) {
            this.label = Util.checkNotNull(label);
            this.orientation = orientation;
        }

        public final void setLabel(@NotNull String label) {
            Util.checkNotNull(label);
            if (this.label.equals(label))
                return;
            this.label = label;
            evtLabelChanged.fire(label);
        }

        protected String getToolTipText() {
            return null;
        }

        @NotNull
        protected abstract String getLabel(E element);

        protected Image getImage(E element) {
            return null;
        }

        protected Color getForeground(E element) {
            return null;
        }

        protected Color getBackground(E element) {
            return null;
        }
    }

    @Nullable
    private Object rootElement;
    private final Table table;
    private final Map<E, TableItem> elementToItemMap = Maps.newHashMap();
    @Nullable
    private ElementInfo<E> elementInfo;
    private final List<Column<E>> columns = Lists.newArrayList();

    private int elementsPerPage = Integer.MAX_VALUE;
    private int pageIndex = 0;
    private final List<List<E>> emptyPages = Collections.singletonList(Collections.<E>emptyList());
    private List<List<E>> pages = emptyPages;

    public PagedTableViewer(@NotNull Composite parent, int style) {
        if (Util.contains(style, SWT.VIRTUAL))
            throw new IllegalArgumentException("This class does not support virtual tables.");

        table = new Table(parent, style);
        table.setHeaderVisible(true);

        table.addMouseListener(new MouseAdapter() {
            @SuppressWarnings("unchecked")
            public void mouseDoubleClick(MouseEvent e) {
                TableItem item = table.getItem(new Point(e.x, e.y));
                if (item == null)
                    return;
                onDoubleClick((E) item.getData());
            }
        });
    }

    @NotNull
    public final Table getControl() {
        return table;
    }

    public final void setElementInfo(@Nullable ElementInfo<E> elementInfo) {
        this.elementInfo = elementInfo;
    }

    public final void addColumn(@NotNull Column<E> column) {
        Util.checkNotNull(column);
        columns.add(column);

        final TableColumn tableColumn = new TableColumn(table, column.orientation);
        tableColumn.setText(column.label);
        tableColumn.setToolTipText(column.getToolTipText());

        column.evtLabelChanged.add(new Event.Listener<String>() {
            public void update(String eventData) {
                tableColumn.setText(eventData);
            }
        });
    }

    @Immutable
    @NotNull
    public final List<Column<E>> getColumns() {
        return Collections.unmodifiableList(columns);
    }

    // This does not reset the page index, but clamps it if necessary (explain why)
    public final void setRoot(@Nullable Object rootElement) {
        // Reset fields, excluding the pageIndex
        this.rootElement = rootElement;
        table.removeAll();
        elementToItemMap.clear();
        pages = emptyPages;
        if (columns.isEmpty())
            return;

        /*
         * Get elements and split them into pages
         * 
         * Lists.partition(...) returns an empty list (i.e. zero pages) if the
         * input list is empty or if the page size is Integer.MAX_VALUE, which
         * is not what we want.
         */
        Collection<E> elements = getElements(rootElement);
        if (elements == null || elements.isEmpty())
            pages = emptyPages;
        else if (elementsPerPage == Integer.MAX_VALUE)
            pages = Collections.singletonList(filterAndSort(elements));
        else
            pages = Lists.partition(filterAndSort(elements), elementsPerPage);

        // Populate the table with the elements on the current page
        assert pageIndex >= 0;
        assert pages.size() >= 1;
        pageIndex = Util.clamp(pageIndex, 0, pages.size() - 1);
        List<E> pageElements = pages.get(pageIndex);
        onPageRefresh(pageElements);
        for (E element : pageElements) {
            TableItem item = new TableItem(table, SWT.NONE);
            update(element, item);
            elementToItemMap.put(element, item);
        }
    }

    @Nullable
    public final Object getRoot() {
        return rootElement;
    }

    // This does not reset the page index, but clamps it if necessary
    public final void refresh() {
        setRoot(rootElement);
    }

    private void update(E element, TableItem item) {
        int nCol = columns.size();
        if (nCol == 0)
            return;
        if (elementInfo != null)
            item.setChecked(elementInfo.isChecked(element));
        for (int iCol = 0; iCol < nCol; iCol++) {
            Column<E> column = columns.get(iCol);
            item.setText(iCol, column.getLabel(element));
            item.setImage(iCol, column.getImage(element));
            item.setForeground(iCol, column.getForeground(element));
            item.setBackground(iCol, column.getBackground(element));
        }
        item.setData(element);
    }

    @MutableCopy
    private List<E> filterAndSort(Collection<E> elements) {
        List<E> newElements = new ArrayList<E>(elements.size());
        for (E child : elements)
            if (filter(child))
                newElements.add(child);
        sort(newElements);
        return newElements;
    }

    @Nullable
    protected abstract Collection<E> getElements(@Nullable Object rootElement);

    protected void sort(@NotNull List<E> unsortedElements) {
    }

    // Returns true if the element should *not* be excluded
    protected boolean filter(@NotNull E element) {
        return true;
    }

    protected void onDoubleClick(@NotNull E element) {
        return;
    }

    // Called shortly before the current page is about to be populated with table items
    // The given elements are what's left of the original input
    // after sorting, filtering and paging have been applied
    protected void onPageRefresh(@NotNull List<E> currentPageElements) {
    }

    protected void onSelectionChanged(@NotNull E newSelection) {
    }

    @MutableCopy
    @NotNull
    @SuppressWarnings("unchecked")
    public final List<E> getChecked() {
        List<E> checkedElements = new ArrayList<E>();
        for (TableItem item : table.getItems())
            if (item.getChecked())
                checkedElements.add((E) item.getData());
        return checkedElements;
    }

    @MutableCopy
    @NotNull
    @SuppressWarnings("unchecked")
    public final List<E> getSelection() {
        TableItem[] selection = table.getSelection();
        List<E> selElements = new ArrayList<E>(selection.length);
        for (TableItem item : selection)
            selElements.add((E) item.getData());
        return selElements;
    }

    public final int getSelectionCount() {
        return table.getSelectionCount();
    }

    public final void update(@Nullable E element) {
        if (element == null)
            return;
        update(element, elementToItemMap.get(element));
    }

    public final void update() {
        for (Map.Entry<E, TableItem> entry : elementToItemMap.entrySet())
            update(entry.getKey(), entry.getValue());
    }

    // Switches to the appropriate page if necessary
    public final void showElement(@Nullable E element) {
        if (element == null)
            return;
        TableItem item = elementToItemMap.get(element);
        if (item != null) {
            table.showItem(item);
            table.setSelection(item);
            return;
        }
        // Switch to the page that contains the given element
        for (int i = 0; i < pages.size(); i++) {
            for (E candidate : pages.get(i)) {
                if (candidate != element)
                    continue;
                pageIndex = i;
                refresh();
                TableItem item1 = elementToItemMap.get(element);
                table.showItem(item1);
                table.setSelection(item1);
                return;
            }
        }
    }

    @Immutable
    @NotNull
    public final List<E> getElements(int pageIndex) {
        Util.checkThat(pageIndex >= 0);
        Util.checkThat(pageIndex < pages.size());
        return Collections.unmodifiableList(pages.get(pageIndex));
    }

    public final boolean setFocus() {
        return table.setFocus();
    }

    // returned value is >= 1
    public final int getElementsPerPage() {
        assert elementsPerPage >= 1;
        return elementsPerPage;
    }

    // values <= 0 will be set to 1
    public final void setElementsPerPage(int elementsPerPage) {
        int oldElementsPerPage = this.elementsPerPage;
        this.elementsPerPage = Math.max(1, elementsPerPage);
        if (oldElementsPerPage != this.elementsPerPage) {
            pageIndex = 0;
            refresh();
        }
    }

    public final int getPageCount() {
        return pages.size();
    }

    // pageIndex is zero-based
    public final int getPageIndex() {
        assert pageIndex >= 0 && pageIndex < pages.size();
        return pageIndex;
    }

    // values outside [0, page.size() - 1] will be clamped
    // pageIndex is zero-based
    public final void setPage(int pageIndex) {
        int oldPageIndex = this.pageIndex;
        this.pageIndex = Util.clamp(pageIndex, 0, pages.size() - 1);
        if (oldPageIndex != this.pageIndex)
            refresh();
    }

    public final void previousPage() {
        setPage(--pageIndex);
    }

    public final void nextPage() {
        setPage(++pageIndex);
    }

}