/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * 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 Lesser General Public License for more details. * * Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.modules.misc.tablemodel; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.table.TableModel; import java.util.ArrayList; /** * A TableModel that proxies an other tablemodel and cuts rows from the start and/or the end of the other tablemodel. * * @author Thomas Morgner */ public class SubSetTableModel implements TableModel { /** * A helper class, that translates tableevents received from the wrapped table model and forwards them with changed * indices to the registered listeners. */ private final class TableEventTranslator implements TableModelListener { /** * the registered listeners. */ private final ArrayList listeners; /** * Default Constructor. */ private TableEventTranslator() { listeners = new ArrayList(); } /** * This fine grain notification tells listeners the exact range of cells, rows, or columns that changed. The * received rows are translated to fit the external tablemodel size. * * @param e * the event, that should be translated. */ public void tableChanged( final TableModelEvent e ) { int firstRow = e.getFirstRow(); if ( e.getFirstRow() > 0 ) { firstRow -= getStart(); } int lastRow = e.getLastRow(); if ( lastRow > 0 ) { lastRow -= getStart(); lastRow -= ( getEnclosedModel().getRowCount() - getEnd() ); } final int type = e.getType(); final int column = e.getColumn(); final TableModelEvent event = new TableModelEvent( SubSetTableModel.this, firstRow, lastRow, column, type ); for ( int i = 0; i < listeners.size(); i++ ) { final TableModelListener l = (TableModelListener) listeners.get( i ); l.tableChanged( event ); } } /** * Adds the TableModelListener to this Translator. * * @param l * the tablemodel listener */ protected void addTableModelListener( final TableModelListener l ) { listeners.add( l ); } /** * Removes the TableModelListener from this Translator. * * @param l * the tablemodel listener */ protected void removeTableModelListener( final TableModelListener l ) { listeners.remove( l ); } } /** * the row that should be the first row. */ private int start; /** * the row that should be the last row. */ private int end; /** * the model. */ private TableModel model; /** * the event translator. */ private TableEventTranslator eventHandler; /** * Creates a new SubSetTableModel, the start and the end parameters define the new tablemodel row count. The parameter * <code>start</code> must be a positive integer and denotes the number or rows removed from the start of the * tablemodel. <code>end</code> is the number of the last translated row. Any row after <code>end</code> is ignored. * End must be greater or equal the given start row. * * @param start * the number of rows that should be removed. * @param end * the last row. * @param model * the wrapped model * @throws NullPointerException * if the given model is null * @throws IllegalArgumentException * if start or end are invalid. */ public SubSetTableModel( final int start, final int end, final TableModel model ) { if ( start < 0 ) { throw new IllegalArgumentException( "Start < 0" ); //$NON-NLS-1$ } if ( end <= start ) { throw new IllegalArgumentException( "end < start" ); //$NON-NLS-1$ } if ( model == null ) { throw new NullPointerException(); } if ( end >= model.getRowCount() ) { throw new IllegalArgumentException( "End >= Model.RowCount" ); //$NON-NLS-1$ } this.start = start; this.end = end; this.model = model; this.eventHandler = new TableEventTranslator(); } /** * Translates the given row to fit for the wrapped tablemodel. * * @param rowIndex * the original row index. * @return the translated row index. */ private int getClientRowIndex( final int rowIndex ) { return rowIndex + start; } /** * Returns the number of rows in the model. A <code>JTable</code> uses this method to determine how many rows it * should display. This method should be quick, as it is called frequently during rendering. * * @return the number of rows in the model * @see #getColumnCount */ public int getRowCount() { final int rowCount = model.getRowCount(); return rowCount - start - ( rowCount - end ); } /** * Returns the number of columns in the model. A <code>JTable</code> uses this method to determine how many columns it * should create and display by default. * * @return the number of columns in the model * @see #getRowCount */ public int getColumnCount() { return model.getColumnCount(); } /** * Returns the name of the column at <code>columnIndex</code>. This is used to initialize the table's column header * name. Note: this name does not need to be unique; two columns in a table can have the same name. * * @param columnIndex * the index of the column * @return the name of the column */ public String getColumnName( final int columnIndex ) { return model.getColumnName( columnIndex ); } /** * Returns the most specific superclass for all the cell values in the column. This is used by the <code>JTable</code> * to set up a default renderer and editor for the column. * * @param columnIndex * the index of the column * @return the base ancestor class of the object values in the model. */ public Class getColumnClass( final int columnIndex ) { return model.getColumnClass( columnIndex ); } /** * Returns true if the cell at <code>rowIndex</code> and <code>columnIndex</code> is editable. Otherwise, * <code>setValueAt</code> on the cell will not change the value of that cell. * * @param rowIndex * the row whose value to be queried * @param columnIndex * the column whose value to be queried * @return true if the cell is editable * @see #setValueAt */ public boolean isCellEditable( final int rowIndex, final int columnIndex ) { return model.isCellEditable( getClientRowIndex( rowIndex ), columnIndex ); } /** * Returns the value for the cell at <code>columnIndex</code> and <code>rowIndex</code>. * * @param rowIndex * the row whose value is to be queried * @param columnIndex * the column whose value is to be queried * @return the value Object at the specified cell */ public Object getValueAt( final int rowIndex, final int columnIndex ) { return model.getValueAt( getClientRowIndex( rowIndex ), columnIndex ); } /** * Sets the value in the cell at <code>columnIndex</code> and <code>rowIndex</code> to <code>aValue</code>. * * @param aValue * the new value * @param rowIndex * the row whose value is to be changed * @param columnIndex * the column whose value is to be changed * @see #getValueAt * @see #isCellEditable */ public void setValueAt( final Object aValue, final int rowIndex, final int columnIndex ) { model.setValueAt( aValue, getClientRowIndex( rowIndex ), columnIndex ); } /** * Adds a listener to the list that is notified each time a change to the data model occurs. * * @param l * the TableModelListener */ public void addTableModelListener( final TableModelListener l ) { eventHandler.addTableModelListener( l ); } /** * Removes a listener from the list that is notified each time a change to the data model occurs. * * @param l * the TableModelListener */ public void removeTableModelListener( final TableModelListener l ) { eventHandler.removeTableModelListener( l ); } /** * Returns the enclosed tablemodel, which is wrapped by this subset table model. * * @return the enclosed table model, never null. */ protected TableModel getEnclosedModel() { return model; } /** * Returns the start row that should be mapped to row 0 of this model. * * @return the first row that should be visible. */ protected int getStart() { return start; } /** * Returns the last row that should be visible. * * @return the number of the last row. */ protected int getEnd() { return end; } }