/*!
* 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.pentaho.reporting.designer.core.Messages;
import org.pentaho.reporting.designer.core.editor.ReportDocumentContext;
import org.pentaho.reporting.designer.core.util.UtilMessages;
import org.pentaho.reporting.engine.classic.core.designtime.DesignTimeUtil;
import org.pentaho.reporting.engine.classic.core.modules.parser.base.ClassicEngineFactoryParameters;
import org.pentaho.reporting.libraries.base.util.FilesystemFilter;
import org.pentaho.reporting.libraries.base.util.IOUtils;
import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
import org.pentaho.reporting.libraries.base.util.StringUtils;
import org.pentaho.reporting.libraries.designtime.swing.event.DocumentChangeHandler;
import org.pentaho.reporting.libraries.designtime.swing.filechooser.CommonFileChooser;
import org.pentaho.reporting.libraries.designtime.swing.filechooser.FileChooserService;
import org.pentaho.reporting.libraries.designtime.swing.propertyeditors.ValidatingPropertyEditorComponent;
import org.pentaho.reporting.libraries.repository.DefaultMimeRegistry;
import org.pentaho.reporting.libraries.resourceloader.LoaderParameterKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceKey;
import org.pentaho.reporting.libraries.resourceloader.ResourceKeyCreationException;
import org.pentaho.reporting.libraries.resourceloader.ResourceKeyUtils;
import org.pentaho.reporting.libraries.resourceloader.ResourceLoadingException;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.filechooser.FileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* A UI component which will capture a resource name / location and if that resource should be linked externally or
* embedded in the report.
*
* @author Thomas Morgner
* @author David Kincade
* @author Ezequiel Cuellar
*/
public class ResourcePropertyEditorComponent extends JComponent implements ValidatingPropertyEditorComponent {
/**
* Enumeration defining the possible values for Document Locations
*/
public enum DocumentLocation {
LINK, EMBED
}
private static final DefaultMimeRegistry mimeRegistry = new DefaultMimeRegistry();
private JRadioButton linkToRadio;
private JRadioButton embedRadio;
private JTextField sourceTextField;
private ReportDocumentContext reportRenderContext;
private Object currentValue;
private String lastTextValue;
private DocumentLocation lastDocumentLocation;
private boolean disableEvents;
/**
* Constructor which sets up the location of all the elements on this component
*
* @param reportRenderContext the report render context for the current report
*/
public ResourcePropertyEditorComponent( final ReportDocumentContext reportRenderContext ) {
// Get the information used to embed items in the document bundle
if ( reportRenderContext == null ) {
throw new NullPointerException( "The ReportRenderContext must not be null" );
}
this.reportRenderContext = reportRenderContext;
final SourceChangeHandler changeHandler = new SourceChangeHandler();
sourceTextField = new JTextField( 35 );
sourceTextField.getDocument().addDocumentListener( changeHandler );
linkToRadio = new JRadioButton( UtilMessages.getInstance().getString( "ResourcePropertyEditorComponent.LinkTo" ) );
linkToRadio.setSelected( true );
linkToRadio.addChangeListener( changeHandler );
embedRadio = new JRadioButton( UtilMessages.getInstance().getString( "ResourcePropertyEditorComponent.Embed" ) );
embedRadio.addChangeListener( changeHandler );
final JPanel identifierSelectorPanel = new JPanel( new BorderLayout() );
identifierSelectorPanel.add( sourceTextField, BorderLayout.CENTER );
identifierSelectorPanel.add( new JButton( new FileSelectAction() ), BorderLayout.EAST );
final ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add( linkToRadio );
buttonGroup.add( embedRadio );
final JPanel identifierPane = new JPanel( new BorderLayout() );
identifierPane.setBorder( BorderFactory.createEmptyBorder( 0, 6, 0, 0 ) );
identifierPane
.add( new JLabel( UtilMessages.getInstance().getString( "ResourcePropertyEditorComponent.SourceLabel" ) ),
BorderLayout.NORTH );
identifierPane.add( identifierSelectorPanel, BorderLayout.CENTER );
final JPanel selectionPanel = new JPanel( new GridLayout( 2, 1 ) );
selectionPanel.setBorder( BorderFactory.createEmptyBorder( 0, 5, 0, 0 ) );
selectionPanel.add( linkToRadio );
selectionPanel.add( embedRadio );
setLayout( new BorderLayout() );
add( identifierPane, BorderLayout.CENTER );
add( selectionPanel, BorderLayout.SOUTH );
}
/**
* Returns the current value of the property as a <code>ResourceKey</code>. <br/> If the key is embedded, additional
* information will be stored in the <code>Factory Parameters</code> in the following way: <table>
* <row><th>key</th><th>value</th></row> <row><td>original_value</td><td>The string version of the key that was
* original entered</td></row> <row><td>embedded</td><td>Indicates if the resource should be / is embedded in the
* document bundle</td></row> </table>
*
* @return the ResourceKey being created in this
*/
public Object getValue() {
// If nothing has changed since the last check, we should return the current value
// instead of recomputing it
final String currentTextValue = sourceTextField.getText();
final DocumentLocation currentDocumentLocation = getDocumentLocation();
if ( currentTextValue.equals( lastTextValue ) &&
currentDocumentLocation.equals( lastDocumentLocation ) ) {
return currentValue;
}
if ( StringUtils.isEmpty( currentTextValue ) ) {
lastTextValue = currentTextValue;
lastDocumentLocation = currentDocumentLocation;
return null;
}
// Try to compute a ResourceKey from the current data
try {
currentValue = createResourceKey( currentTextValue, currentDocumentLocation );
} catch ( Exception ignored ) {
// The text does not yield a valid ResourceKey
currentValue = currentTextValue;
}
lastTextValue = currentTextValue;
lastDocumentLocation = currentDocumentLocation;
return currentValue;
}
/**
* Creates a ResourceKey based on the current values in this component.
*
* @param source the current location used in the creation of the resource key
* @param docLoc the link/embed flag used in the creation of the resource key
* @return the newly created ResourceKey
* @throws ResourceKeyCreationException indicates the current values do not generate a valid ResourceKey
*/
private Object createResourceKey( final String source,
final DocumentLocation docLoc )
throws ResourceKeyCreationException, IOException, ResourceLoadingException {
if ( StringUtils.isEmpty( source, true ) ) {
return null;
}
// If we are embedding the key, create some factory patameters
if ( DocumentLocation.EMBED.equals( docLoc ) ) {
final String mimeType = mimeRegistry.getMimeType( source );
final String pattern = "resources/image{0}" + IOUtils.getInstance().getFileExtension( source ); // NON-NLS
final Map<LoaderParameterKey, String> parameters = new HashMap<LoaderParameterKey, String>();
parameters.put( ClassicEngineFactoryParameters.ORIGINAL_VALUE, source );
parameters.put( ClassicEngineFactoryParameters.MIME_TYPE, mimeType );
parameters.put( ClassicEngineFactoryParameters.PATTERN, pattern );
parameters.put( ClassicEngineFactoryParameters.EMBED, "true" ); // NON-NLS
// create an embedded key in here.
final ResourceKey key = ResourceKeyUtils.toResourceKey
( source, reportRenderContext.getResourceManager(),
reportRenderContext.getReportDefinition().getContentBase(), parameters );
return ResourceKeyUtils.embedResourceInKey( reportRenderContext.getResourceManager(),
key, key.getFactoryParameters() );
}
// See if we can create a valid resource key
final ResourceKey contentBaseKey = reportRenderContext.getReportDefinition().getContentBase();
final ResourceKey resourceKey = ResourceKeyUtils.toResourceKey
( source, reportRenderContext.getResourceManager(), contentBaseKey, Collections.EMPTY_MAP );
if ( isDerivedKey( resourceKey, contentBaseKey ) ) {
return source;
}
return resourceKey;
}
private boolean isDerivedKey( final ResourceKey key, final ResourceKey contentKey ) {
if ( contentKey == null ) {
return false;
}
if ( contentKey.getSchema().equals( key.getSchema() ) ) {
return true;
}
return isDerivedKey( key, contentKey.getParent() );
}
/**
* Sets the value of the source field
*
* @param value the current string value
*/
public void setValue( final String value ) {
sourceTextField.setText( value );
setDocumentLocation( DocumentLocation.LINK );
fireValueChangeEvent();
}
/**
* Sets the value of the source field
*
* @param object the current value
*/
public void setValue( final Object object ) {
// Nothing to do if nothing is changing
if ( ObjectUtilities.equal( currentValue, object ) ) {
return;
}
try {
disableEvents = true;
// Set the internal fields properly
if ( object == null ) {
sourceTextField.setText( "" );
setDocumentLocation( DocumentLocation.LINK );
} else if ( object instanceof ResourceKey ) {
// Is the resource key an embedded resource key?
final ResourceKey resourceKey = (ResourceKey) object;
final Object originalValue =
resourceKey.getFactoryParameters().get( ClassicEngineFactoryParameters.ORIGINAL_VALUE );
if ( originalValue != null ) {
sourceTextField.setText( String.valueOf( originalValue ) );
setDocumentLocation( DocumentLocation.EMBED );
} else {
sourceTextField.setText( resourceKey.getIdentifierAsString() );
setDocumentLocation( DocumentLocation.LINK );
}
} else {
sourceTextField.setText( String.valueOf( object ) );
setDocumentLocation( DocumentLocation.LINK );
}
// Save the current values as the latest values
currentValue = object;
} finally {
disableEvents = false;
}
// Let everyone know there has been a change in values
fireValueChangeEvent();
}
/**
* Returns the current value of the <code>embed / link</code> radio button
*
* @return the current value of the document location
*/
private DocumentLocation getDocumentLocation() {
return ( linkToRadio.isSelected() ? DocumentLocation.LINK : DocumentLocation.EMBED );
}
/**
* Sets the document location to the specified value
*
* @param documentLocation the value of the document location
*/
private void setDocumentLocation( final DocumentLocation documentLocation ) {
if ( DocumentLocation.LINK.equals( documentLocation ) ) {
linkToRadio.setSelected( true );
embedRadio.setSelected( false );
} else {
linkToRadio.setSelected( false );
embedRadio.setSelected( true );
}
}
/**
* Indicates if the current value is a valid value. The current value is valid if the document location is LINK, or
* the current value is a ResourceKey and the document location is embed.
*/
public boolean isValidEditorValue() {
if ( getDocumentLocation().equals( DocumentLocation.LINK ) ) {
return true;
}
final Object o = getValue();
return o instanceof ResourceKey;
}
/**
* Notifies all the listeners that a value on this component has changed
*/
private void fireValueChangeEvent() {
if ( disableEvents ) {
return;
}
final Object oldValue = currentValue;
final Object newValue = getValue();
firePropertyChange( null, oldValue, newValue );
}
/**
* Notifies all the listeners that a value on this component has changed
*/
private void fireRadioChangeEvent() {
if ( disableEvents ) {
return;
}
final Object oldValue = lastDocumentLocation;
final Object newValue = getDocumentLocation();
firePropertyChange( null, oldValue, newValue );
}
/**
* The class which will handle the notification changes when the source text field changes.
*/
private class SourceChangeHandler extends DocumentChangeHandler implements ChangeListener {
private SourceChangeHandler() {
}
/**
* Indicates the radio button has changed
*/
public void stateChanged( final ChangeEvent e ) {
fireRadioChangeEvent();
}
/**
* Indicates the text value changed
*/
protected void handleChange( final DocumentEvent e ) {
fireValueChangeEvent();
}
}
private class FileSelectAction extends AbstractAction {
private FileSelectAction() {
putValue( Action.NAME, ".." );
}
public void actionPerformed( final ActionEvent aEvt ) {
final File reportContextFile = DesignTimeUtil.getContextAsFile( reportRenderContext.getReportDefinition() );
final FileFilter[] filters = {
new FilesystemFilter( ".properties", // NON-NLS
Messages.getString( "BundledResourceEditor.PropertiesTranslations" ) ),
new FilesystemFilter( new String[] { ".xml", ".report", ".prpt", ".prpti", ".prptstyle" }, // NON-NLS
Messages.getString( "BundledResourceEditor.Resources" ), true ),
new FilesystemFilter( new String[] { ".gif", ".jpg", ".jpeg", ".png", ".svg", ".wmf" }, // NON-NLS
Messages.getString( "BundledResourceEditor.Images" ), true ),
};
final CommonFileChooser fileChooser = FileChooserService.getInstance().getFileChooser( "resources" );//NON-NLS
fileChooser.setFilters( filters );
final String sourceFile = sourceTextField.getText();
if ( StringUtils.isEmpty( sourceFile ) == false ) {
if ( reportContextFile != null ) {
fileChooser.setSelectedFile( new File( reportContextFile.getParentFile(), sourceTextField.getText() ) );
} else {
fileChooser.setSelectedFile( new File( sourceTextField.getText() ) );
}
}
if ( fileChooser.showDialog( ResourcePropertyEditorComponent.this, JFileChooser.OPEN_DIALOG ) == false ) {
return;
}
final File file = fileChooser.getSelectedFile();
if ( file == null ) {
return;
}
final String path;
if ( reportContextFile != null ) {
path = IOUtils.getInstance().createRelativePath( file.getPath(), reportContextFile.getAbsolutePath() );
} else {
path = file.getPath();
}
sourceTextField.setText( path );
}
}
}