weka.gui.SortedTableModel.java Source code

Java tutorial

Introduction

Here is the source code for weka.gui.SortedTableModel.java

Source

/*
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * SortedTableModel.java
 * Copyright (C) 2005-2012 University of Waikato, Hamilton, New Zealand
 *
 */

package weka.gui;

import weka.core.InheritanceUtils;

import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import java.awt.event.InputEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;

/**
 * Represents a TableModel with sorting functionality.
 * 
 * @author FracPete (fracpete at waikato dot ac dot nz)
 * @version $Revision$
 */

public class SortedTableModel extends AbstractTableModel implements TableModelListener {

    /** for serialization */
    static final long serialVersionUID = 4030907921461127548L;

    /**
     * Helper class for sorting the columns.
     */
    public static class SortContainer implements Comparable<SortContainer> {

        /** the value to sort. */
        protected Comparable<?> m_Value;

        /** the index of the value. */
        protected int m_Index;

        /**
         * Initializes the container.
         * 
         * @param value the value to sort on
         * @param index the original index
         */
        public SortContainer(Comparable<?> value, int index) {
            super();

            m_Value = value;
            m_Index = index;
        }

        /**
         * Returns the value to sort on.
         * 
         * @return the value
         */
        public Comparable<?> getValue() {
            return m_Value;
        }

        /**
         * Returns the original index of the item.
         * 
         * @return the index
         */
        public int getIndex() {
            return m_Index;
        }

        /**
         * Compares this object with the specified object for order. Returns a
         * negative integer, zero, or a positive integer as this object is less
         * than, equal to, or greater than the specified object. Null is considered
         * smallest. If both values are null, then 0 is returned.
         * 
         * @param o the object to be compared.
         * @return a negative integer, zero, or a positive integer as this object is
         *         less than, equal to, or greater than the specified object.
         * @throws ClassCastException if the specified object's type prevents it
         *           from being compared to this object.
         */
        @SuppressWarnings({ "rawtypes", "unchecked" })
        @Override
        public int compareTo(SortContainer o) {
            if ((m_Value == null) || (o.getValue() == null)) {
                if (m_Value == o.getValue()) {
                    return 0;
                }
                if (m_Value == null) {
                    return -1;
                } else {
                    return +1;
                }
            } else {
                return ((Comparable) m_Value).compareTo(o.getValue());
            }
        }

        /**
         * Indicates whether some other object is "equal to" this one.
         * 
         * @param obj the reference object with which to compare.
         * @return true if this object is the same as the obj argument; false
         *         otherwise.
         * @throws ClassCastException if the specified object's type prevents it
         *           from being compared to this object.
         */
        @Override
        public boolean equals(Object obj) {
            return (compareTo((SortContainer) obj) == 0);
        }

        /**
         * Returns a string representation of the sort container.
         * 
         * @return the string representation (value + index)
         */
        @Override
        public String toString() {
            return "value=" + m_Value + ", index=" + m_Index;
        }
    }

    /** the actual table model */
    protected TableModel mModel;

    /** the mapping between displayed and actual index */
    protected int[] mIndices;

    /** the sort column */
    protected int mSortColumn;

    /** whether sorting is ascending or descending */
    protected boolean mAscending;

    /**
     * initializes with no model
     */
    public SortedTableModel() {
        this(null);
    }

    /**
     * initializes with the given model
     * 
     * @param model the model to initialize the sorted model with
     */
    public SortedTableModel(TableModel model) {
        setModel(model);
    }

    /**
     * sets the model to use
     * 
     * @param value the model to use
     */
    public void setModel(TableModel value) {
        mModel = value;

        // initialize indices
        if (mModel == null) {
            mIndices = null;
        } else {
            initializeIndices();
            mSortColumn = -1;
            mAscending = true;
            mModel.addTableModelListener(this);
        }
    }

    /**
     * (re-)initializes the indices
     */
    protected void initializeIndices() {
        int i;

        mIndices = new int[mModel.getRowCount()];
        for (i = 0; i < mIndices.length; i++) {
            mIndices[i] = i;
        }
    }

    /**
     * returns the current model, can be null
     * 
     * @return the current model
     */
    public TableModel getModel() {
        return mModel;
    }

    /**
     * returns whether the table was sorted
     * 
     * @return true if the table was sorted
     */
    public boolean isSorted() {
        return (mSortColumn > -1);
    }

    /**
     * whether the model is initialized
     * 
     * @return true if the model is not null and the sort indices match the number
     *         of rows
     */
    protected boolean isInitialized() {
        return (getModel() != null);
    }

    /**
     * Returns the actual underlying row the given visible one represents. Useful
     * for retrieving "non-visual" data that is also stored in a TableModel.
     * 
     * @param visibleRow the displayed row to retrieve the original row for
     * @return the original row
     */
    public int getActualRow(int visibleRow) {
        if (!isInitialized()) {
            return -1;
        } else {
            return mIndices[visibleRow];
        }
    }

