/*! * 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(); } } }