/*!
* 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) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.designer.core.actions.elements;
import org.pentaho.reporting.designer.core.ReportDesignerContext;
import org.pentaho.reporting.designer.core.actions.AbstractDesignerContextAction;
import org.pentaho.reporting.designer.core.actions.ActionMessages;
import org.pentaho.reporting.designer.core.actions.ToggleStateAction;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.model.selection.DocumentContextSelectionModel;
import org.pentaho.reporting.designer.core.util.IconLoader;
import org.pentaho.reporting.engine.classic.core.AbstractReportDefinition;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.CrosstabCell;
import org.pentaho.reporting.engine.classic.core.CrosstabCellBody;
import org.pentaho.reporting.engine.classic.core.CrosstabColumnGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabColumnGroupBody;
import org.pentaho.reporting.engine.classic.core.CrosstabElement;
import org.pentaho.reporting.engine.classic.core.CrosstabGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabHeader;
import org.pentaho.reporting.engine.classic.core.CrosstabOtherGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabOtherGroupBody;
import org.pentaho.reporting.engine.classic.core.CrosstabRowGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabRowGroupBody;
import org.pentaho.reporting.engine.classic.core.CrosstabSummaryHeader;
import org.pentaho.reporting.engine.classic.core.CrosstabTitleHeader;
import org.pentaho.reporting.engine.classic.core.Element;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.GroupHeader;
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
/**
* This class handle the select crosstab action. This action is invoked from the Crosstab subreport toolbar. Each time
* user clicks on the button, a different section of the crosstab is selected where all elements inside the section are
* selected,
*
* @author Sulaiman Karmali
*/
public class SelectCrosstabBandAction extends AbstractDesignerContextAction implements ToggleStateAction {
private DocumentContextSelectionModel selectionModel;
private ArrayList<Element> otherGroupBodyList;
private ArrayList<Element> rowGroupBodyList;
private ArrayList<Element> columnGroupBodyList;
private ArrayList<Element> cellBodyList;
private ArrayList<Element> noneList;
private ArrayList<Element> allElementsList;
private CrosstabSelectionBandState selectionBandState;
private enum CrosstabSelectionBandState {
NONE() {
public CrosstabSelectionBandState getNextState() {
return OTHER;
}
public CrosstabSelectionBandState getCurrentState() {
return NONE;
}
},
OTHER() {
public CrosstabSelectionBandState getNextState() {
return ROW;
}
public CrosstabSelectionBandState getCurrentState() {
return OTHER;
}
},
ROW() {
public CrosstabSelectionBandState getNextState() {
return COLUMN;
}
public CrosstabSelectionBandState getCurrentState() {
return ROW;
}
},
COLUMN() {
public CrosstabSelectionBandState getNextState() {
return CELL;
}
public CrosstabSelectionBandState getCurrentState() {
return COLUMN;
}
},
CELL() {
public CrosstabSelectionBandState getNextState() {
return ALL;
}
public CrosstabSelectionBandState getCurrentState() {
return CELL;
}
},
ALL() {
public CrosstabSelectionBandState getNextState() {
return NONE;
}
public CrosstabSelectionBandState getCurrentState() {
return ALL;
}
};
public abstract CrosstabSelectionBandState getNextState();
public abstract CrosstabSelectionBandState getCurrentState();
}
public SelectCrosstabBandAction() {
putValue( Action.SELECTED_KEY, Boolean.TRUE );
putValue( Action.NAME, ActionMessages.getString( "SelectCrosstabBandAction.Text" ) );
putValue( Action.SHORT_DESCRIPTION, ActionMessages.getString( "SelectCrosstabBandAction.Description" ) );
putValue( Action.MNEMONIC_KEY, ActionMessages.getOptionalMnemonic( "SelectCrosstabBandAction.Mnemonic" ) );
putValue( Action.SMALL_ICON, IconLoader.getInstance().getSelectCrosstabBandCommand() );
putValue( Action.ACCELERATOR_KEY, ActionMessages.getOptionalKeyStroke( "SelectCrosstabBandAction.Accelerator" ) );
selectionBandState = CrosstabSelectionBandState.NONE;
otherGroupBodyList = new ArrayList<Element>();
rowGroupBodyList = new ArrayList<Element>();
columnGroupBodyList = new ArrayList<Element>();
cellBodyList = new ArrayList<Element>();
noneList = new ArrayList<Element>(); // This will always be empty
allElementsList = new ArrayList<Element>();
setEnabled( true );
}
private ArrayList<Element> getNextSelectionList() {
selectionBandState = selectionBandState.getNextState();
switch( selectionBandState ) {
case NONE:
return noneList;
case OTHER:
return otherGroupBodyList;
case ROW:
return rowGroupBodyList;
case COLUMN:
return columnGroupBodyList;
case CELL:
return cellBodyList;
case ALL:
return allElementsList;
}
return noneList;
}
public boolean isSelected() {
return Boolean.TRUE.equals( getValue( Action.SELECTED_KEY ) );
}
public void setSelected( final boolean selected ) {
putValue( Action.SELECTED_KEY, selected );
}
public void initialize() {
allElementsList.clear();
otherGroupBodyList.clear();
rowGroupBodyList.clear();
columnGroupBodyList.clear();
cellBodyList.clear();
}
private DocumentContextSelectionModel getSelectionModel() {
if ( selectionModel == null ) {
final ReportDesignerContext reportDesignerContext = getReportDesignerContext();
final ReportDocumentContext activeContext = reportDesignerContext.getActiveContext();
selectionModel = activeContext.getSelectionModel();
}
return selectionModel;
}
public ArrayList<Element> getOtherGroupBodyList() {
return otherGroupBodyList;
}
public void buildCrosstabLists() {
final ReportDesignerContext reportDesignerContext = getReportDesignerContext();
if ( reportDesignerContext == null ) {
return;
}
// Clear lists just in case something changed.
initialize();
final ReportDocumentContext activeContext = reportDesignerContext.getActiveContext();
selectionModel = getSelectionModel();
final AbstractReportDefinition reportDefinition = activeContext.getReportDefinition();
if ( reportDefinition instanceof CrosstabElement ) {
final CrosstabElement crosstabElement = (CrosstabElement) reportDefinition;
final Group group = crosstabElement.getRootGroup();
if ( group instanceof CrosstabGroup ) {
final CrosstabGroup crosstabGroup = (CrosstabGroup) group;
final GroupBody crosstabGroupBody = crosstabGroup.getBody();
// Start with the other group and work our way deeper recursively.
// Note: Other Group is optional.
if ( crosstabGroupBody instanceof CrosstabOtherGroupBody ) {
final CrosstabOtherGroup crosstabOtherGroup = ( (CrosstabOtherGroupBody) crosstabGroupBody ).getGroup();
buildCrosstabOtherRowGroupBands( crosstabOtherGroup );
} else if ( crosstabGroupBody instanceof CrosstabRowGroupBody ) {
final CrosstabRowGroupBody crosstabRowGroupBody = (CrosstabRowGroupBody) crosstabGroup.getBody();
buildCrosstabRowGroupBands( crosstabRowGroupBody );
}
// Create an array of all elements.
allElementsList.addAll( otherGroupBodyList );
allElementsList.addAll( rowGroupBodyList );
allElementsList.addAll( columnGroupBodyList );
allElementsList.addAll( cellBodyList );
}
}
}
/**
* Invoked when an action occurs. We are going to select all the elements inside of a crosstab band (row, column, or
* cell) every time actionPerformed is called - usually when user clicks on selection icon in subreport toolbar
*/
public void actionPerformed( final ActionEvent e ) {
// We want to build the row, column and cell lists only once. These lists
// contain all the elements for a particular section of the crosstab.
if ( allElementsList.isEmpty() ) {
buildCrosstabLists();
}
// Select the next crosstab band
selectCrosstabElements( getNextSelectionList() );
}
/**
* Iterate over the elements in the main Crosstab Cell body. This is where the data of a crosstab is presented in a
* row/column fashion
*
* @param crosstabCellBody - Contains the body of all the crosstab cells
*/
private void buildCrosstabCellBands( final CrosstabCellBody crosstabCellBody ) {
if ( crosstabCellBody == null ) {
return;
}
buildCrosstabElementsList( crosstabCellBody.getHeader(), cellBodyList );
final int count = crosstabCellBody.getElementCount();
for ( int i = 0; i < count; i++ ) {
final Element element = crosstabCellBody.getElement( i );
if ( element instanceof CrosstabCell ) {
final CrosstabCell cell = (CrosstabCell) element;
final int cellCount = cell.getElementCount();
for ( int c = 0; c < cellCount; c++ ) {
final Element cellElement = cell.getElement( c );
cellBodyList.add( cellElement );
}
}
}
}
/**
* Iterate over the Crosstab's Column Group Body. This is where the Crosstab column title, column header and column's
* summary header live ('crosstab-row-group-body')
*
* @param crosstabColumnGroupBody
*/
private void buildCrosstabColumnGroupBands( final CrosstabColumnGroupBody crosstabColumnGroupBody ) {
if ( crosstabColumnGroupBody == null ) {
return;
}
final CrosstabColumnGroup crosstabColumnGroup = crosstabColumnGroupBody.getGroup();
final CrosstabTitleHeader crosstabTitleHeader = crosstabColumnGroup.getTitleHeader();
final CrosstabHeader crosstabHeader = crosstabColumnGroup.getHeader();
final CrosstabSummaryHeader crosstabSummaryHeader = crosstabColumnGroup.getSummaryHeader();
buildCrosstabElementsList( crosstabTitleHeader, columnGroupBodyList );
buildCrosstabElementsList( crosstabHeader, columnGroupBodyList );
buildCrosstabElementsList( crosstabSummaryHeader, columnGroupBodyList );
final GroupBody body = crosstabColumnGroup.getBody();
if ( body instanceof CrosstabColumnGroupBody ) {
// Recurse to the next column-group
buildCrosstabColumnGroupBands( (CrosstabColumnGroupBody) body );
} else if ( body instanceof CrosstabCellBody ) {
// We are done with column-groups, lets start the cell band (row/col of cells containing
// values of the crosstab). This is the most granular we go.
buildCrosstabCellBands( (CrosstabCellBody) body );
}
}
/**
* Iterate over the Crosstab's Row Group Body. This is where the Crosstab's row title header, row header, and row
* summary live ('crosstab-row-group-body')
*
* @param crosstabRowGroupBody
*/
private void buildCrosstabRowGroupBands( final CrosstabRowGroupBody crosstabRowGroupBody ) {
if ( crosstabRowGroupBody == null ) {
return;
}
final CrosstabRowGroup crosstabRowGroup = crosstabRowGroupBody.getGroup();
final CrosstabTitleHeader crosstabTitleHeader = crosstabRowGroup.getTitleHeader();
final CrosstabHeader crosstabHeader = crosstabRowGroup.getHeader();
final CrosstabSummaryHeader crosstabSummaryHeader = crosstabRowGroup.getSummaryHeader();
buildCrosstabElementsList( crosstabTitleHeader, rowGroupBodyList );
buildCrosstabElementsList( crosstabHeader, rowGroupBodyList );
buildCrosstabElementsList( crosstabSummaryHeader, rowGroupBodyList );
final GroupBody body = crosstabRowGroup.getBody();
if ( body instanceof CrosstabRowGroupBody ) {
// Recurse to add additional row-groups
buildCrosstabRowGroupBands( (CrosstabRowGroupBody) body );
} else if ( body instanceof CrosstabColumnGroupBody ) {
// We are done with row-groups, let's now deal with column-groups
buildCrosstabColumnGroupBands( (CrosstabColumnGroupBody) body );
}
}
/**
* Iterate over the Crosstab's Other Group Body. This is where the Crosstab's Group Header lives
* ('crosstab-other-group')
*
* @param crosstabOtherGroup
*/
private void buildCrosstabOtherRowGroupBands( final CrosstabOtherGroup crosstabOtherGroup ) {
if ( crosstabOtherGroup == null ) {
return;
}
final GroupHeader otherGroupHeader = (GroupHeader) crosstabOtherGroup.getElement( 0 );
for ( int i = 0; i < otherGroupHeader.getElementCount(); i++ ) {
final Element element = otherGroupHeader.getElement( i );
otherGroupBodyList.add( element );
}
// We got a Other Group header. If we have multiple group headers, we recurse.
// Otherwise we can now get the elements from other parts of the crosstab
final GroupBody groupBody = crosstabOtherGroup.getBody();
if ( groupBody instanceof CrosstabOtherGroupBody ) {
buildCrosstabOtherRowGroupBands( ( (CrosstabOtherGroupBody) groupBody ).getGroup() );
} else if ( groupBody instanceof CrosstabRowGroupBody ) {
buildCrosstabRowGroupBands( (CrosstabRowGroupBody) groupBody );
}
}
/**
* Iterate over a Band's elements selecting each element.
*
* @param elementsList - Element list containing a section of the crosstab that we want to select
*/
private void selectCrosstabElements( final ArrayList<Element> elementsList ) {
if ( elementsList == null ) {
return;
}
if ( selectionModel == null ) {
selectionModel = getSelectionModel();
}
// First clear any previously selected elements
selectionModel.clearSelection();
// Select elements
selectionModel.setSelectedElements( elementsList.toArray() );
}
/**
* For caching purposes, we iterate over the elements in a band and add the elements to list
*
* @param band
* @param elementsList - list containing elements that are part of the band.
*/
private void buildCrosstabElementsList( final Band band, final ArrayList<Element> elementsList ) {
if ( ( band == null ) || ( elementsList == null ) ) {
return;
}
final int count = band.getElementCount();
for ( int i = 0; i < count; i++ ) {
final Element element = band.getElement( i );
elementsList.add( element );
}
}
}