    /**
     * Returns the most specific superclass for all the cell values in the column.
     * 
     * @param columnIndex the index of the column
     * @return the class of the specified column
     */
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        if (!isInitialized()) {
            return null;
        } else {
            return getModel().getColumnClass(columnIndex);
        }
    }

    /**
     * Returns the number of columns in the model
     * 
     * @return the number of columns in the model
     */
    @Override
    public int getColumnCount() {
        if (!isInitialized()) {
            return 0;
        } else {
            return getModel().getColumnCount();
        }
    }

    /**
     * Returns the name of the column at columnIndex
     * 
     * @param columnIndex the column to retrieve the name for
     * @return the name of the specified column
     */
    @Override
    public String getColumnName(int columnIndex) {
        if (!isInitialized()) {
            return null;
        } else {
            return getModel().getColumnName(columnIndex);
        }
    }

    /**
     * Returns the number of rows in the model.
     * 
     * @return the number of rows in the model
     */
    @Override
    public int getRowCount() {
        if (!isInitialized()) {
            return 0;
        } else {
            return getModel().getRowCount();
        }
    }

    /**
     * Returns the value for the cell at columnIndex and rowIndex.
     * 
     * @param rowIndex the row
     * @param columnIndex the column
     * @return the value of the sepcified cell
     */
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        if (!isInitialized()) {
            return null;
        } else {
            return getModel().getValueAt(mIndices[rowIndex], columnIndex);
        }
    }

    /**
     * Returns true if the cell at rowIndex and columnIndex is editable.
     * 
     * @param rowIndex the row
     * @param columnIndex the column
     * @return true if the cell is editable
     */
    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        if (!isInitialized()) {
            return false;
        } else {
            return getModel().isCellEditable(mIndices[rowIndex], columnIndex);
        }
    }

    /**
     * Sets the value in the cell at columnIndex and rowIndex to aValue.
     * 
     * @param aValue the new value of the cell
     * @param rowIndex the row
     * @param columnIndex the column
     */
    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        if (isInitialized()) {
            getModel().setValueAt(aValue, mIndices[rowIndex], columnIndex);
        }
    }

    /**
     * sorts the table over the given column (ascending)
     * 
     * @param columnIndex the column to sort over
     */
    public void sort(int columnIndex) {
        sort(columnIndex, true);
    }

    /**
     * sorts the table over the given column, either ascending or descending
     * 
     * @param columnIndex the column to sort over
     * @param ascending ascending if true, otherwise descending
     */
    public void sort(int columnIndex, boolean ascending) {
        int columnType;
        int i;
        ArrayList<SortContainer> sorted;
        SortContainer cont;
        Object value;

        // can we sort?
        if ((!isInitialized()) || (getModel().getRowCount() != mIndices.length)) {

            System.out.println(this.getClass().getName() + ": Table model not initialized!");

            return;
        }

        // init
        mSortColumn = columnIndex;
        mAscending = ascending;
        initializeIndices();

        // determine the column type: 0=string/other, 1=comparable
        if (InheritanceUtils.hasInterface(Comparable.class, getColumnClass(mSortColumn))) {
            columnType = 1;
        } else {
            columnType = 0;
        }

        // create list for sorting
        sorted = new ArrayList<SortContainer>();
        for (i = 0; i < getRowCount(); i++) {
            value = mModel.getValueAt(mIndices[i], mSortColumn);
            if (columnType == 0) {
                cont = new SortContainer((value == null) ? null : value.toString(), mIndices[i]);
            } else {
                cont = new SortContainer((Comparable<?>) value, mIndices[i]);
            }
            sorted.add(cont);
        }
        Collections.sort(sorted);

        for (i = 0; i < sorted.size(); i++) {
            if (mAscending) {
                mIndices[i] = sorted.get(i).getIndex();
            } else {
                mIndices[i] = sorted.get(sorted.size() - 1 - i).getIndex();
            }
        }

        sorted.clear();
        sorted = null;
    }

    /**
     * This fine grain notification tells listeners the exact range of cells,
     * rows, or columns that changed.
     * 
     * @param e the event
     */
    @Override
    public void tableChanged(TableModelEvent e) {
        initializeIndices();
        if (isSorted()) {
            sort(mSortColumn, mAscending);
        }

        fireTableChanged(e);
    }

    /**
     * Adds a mouselistener to the header: left-click on the header sorts in
     * ascending manner, using shift-left-click in descending manner.
     * 
     * @param table the table to add the listener to
     */
    public void addMouseListenerToHeader(JTable table) {
        final SortedTableModel modelFinal = this;
        final JTable tableFinal = table;
        tableFinal.setColumnSelectionAllowed(false);
        JTableHeader header = tableFinal.getTableHeader();

        if (header != null) {
            MouseAdapter listMouseListener = new MouseAdapter() {
                @Override
                public void mouseClicked(MouseEvent e) {
                    TableColumnModel columnModel = tableFinal.getColumnModel();
                    int viewColumn = columnModel.getColumnIndexAtX(e.getX());
                    int column = tableFinal.convertColumnIndexToModel(viewColumn);
                    if (e.getButton() == MouseEvent.BUTTON1 && e.getClickCount() == 1 && !e.isAltDown()
                            && column != -1) {
                        int shiftPressed = e.getModifiers() & InputEvent.SHIFT_MASK;
                        boolean ascending = (shiftPressed == 0);
                        modelFinal.sort(column, ascending);
                    }
                }
            };

            header.addMouseListener(listMouseListener);
        }
    }
}