All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.gv2011.util.swing.alt.AltTableColumnModel Maven / Gradle / Ivy

There is a newer version: 0.12
Show newest version
/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.github.gv2011.util.swing.alt;

/*-
 * #%L
 * util-swing
 * %%
 * Copyright (C) 2018 Vinz (https://github.com/gv2011)
 * %%
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 * #L%
 */
import static com.github.gv2011.util.Verify.verify;
import static com.github.gv2011.util.Verify.verifyEqual;
import static com.github.gv2011.util.ex.Exceptions.bug;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.EventListener;
import java.util.List;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.swing.DefaultListSelectionModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.event.TableColumnModelListener;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;

import com.github.gv2011.util.legacy.LegacyCollectionUtils;

public final class AltTableColumnModel implements TableColumnModel,
                        PropertyChangeListener, ListSelectionListener, Serializable
{
//
// Instance Variables
//

    /** Array of TableColumn objects in this model */
    private final List tableColumns;

    /** Model for keeping track of column selections */
    private ListSelectionModel selectionModel;

    /** Width margin between each column */
    private int columnMargin;

    /** List of TableColumnModelListener */
    private final EventListenerList listenerList = new EventListenerList();

    /** Change event (only one needed) */
    transient private ChangeEvent changeEvent = null;

    /** Column selection allowed in this column model */
    private boolean columnSelectionAllowed;

    /** A local cache of the combined width of all columns */
    private int totalColumnWidth;

    AltTableColumnModel(final TableModel tableModel) {
      tableColumns = new ArrayList<>();
      setSelectionModel(createSelectionModel());
      setColumnMargin(1);
      invalidateWidthCache();
      setColumnSelectionAllowed(false);

      final int count = tableModel.getColumnCount();
      for(int modelIndex = 0; modelIndex getColumns() {
      return LegacyCollectionUtils.enumeration(tableColumns);
    }

    private void removeFromList(final AltTableColumn col) {
      final int index = col.getDisplayIndex();
      tableColumns.remove(index);
      col.setDisplayIndex(-1);
      for(int i=index; icolumnIndex to
     * newIndex.  The old column at columnIndex
     * will now be found at newIndex.  The column
     * that used to be at newIndex is shifted
     * left or right to make room.  This will not move any columns if
     * columnIndex equals newIndex.  This method
     * also posts a columnMoved event to its listeners.
     *
     * @param   columnIndex                     the index of column to be moved
     * @param   newIndex                        new index to move the column
     * @exception IllegalArgumentException      if column or
     *                                          newIndex
     *                                          are not in the valid range
     */
    @Override
    public void moveColumn(final int columnIndex, final int newIndex) {
        if ((columnIndex < 0) || (columnIndex >= getColumnCount()) ||
            (newIndex < 0) || (newIndex >= getColumnCount()))
            throw new IllegalArgumentException("moveColumn() - Index out of range");

        AltTableColumn aColumn;

        // If the column has not yet moved far enough to change positions
        // post the event anyway, the "draggedDistance" property of the
        // tableHeader will say how far the column has been dragged.
        // Here we are really trying to get the best out of an
        // API that could do with some rethinking. We preserve backward
        // compatibility by slightly bending the meaning of these methods.
        if (columnIndex == newIndex) {
            fireColumnMoved(new TableColumnModelEvent(this, columnIndex, newIndex));
            return;
        }
        aColumn = getFromList(columnIndex);

        removeFromList(aColumn);

        final boolean selected = selectionModel.isSelectedIndex(columnIndex);
        selectionModel.removeIndexInterval(columnIndex,columnIndex);

        insertIntoList(aColumn, newIndex);

        selectionModel.insertIndexInterval(newIndex, 1, true);
        if (selected) {
            selectionModel.addSelectionInterval(newIndex, newIndex);
        }
        else {
            selectionModel.removeSelectionInterval(newIndex, newIndex);
        }

        fireColumnMoved(new TableColumnModelEvent(this, columnIndex,
                                                               newIndex));
    }


    /**
     * Sets the column margin to newMargin.  This method
     * also posts a columnMarginChanged event to its
     * listeners.
     *
     * @param   newMargin               the new margin width, in pixels
     * @see     #getColumnMargin
     * @see     #getTotalColumnWidth
     */
    @Override
    public void setColumnMargin(final int newMargin) {
        if (newMargin != columnMargin) {
            columnMargin = newMargin;
            // Post columnMarginChanged event notification.
            fireColumnMarginChanged();
        }
    }


//
// Querying the model
//




    /**
     * Returns the index of the first column in the tableColumns
     * array whose identifier is equal to identifier,
     * when compared using equals.
     *
     * @param           identifier              the identifier object
     * @return          the index of the first column in the
     *                  tableColumns array whose identifier
     *                  is equal to identifier
     * @exception       IllegalArgumentException  if identifier
     *                          is null, or if no
     *                          TableColumn has this
     *                          identifier
     * @see             #getColumn
     */
    @Override
    public int getColumnIndex(final Object identifier) {
        if (identifier == null) {
            throw new IllegalArgumentException("Identifier is null");
        }

        final Enumeration enumeration = getColumns();
        TableColumn aColumn;
        int index = 0;

        while (enumeration.hasMoreElements()) {
            aColumn = (TableColumn)enumeration.nextElement();
            // Compare them this way in case the column's identifier is null.
            if (identifier.equals(aColumn.getIdentifier()))
                return index;
            index++;
        }
        throw new IllegalArgumentException("Identifier not found");
    }

    /**
     * Returns the TableColumn object for the column
     * at columnIndex.
     *
     * @param   columnIndex     the index of the column desired
     * @return  the TableColumn object for the column
     *                          at columnIndex
     */
    @Override
    public AltTableColumn getColumn(final int columnIndex) {
      return getFromList(columnIndex);
    }

    /**
     * Returns the width margin for TableColumn.
     * The default columnMargin is 1.
     *
     * @return  the maximum width for the TableColumn
     * @see     #setColumnMargin
     */
    @Override
    public int getColumnMargin() {
        return columnMargin;
    }

    /**
     * Returns the index of the column that lies at position x,
     * or -1 if no column covers this point.
     *
     * In keeping with Swing's separable model architecture, a
     * TableColumnModel does not know how the table columns actually appear on
     * screen.  The visual presentation of the columns is the responsibility
     * of the view/controller object using this model (typically JTable).  The
     * view/controller need not display the columns sequentially from left to
     * right.  For example, columns could be displayed from right to left to
     * accommodate a locale preference or some columns might be hidden at the
     * request of the user.  Because the model does not know how the columns
     * are laid out on screen, the given xPosition should not be
     * considered to be a coordinate in 2D graphics space.  Instead, it should
     * be considered to be a width from the start of the first column in the
     * model.  If the column index for a given X coordinate in 2D space is
     * required, JTable.columnAtPoint can be used instead.
     *
     * @param  x  the horizontal location of interest
     * @return  the index of the column or -1 if no column is found
     * @see javax.swing.JTable#columnAtPoint
     */
    @Override
    public int getColumnIndexAtX(int x) {
        if (x < 0) {
            return -1;
        }
        final int cc = getColumnCount();
        for(int column = 0; column < cc; column++) {
            x = x - getColumn(column).getWidth();
            if (x < 0) {
                return column;
            }
        }
        return -1;
    }

    /**
     * Returns the total combined width of all columns.
     * @return the totalColumnWidth property
     */
    @Override
    public int getTotalColumnWidth() {
        if (totalColumnWidth == -1) {
            recalcWidthCache();
        }
        return totalColumnWidth;
    }

//
// Selection model
//

    /**
     *  Sets the selection model for this TableColumnModel
     *  to newModel
     *  and registers for listener notifications from the new selection
     *  model.  If newModel is null,
     *  an exception is thrown.
     *
     * @param   newModel        the new selection model
     * @exception IllegalArgumentException      if newModel
     *                                          is null
     * @see     #getSelectionModel
     */
    @Override
    public void setSelectionModel(final ListSelectionModel newModel) {
        if (newModel == null) {
            throw new IllegalArgumentException("Cannot set a null SelectionModel");
        }

        final ListSelectionModel oldModel = selectionModel;

        if (newModel != oldModel) {
            if (oldModel != null) {
                oldModel.removeListSelectionListener(this);
            }

            selectionModel= newModel;
            newModel.addListSelectionListener(this);
        }
    }

    /**
     * Returns the ListSelectionModel that is used to
     * maintain column selection state.
     *
     * @return  the object that provides column selection state.  Or
     *          null if row selection is not allowed.
     * @see     #setSelectionModel
     */
    @Override
    public ListSelectionModel getSelectionModel() {
        return selectionModel;
    }

    // implements javax.swing.table.TableColumnModel
    /**
     * Sets whether column selection is allowed.  The default is false.
     * @param  flag true if column selection will be allowed, false otherwise
     */
    @Override
    public void setColumnSelectionAllowed(final boolean flag) {
        columnSelectionAllowed = flag;
    }

    // implements javax.swing.table.TableColumnModel
    /**
     * Returns true if column selection is allowed, otherwise false.
     * The default is false.
     * @return the columnSelectionAllowed property
     */
    @Override
    public boolean getColumnSelectionAllowed() {
        return columnSelectionAllowed;
    }

    // implements javax.swing.table.TableColumnModel
    /**
     * Returns an array of selected columns.  If selectionModel
     * is null, returns an empty array.
     * @return an array of selected columns or an empty array if nothing
     *                  is selected or the selectionModel is
     *                  null
     */
    @Override
    public int[] getSelectedColumns() {
        if (selectionModel != null) {
            final int iMin = selectionModel.getMinSelectionIndex();
            final int iMax = selectionModel.getMaxSelectionIndex();

            if ((iMin == -1) || (iMax == -1)) {
                return new int[0];
            }

            final int[] rvTmp = new int[1+ (iMax - iMin)];
            int n = 0;
            for(int i = iMin; i <= iMax; i++) {
                if (selectionModel.isSelectedIndex(i)) {
                    rvTmp[n++] = i;
                }
            }
            final int[] rv = new int[n];
            System.arraycopy(rvTmp, 0, rv, 0, n);
            return rv;
        }
        return  new int[0];
    }

    // implements javax.swing.table.TableColumnModel
    /**
     * Returns the number of columns selected.
     * @return the number of columns selected
     */
    @Override
    public int getSelectedColumnCount() {
        if (selectionModel != null) {
            final int iMin = selectionModel.getMinSelectionIndex();
            final int iMax = selectionModel.getMaxSelectionIndex();
            int count = 0;

            for(int i = iMin; i <= iMax; i++) {
                if (selectionModel.isSelectedIndex(i)) {
                    count++;
                }
            }
            return count;
        }
        return 0;
    }

//
// Listener Support Methods
//

    // implements javax.swing.table.TableColumnModel
    /**
     * Adds a listener for table column model events.
     * @param x  a TableColumnModelListener object
     */
    @Override
    public void addColumnModelListener(final TableColumnModelListener x) {
        listenerList.add(TableColumnModelListener.class, x);
    }

    // implements javax.swing.table.TableColumnModel
    /**
     * Removes a listener for table column model events.
     * @param x  a TableColumnModelListener object
     */
    @Override
    public void removeColumnModelListener(final TableColumnModelListener x) {
        listenerList.remove(TableColumnModelListener.class, x);
    }

    /**
     * Returns an array of all the column model listeners
     * registered on this model.
     *
     * @return all of this default table column model's ColumnModelListeners
     *         or an empty
     *         array if no column model listeners are currently registered
     *
     * @see #addColumnModelListener
     * @see #removeColumnModelListener
     *
     * @since 1.4
     */
    public TableColumnModelListener[] getColumnModelListeners() {
        return listenerList.getListeners(TableColumnModelListener.class);
    }

//
//   Event firing methods
//

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @param e  the event received
     * @see EventListenerList
     */
    private void fireColumnAdded(final TableColumnModelEvent e) {
        // Guaranteed to return a non-null array
        final Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TableColumnModelListener.class) {
                // Lazily create the event:
                // if (e == null)
                //  e = new ChangeEvent(this);
                ((TableColumnModelListener)listeners[i+1]).
                    columnAdded(e);
            }
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @param  e the event received
     * @see EventListenerList
     */
    private void fireColumnMoved(final TableColumnModelEvent e) {
        // Guaranteed to return a non-null array
        final Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TableColumnModelListener.class) {
                // Lazily create the event:
                // if (e == null)
                //  e = new ChangeEvent(this);
                ((TableColumnModelListener)listeners[i+1]).
                    columnMoved(e);
            }
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @param e the event received
     * @see EventListenerList
     */
    private void fireColumnSelectionChanged(final ListSelectionEvent e) {
        // Guaranteed to return a non-null array
        final Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TableColumnModelListener.class) {
                // Lazily create the event:
                // if (e == null)
                //  e = new ChangeEvent(this);
                ((TableColumnModelListener)listeners[i+1]).
                    columnSelectionChanged(e);
            }
        }
    }

    /**
     * Notifies all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * @see EventListenerList
     */
    private void fireColumnMarginChanged() {
        // Guaranteed to return a non-null array
        final Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length-2; i>=0; i-=2) {
            if (listeners[i]==TableColumnModelListener.class) {
                // Lazily create the event:
                if (changeEvent == null)
                    changeEvent = new ChangeEvent(this);
                ((TableColumnModelListener)listeners[i+1]).
                    columnMarginChanged(changeEvent);
            }
        }
    }

    /**
     * Returns an array of all the objects currently registered
     * as FooListeners
     * upon this model.
     * FooListeners are registered using the
     * addFooListener method.
     *
     * 

* * You can specify the listenerType argument * with a class literal, * such as * FooListener.class. * For example, you can query a * DefaultTableColumnModel m * for its column model listeners with the following code: * *

ColumnModelListener[] cmls = (ColumnModelListener[])(m.getListeners(ColumnModelListener.class));
* * If no such listeners exist, this method returns an empty array. * * @param listenerType the type of listeners requested; this parameter * should specify an interface that descends from * java.util.EventListener * @return an array of all objects registered as * FooListeners on this model, * or an empty array if no such * listeners have been added * @exception ClassCastException if listenerType * doesn't specify a class or interface that implements * java.util.EventListener * * @see #getColumnModelListeners * @since 1.3 */ public T[] getListeners(final Class listenerType) { return listenerList.getListeners(listenerType); } @Override public void propertyChange(final PropertyChangeEvent evt) { final String name = evt.getPropertyName(); if (name == "width") { invalidateWidthCache(); // This is a misnomer, we're using this method // simply to cause a relayout. fireColumnMarginChanged(); } else if(name.equals("preferredWidth")) { recalculateWeights((AltTableColumn) evt.getSource()); // This is a misnomer, we're using this method // simply to cause a relayout. fireColumnMarginChanged(); } } private void recalculateWeights(final AltTableColumn column) { final int count = getColumnCount(); if(!column.equals(getColumn(count-1))) { final int oldWidth = column.getWidth(); final int rawNewWidth = column.getRequestedWidth(); verify(rawNewWidth!=oldWidth); final int idx = column.getDisplayIndex(); verify(getColumn(idx).equals(column)); verify(idx < count-1); //ensure limit to the right: final int widthRigth = IntStream.range(idx+1, count).map(i->getColumn(i).getWidth()).sum(); final int minRigth = IntStream.range(idx+1, count).map(i->getColumn(i).getMinWidth()).sum(); final int max = widthRigth - minRigth; verify(max>=0); final int newWidth = Math.min(rawNewWidth, oldWidth+max); final int move = newWidth - oldWidth; //positive to the right //if moving to the left: // * columns to the right grow, those to the left remain unchanged // * weight to the left stays unchanged, weight of column decreases, weight right increases by same amount //if moving to the right: columns to the right decrease, those to the left remain unchanged final long colWeightOld = column.getWeight(); final long rightWeightOld = IntStream.range(idx+1, count) .mapToLong(i->getColumn(i).getWeight()) .sum() ; final long weightDiff = (long) (((double)move/(double)oldWidth) * (double)colWeightOld); final long colWeightNew = colWeightOld + weightDiff; column.setWeight(colWeightNew); long rightWeightAvailable = rightWeightOld - weightDiff; long rightWeightBase = rightWeightOld; double factor = (double)rightWeightAvailable / (double)rightWeightBase; for(int i=idx+1; iListSelectionListener that forwards * ListSelectionEvents when there is a column * selection change. * * @param e the change event */ @Override public void valueChanged(final ListSelectionEvent e) { fireColumnSelectionChanged(e); } // // Protected Methods // /** * Creates a new default list selection model. */ private ListSelectionModel createSelectionModel() { return new DefaultListSelectionModel(); } /** * Recalculates the total combined width of all columns. Updates the * totalColumnWidth property. */ private void recalcWidthCache() { final Enumeration enumeration = getColumns(); totalColumnWidth = 0; while (enumeration.hasMoreElements()) { totalColumnWidth += ((TableColumn)enumeration.nextElement()).getWidth(); } } private void invalidateWidthCache() { totalColumnWidth = -1; } public Stream stream() { return IntStream.range(0, getColumnCount()).parallel().mapToObj(this::getColumn); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy