/*!
* 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.util.table;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.designer.core.DesignerContextComponent;
import org.pentaho.reporting.designer.core.ReportDesignerContext;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.editor.ReportRenderContext;
import org.pentaho.reporting.designer.core.settings.SettingsListener;
import org.pentaho.reporting.designer.core.settings.WorkspaceSettings;
import org.pentaho.reporting.designer.core.util.table.expressions.ExpressionCellHandler;
import org.pentaho.reporting.designer.core.util.table.expressions.ReportPreProcessorCellEditor;
import org.pentaho.reporting.designer.core.util.table.expressions.ReportPreProcessorCellRenderer;
import org.pentaho.reporting.designer.core.util.table.expressions.StructureFunctionCellEditor;
import org.pentaho.reporting.engine.classic.core.ReportPreProcessor;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.function.StructureFunction;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.designtime.swing.FormattingTableCellRenderer;
import org.pentaho.reporting.libraries.designtime.swing.GenericCellEditor;
import org.pentaho.reporting.libraries.designtime.swing.GenericCellRenderer;
import org.pentaho.reporting.libraries.designtime.swing.PaintCellRenderer;
import org.pentaho.reporting.libraries.designtime.swing.date.DateCellEditor;
import org.pentaho.reporting.libraries.designtime.swing.date.TimeCellEditor;
import org.pentaho.reporting.libraries.designtime.swing.propertyeditors.PropertyEditorCellEditor;
import org.pentaho.reporting.libraries.designtime.swing.propertyeditors.PropertyEditorCellRenderer;
import org.pentaho.reporting.libraries.designtime.swing.settings.LocaleSettings;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyEditor;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
/**
* A table implementation that selects the cell-renderer and editor based on some extended rules (and not just based on
* the current column).
*
* @author Thomas Morgner
*/
public class ElementMetaDataTable extends JTable implements DesignerContextComponent {
private class LocaleSettingsListener implements SettingsListener {
private LocaleSettingsListener() {
}
public void settingsChanged() {
applyLocaleSettings( WorkspaceSettings.getInstance() );
}
}
private class ActiveContextChangeHandler implements PropertyChangeListener {
private ActiveContextChangeHandler() {
}
/**
* This method gets called when a bound property is changed.
*
* @param evt A PropertyChangeEvent object describing the event source and the property that has changed.
*/
public void propertyChange( final PropertyChangeEvent evt ) {
final ReportRenderContext oldContext = (ReportRenderContext) evt.getOldValue();
final ReportRenderContext activeContext = (ReportRenderContext) evt.getNewValue();
updateActiveContext( oldContext, activeContext );
}
}
private static final Log logger = LogFactory.getLog( ElementMetaDataTable.class );
private GroupingHeaderCellRenderer groupingCellRenderer;
private PropertyEditorCellRenderer propertyEditorCellRenderer;
private DesignerPropertyCellEditorWithEllipsis propertyEditorCellEditor;
private PropertyEditorCellEditor taggedPropertyEditorCellEditor;
private ArrayCellRenderer arrayCellRenderer;
private ArrayCellEditor arrayCellEditor;
private ExpressionCellHandler expressionsCellEditor;
private ExpressionCellHandler expressionsCellRenderer;
private StringValueCellEditor stringValueCellEditor;
private StructureFunctionCellEditor structureFunctionCellEditor;
private ReportPreProcessorCellEditor reportPreProcessorCellEditor;
private ReportPreProcessorCellRenderer reportPreProcessorCellRenderer;
private ActiveContextChangeHandler changeHandler;
public ElementMetaDataTable() {
putClientProperty( "terminateEditOnFocusLost", Boolean.TRUE );
// This is a hack as Mac chooses white on white for grids.
this.setShowHorizontalLines( true );
this.setShowVerticalLines( true );
this.setGridColor( SystemColor.controlShadow );
changeHandler = new ActiveContextChangeHandler();
structureFunctionCellEditor = new StructureFunctionCellEditor();
reportPreProcessorCellEditor = new ReportPreProcessorCellEditor();
expressionsCellEditor = new ExpressionCellHandler();
expressionsCellRenderer = new ExpressionCellHandler();
reportPreProcessorCellRenderer = new ReportPreProcessorCellRenderer();
groupingCellRenderer = new GroupingHeaderCellRenderer();
taggedPropertyEditorCellEditor = new PropertyEditorCellEditor();
propertyEditorCellEditor = new DesignerPropertyCellEditorWithEllipsis();
propertyEditorCellRenderer = new PropertyEditorCellRenderer();
arrayCellRenderer = new ArrayCellRenderer();
arrayCellEditor = new ArrayCellEditor();
stringValueCellEditor = new StringValueCellEditor();
setDefaultEditor( Object.class, null );
setDefaultEditor( GroupingHeader.class, new GroupingHeaderCellEditor() );
setDefaultEditor( Expression.class, expressionsCellEditor );
setDefaultEditor( StructureFunction.class, structureFunctionCellEditor );
setDefaultEditor( ReportPreProcessor.class, reportPreProcessorCellEditor );
setDefaultEditor( Number.class, new GenericCellEditor( BigDecimal.class ) );
setDefaultEditor( Integer.class, new GenericCellEditor( Integer.class ) );
setDefaultEditor( Float.class, new GenericCellEditor( Float.class ) );
setDefaultEditor( Double.class, new GenericCellEditor( Double.class ) );
setDefaultEditor( Short.class, new GenericCellEditor( Short.class ) );
setDefaultEditor( Byte.class, new GenericCellEditor( Byte.class ) );
setDefaultEditor( Long.class, new GenericCellEditor( Long.class ) );
setDefaultEditor( BigInteger.class, new GenericCellEditor( BigInteger.class ) );
setDefaultEditor( BigDecimal.class, new GenericCellEditor( BigDecimal.class ) );
setDefaultEditor( String.class, stringValueCellEditor );
setDefaultEditor( Date.class, new DateCellEditor( Date.class ) );
setDefaultEditor( java.sql.Date.class, new DateCellEditor( Date.class ) );
setDefaultEditor( Time.class, new TimeCellEditor( Time.class ) );
setDefaultEditor( Timestamp.class, new DateCellEditor( Timestamp.class ) );
setDefaultRenderer( GroupingHeader.class, new GroupingHeaderCellRenderer() );
setDefaultRenderer( GroupedName.class, new GroupedNameCellRenderer() );
setDefaultRenderer( StructureFunction.class, expressionsCellRenderer );
setDefaultRenderer( Expression.class, expressionsCellRenderer );
setDefaultRenderer( Paint.class, new PaintCellRenderer() );
setDefaultRenderer( Object.class, new GenericCellRenderer() );
setDefaultRenderer( String.class, new GenericCellRenderer() );
setDefaultRenderer( ReportPreProcessor.class, reportPreProcessorCellRenderer );
final SimpleDateFormat isoDateFormat =
new SimpleDateFormat( WorkspaceSettings.DATETIME_FORMAT_DEFAULT, Locale.ENGLISH );
setDefaultRenderer( Date.class, new FormattingTableCellRenderer( isoDateFormat ) );
setDefaultRenderer( java.sql.Date.class, new FormattingTableCellRenderer
( new SimpleDateFormat( WorkspaceSettings.DATE_FORMAT_DEFAULT, Locale.ENGLISH ) ) );
setDefaultRenderer( Time.class, new FormattingTableCellRenderer
( new SimpleDateFormat( WorkspaceSettings.TIME_FORMAT_DEFAULT, Locale.ENGLISH ) ) );
setDefaultRenderer( Timestamp.class, new FormattingTableCellRenderer( isoDateFormat ) );
WorkspaceSettings.getInstance().addSettingsListener( new LocaleSettingsListener() );
applyLocaleSettings( WorkspaceSettings.getInstance() );
}
private static SimpleDateFormat createSafely( final String pattern, final String defaultPattern,
final Locale locale ) {
try {
if ( StringUtils.isEmpty( pattern ) == false ) {
return new SimpleDateFormat( pattern, locale );
}
} catch ( Exception e ) {
logger.warn( "Invalid format string found in locale settings", e ); // NON-NLS
}
return new SimpleDateFormat( defaultPattern, locale );
}
public void applyLocaleSettings( final LocaleSettings localeSettings ) {
final SimpleDateFormat isoDateFormat = createSafely( localeSettings.getDatetimeFormatPattern(),
WorkspaceSettings.DATETIME_FORMAT_DEFAULT, localeSettings.getLocale() );
final TimeZone timeZone = localeSettings.getTimeZone();
isoDateFormat.setTimeZone( timeZone );
setDefaultRenderer( Date.class, new FormattingTableCellRenderer( isoDateFormat ) );
setDefaultRenderer( Timestamp.class, new FormattingTableCellRenderer( isoDateFormat ) );
final DateCellEditor dateCellEditor = new DateCellEditor( Date.class );
dateCellEditor.setDateFormat( isoDateFormat );
setDefaultEditor( Date.class, dateCellEditor );
final DateCellEditor timestampEditor = new DateCellEditor( Timestamp.class );
timestampEditor.setDateFormat( isoDateFormat );
setDefaultEditor( Timestamp.class, timestampEditor );
final SimpleDateFormat dateFormat = createSafely( localeSettings.getDateFormatPattern(),
WorkspaceSettings.DATE_FORMAT_DEFAULT, localeSettings.getLocale() );
dateFormat.setTimeZone( timeZone );
setDefaultRenderer( java.sql.Date.class, new FormattingTableCellRenderer( dateFormat ) );
final DateCellEditor sqlDateCellEditor = new DateCellEditor( java.sql.Date.class );
sqlDateCellEditor.setDateFormat( dateFormat );
setDefaultEditor( java.sql.Date.class, sqlDateCellEditor );
final SimpleDateFormat timeFormat = createSafely( localeSettings.getTimeFormatPattern(),
WorkspaceSettings.TIME_FORMAT_DEFAULT, localeSettings.getLocale() );
timeFormat.setTimeZone( timeZone );
setDefaultRenderer( Time.class, new FormattingTableCellRenderer( timeFormat ) );
final TimeCellEditor timeCellEditor = new TimeCellEditor( Time.class );
timeCellEditor.setDateFormat( timeFormat );
setDefaultEditor( Time.class, timeCellEditor );
}
/**
* Returns true if the cell at <code>row</code> and <code>column</code> is editable. Otherwise, invoking
* <code>setValueAt</code> on the cell will have no effect.
* <p/>
* <b>Note</b>: The column is specified in the table view's display order, and not in the <code>TableModel</code>'s
* column order. This is an important distinction because as the user rearranges the columns in the table, the column
* at a given index in the view will change. Meanwhile the user's actions never affect the model's column ordering.
*
* @param row the row whose value is to be queried
* @param column the column whose value is to be queried
* @return true if the cell is editable
* @see #setValueAt
*/
public boolean isCellEditable( final int row, final int column ) {
final int columnIndex = convertColumnIndexToModel( column );
if ( getModel().isCellEditable( row, columnIndex ) ) {
if ( getCellEditor( row, columnIndex ) == null ) {
// no editor, so not editable ...
return false;
}
return true;
}
return false;
}
public TableCellRenderer getCellRenderer( final int row, final int viewColumn ) {
final int column = convertColumnIndexToModel( viewColumn );
final Object value = getModel().getValueAt( row, column );
if ( value instanceof GroupingHeader ) {
return groupingCellRenderer;
}
final ElementMetaDataTableModel model = (ElementMetaDataTableModel) getModel();
final Class columnClass = model.getClassForCell( row, column );
if ( columnClass.isArray() ) {
return arrayCellRenderer;
}
final PropertyEditor propertyEditor = model.getEditorForCell( row, column );
if ( propertyEditor != null ) {
propertyEditorCellRenderer.setPropertyEditor( propertyEditor );
return propertyEditorCellRenderer;
}
final TableColumn tableColumn = getColumnModel().getColumn( column );
final TableCellRenderer renderer = tableColumn.getCellRenderer();
if ( renderer != null ) {
return renderer;
}
final TableCellRenderer defaultRenderer = getDefaultRenderer( columnClass );
if ( defaultRenderer != null ) {
return defaultRenderer;
}
if ( logger.isTraceEnabled() ) {
logger.trace( "No renderer for column class " + columnClass ); // NON-NLS
}
return getDefaultRenderer( Object.class );
}
public TableCellEditor getCellEditor( final int row, final int viewColumn ) {
final int column = convertColumnIndexToModel( viewColumn );
final Object value = getModel().getValueAt( row, column );
if ( value instanceof GroupingHeader ) {
return getDefaultEditor( GroupingHeader.class );
}
final ElementMetaDataTableModel model = (ElementMetaDataTableModel) getModel();
final PropertyEditor propertyEditor = model.getEditorForCell( row, column );
final Class columnClass = model.getClassForCell( row, column );
if ( propertyEditor != null ) {
final String[] tags = propertyEditor.getTags();
if ( columnClass.isArray() ) {
arrayCellEditor.setPropertyEditorType( propertyEditor.getClass() );
} else if ( tags == null || tags.length == 0 ) {
propertyEditorCellEditor.setPropertyEditor( propertyEditor );
return propertyEditorCellEditor;
} else {
taggedPropertyEditorCellEditor.setPropertyEditor( propertyEditor );
return taggedPropertyEditorCellEditor;
}
}
final TableColumn tableColumn = getColumnModel().getColumn( column );
final TableCellEditor renderer = tableColumn.getCellEditor();
if ( renderer != null ) {
return renderer;
}
if ( columnClass.isArray() ) {
return arrayCellEditor;
}
final TableCellEditor editor = getDefaultEditor( columnClass );
if ( editor != null && logger.isTraceEnabled() ) {
logger.trace( "Using preconfigured default editor for column class " + columnClass + ": " + editor ); // NON-NLS
}
return editor;
}
public void setReportDesignerContext( final ReportDesignerContext newContext ) {
final ReportDesignerContext oldContext = arrayCellEditor.getReportDesignerContext();
if ( oldContext != null ) {
oldContext.removePropertyChangeListener( this.changeHandler );
final ReportDocumentContext oldActiveContext = getReportRenderContext();
updateActiveContext( oldActiveContext, null );
}
arrayCellEditor.setReportDesignerContext( newContext );
stringValueCellEditor.setReportDesignerContext( newContext );
expressionsCellRenderer.setReportDesignerContext( newContext );
expressionsCellEditor.setReportDesignerContext( newContext );
structureFunctionCellEditor.setReportDesignerContext( newContext );
if ( newContext != null ) {
newContext.addPropertyChangeListener( ReportDesignerContext.ACTIVE_CONTEXT_PROPERTY, changeHandler );
updateActiveContext( null, newContext.getActiveContext() );
}
}
protected void updateActiveContext( final ReportDocumentContext oldContext,
final ReportDocumentContext activeContext ) {
structureFunctionCellEditor.setRenderContext( activeContext );
reportPreProcessorCellEditor.setRenderContext( activeContext );
}
public ReportDesignerContext getReportDesignerContext() {
return arrayCellEditor.getReportDesignerContext();
}
public ReportDocumentContext getReportRenderContext() {
final ReportDesignerContext reportDesignerContext = getReportDesignerContext();
if ( reportDesignerContext == null ) {
return null;
}
return reportDesignerContext.getActiveContext();
}
public boolean isFormulaFragment() {
return stringValueCellEditor.isFormulaFragment();
}
public void setFormulaFragment( final boolean formulaFragment ) {
stringValueCellEditor.setFormulaFragment( formulaFragment );
}
public void stopEditing() {
final TableCellEditor cellEditor = getCellEditor();
if ( cellEditor != null ) {
cellEditor.stopCellEditing();
}
}
}