/*!
* 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 java.awt.BorderLayout;
import java.awt.GridLayout;
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;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.filechooser.FileFilter;
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;
/**
* 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);
}
}
}