/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2003-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library 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.
*/
package org.geotoolkit.gui.swing.image;
import java.util.List;
import java.util.Locale;
import java.text.ParseException;
import javax.swing.Icon;
import javax.swing.JTree;
import javax.swing.JPanel;
import javax.swing.JComponent;
import javax.swing.JSplitPane;
import javax.swing.JScrollPane;
import javax.swing.BorderFactory;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreeSelectionModel;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import java.awt.image.renderable.ParameterBlock;
import java.awt.image.renderable.RenderableImage;
import java.awt.image.RenderedImage;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Container;
import java.awt.Component;
import java.awt.Dimension;
import javax.media.jai.RenderedOp;
import javax.media.jai.RenderableOp;
import javax.media.jai.PropertySource;
import javax.media.jai.OperationNode;
import javax.media.jai.ParameterList;
import javax.media.jai.ParameterListDescriptor;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.KernelJAI;
import org.apache.sis.util.Classes;
import org.geotoolkit.internal.swing.SwingUtilities;
import org.geotoolkit.gui.swing.Dialog;
import org.geotoolkit.gui.swing.IconFactory;
import org.geotoolkit.gui.swing.ParameterEditor;
import org.geotoolkit.gui.swing.tree.Trees;
import org.geotoolkit.gui.swing.tree.TreeNode;
import org.geotoolkit.gui.swing.tree.NamedTreeNode;
import org.geotoolkit.gui.swing.tree.MutableTreeNode;
import org.geotoolkit.gui.swing.tree.DefaultTreeModel;
import org.geotoolkit.gui.swing.tree.DefaultMutableTreeNode;
import org.geotoolkit.resources.Vocabulary;
import org.geotoolkit.lang.Debug;
/**
* Displays a chain of images as a tree. It may be a chain of {@link RenderedImage} or a chain of
* {@link RenderableImage}. Those images are often the result of some operation (i.e. are actually
* instances of {@link RenderedOp} or {@link RenderableOp}). The image given to the constructor is
* the root of the tree. The root contains the following children nodes:
* <p>
* <ul>
* <li>One node for each {@linkplain RenderedImage#getSources source image}, if any.</li>
* <li>One node for each {@linkplain OperationNode#getParameterBlock image parameter}, if any.</li>
* </ul>
* <p>
* Each source image can have its own source and parameters. In an analogy to a file system,
* {@linkplain RenderedImage#getSources source images} are like directories and
* {@linkplain OperationNode#getParameterBlock image parameters} are like files.
* <p>
* When a tree node is selected in the left pane, the content of the right pane is adjusted
* accordingly. If the node is an image, a "preview" tab is show together with an "information"
* tab. Informations include the {@linkplain java.awt.image.ColorModel color model},
* {@linkplain java.awt.image.SampleModel sample model}, data type, etc. If the selected tree node
* is a parameter, then the right pane show the parameter value in {@linkplain ParameterEditor
* some widget} appropriate for the parameter type.
*
* <p> </p>
* <p align="center"><img src="doc-files/OperationTreeBrowser.png"></p>
* <p> </p>
*
* @author Martin Desruisseaux (IRD)
* @author Lionel Flahaut (IRD)
* @version 3.12
*
* @see ImageProperties
* @see ParameterEditor
* @see RegisteredOperationBrowser
*
* @since 2.3
* @module
*/
@SuppressWarnings("serial")
public class OperationTreeBrowser extends JComponent implements Dialog {
/**
* Key for {@link PropertySource}.
*/
private static final String IMAGE = "Image";
/**
* Key for parameter card.
*/
private static final String PARAMETER = "Parameter";
/**
* The image properties panel. Will be constructed only when first needed,
* and the added to the card layout with the {@code IMAGE} name.
*/
private ImageProperties imageProperties;
/**
* The parameter editor panel. Will be constructed only when first needed,
* then added to the card layout with the {@code PARAMETER} name.
*/
private ParameterEditor parameterEditor;
/**
* The properties panel. The content for this panel depends on
* the selected tree item, but usually includes the following:
* <p>
* <ul>
* <li>An {@link ImageProperties} instance.</li>
* <li>An {@link ParameterEditor} instance.</li>
* </ul>
*/
private final Container cards = new JPanel(new CardLayout());
/**
* Constructs a new browser for the given rendered image.
*
* @param source The last image from the rendering chain to browse.
*/
public OperationTreeBrowser(final RenderedImage source) {
this(getTree(source, getDefaultLocale()));
}
/**
* Constructs a new browser for the given renderable image.
*
* @param source The last image from the rendering chain to browse.
*/
public OperationTreeBrowser(final RenderableImage source) {
this(getTree(source, getDefaultLocale()));
}
/**
* Constructs a new browser for the tree.
*
* @param model The tree model built from the rendering chain to browse.
*/
private OperationTreeBrowser(final TreeModel model) {
setLayout(new BorderLayout());
final Listeners listeners = new Listeners();
final JTree tree = new JTree(model);
tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
tree.setCellRenderer(new CellRenderer());
tree.setBorder(BorderFactory.createEmptyBorder(6,6,0,0));
tree.addTreeSelectionListener(listeners);
final JSplitPane split;
split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new JScrollPane(tree), cards);
split.setDividerLocation(220);
add(split, BorderLayout.CENTER);
setPreferredSize(new Dimension(700, 400));
}
/**
* Shows the operation tree for the specified rendered image in a frame.
* This convenience method is mostly a helper for debugging purpose.
*
* @param image The image to display in a frame.
*/
@Debug
public static void show(final RenderedImage image) {
new OperationTreeBrowser(image).showFrame(image);
}
/**
* Shows the operation tree for the specified renderable image in a frame.
* This convenience method is mostly a helper for debugging purpose.
*
* @param image The image to display in a frame.
*/
@Debug
public static void show(final RenderableImage image) {
new OperationTreeBrowser(image).showFrame(image);
}
/**
* Prints to the {@linkplain System#out standard output stream} the operation chain for
* the specified image. This convenience method is used mostly for debugging purpose.
*
* @param image The image for which to print its sources and parameters.
*
* @since 2.4
*/
@Debug
public static void print(final RenderedImage image) {
Trees.print(getTree(image, Locale.getDefault(Locale.Category.DISPLAY)));
}
/**
* Prints to the {@linkplain System#out standard output stream} the operation chain for
* the specified image. This convenience method is used mostly for debugging purpose.
*
* @param image The image for which to print its sources and parameters.
*
* @since 2.4
*/
@Debug
public static void print(final RenderableImage image) {
Trees.print(getTree(image, Locale.getDefault(Locale.Category.DISPLAY)));
}
/**
* Returns a tree with all sources and parameters for the given rendered image.
*
* @param image The last image from an operation chain.
* @param locale The locale for tree node names.
* @return The tree for the given image and all its sources.
*/
public static TreeModel getTree(final RenderedImage image, final Locale locale) {
return new DefaultTreeModel(getNode(image, locale));
}
/**
* Returns a tree with all sources and parameters for the given renderable image.
*
* @param image The last image from an operation chain.
* @param locale The locale for tree node names.
* @return The tree for the given image and all its sources.
*/
public static TreeModel getTree(final RenderableImage image, final Locale locale) {
return new DefaultTreeModel(getNode(image, locale));
}
/**
* Returns the root node of a tree with all sources and parameters for the given source.
*
* @param image The last image from an operation chain.
* @param locale The locale for tree node names.
* @return The tree for the given image and all its sources.
*/
private static MutableTreeNode getNode(final RenderedImage image, final Locale locale) {
final DefaultMutableTreeNode root = new NamedTreeNode(getName(image), image);
final List<RenderedImage> sources = image.getSources();
if (sources != null) {
final int n = sources.size();
for (int i=0; i<n; i++) {
root.add(getNode(sources.get(i), locale));
}
}
if (image instanceof OperationNode) {
addParameters(root, (OperationNode)image, locale);
}
return root;
}
/**
* Returns the root node of a tree with all sources and parameters for the given source.
*
* @param image The last image from an operation chain.
* @param locale The locale for tree node names.
* @return The tree for the given image and all its sources.
*/
private static MutableTreeNode getNode(final RenderableImage image, final Locale locale) {
final DefaultMutableTreeNode root = new NamedTreeNode(getName(image), image);
final List<RenderableImage> sources = image.getSources();
if (sources != null) {
final int n = sources.size();
for (int i=0; i<n; i++) {
root.add(getNode(sources.get(i), locale));
}
}
if (image instanceof OperationNode) {
addParameters(root, (OperationNode)image, locale);
}
return root;
}
/**
* Returns a name for the given image. The default implementation returns the operation
* name if the image is an instance of {@link RenderedOp}. Otherwise, it returns the
* image class.
*
* @param image The image.
* @return A name for the given image.
*/
private static String getName(final Object image) {
if (image instanceof OperationNode) {
return ((OperationNode) image).getOperationName();
}
if (image instanceof CharSequence) {
return image.toString();
}
return Classes.getShortClassName(image);
}
/**
* Add the parameters from the specified operation to the specified tree node.
*
* @param root The tree node to add parameters to.
* @param operation The operation for which to fetch parameters.
* @param locale The locale for tree node names.
*/
private static void addParameters(final DefaultMutableTreeNode root,
final OperationNode operation,
final Locale locale)
{
final ParameterBlock param = operation.getParameterBlock();
final ParameterListDescriptor descriptor;
if (param instanceof ParameterList) {
descriptor = ((ParameterList) param).getParameterListDescriptor();
} else {
final String name = operation.getOperationName();
final String mode = operation.getRegistryModeName();
descriptor = operation.getRegistry().getDescriptor(mode, name)
.getParameterListDescriptor(mode);
}
Vocabulary resources = null;
final String[] names = descriptor.getParamNames();
final int n = param.getNumParameters();
for (int i=0; i<n; i++) {
String name = null;
if (names!=null && i<names.length) {
name = names[i];
}
if (name == null) {
if (resources == null) {
resources = Vocabulary.getResources(locale);
}
name = resources.getString(Vocabulary.Keys.Parameter_1, i);
}
root.add(new NamedTreeNode(name, param.getObjectParameter(i), false));
}
}
/**
* The listener for various event in the {@link OperationTreeBrowser} widget.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.3
* @module
*/
private final class Listeners implements TreeSelectionListener {
/**
* Called whenever the value of the selection changes. This method uses the
* {@link TreeNode#getAllowsChildren} in order to determines if the selection
* is a source (allows children = {@code true}) or a parameter
* (allows children = {@code false}).
*/
@Override
public void valueChanged(final TreeSelectionEvent event) {
Object selection = null; // The selected tree element.
boolean isSource = false; // Is 'selected' a source or a parameter?
OperationNode operation = null; // The parent of the selected element as an op.
int paramIndex = -1; // The index of the selected element.
final TreePath path = event.getPath();
if (path != null) {
selection = path.getLastPathComponent();
/*
* Some of piece of code in the following block can work with the Swing's
* TreeNode (i.e. it doesn't require the fixed Geotk's TreeNode).
*/
if (selection instanceof javax.swing.tree.TreeNode) {
javax.swing.tree.TreeNode node = (javax.swing.tree.TreeNode) selection;
isSource = node.getAllowsChildren();
node = node.getParent();
if (node instanceof TreeNode) {
final Object candidate = ((TreeNode)node).getUserObject();
if (candidate instanceof OperationNode) {
operation = (OperationNode) candidate;
final int count = node.getChildCount();
for (int n=-1,i=0; i<count; i++) {
final javax.swing.tree.TreeNode leaf = node.getChildAt(i);
if (!leaf.getAllowsChildren()) {
n++; // Count only parameters, not sources.
}
if (leaf == selection) {
paramIndex = n;
break;
}
}
}
}
}
if (selection instanceof TreeNode) {
selection = ((TreeNode) selection).getUserObject();
}
}
if (isSource) {
showSourceEditor(selection);
} else {
showParameterEditor(selection);
}
if (parameterEditor != null) {
parameterEditor.setDescription(operation, paramIndex);
}
}
}
/**
* Invoked when the user clicks on a source node in the operation tree (left pane).
* This method show a properties panel in the right pane appropriate for the given
* selection.
*
* @param selection The user selection. This object is usually an instance of
* {@link RenderedImage}, {@link RenderableImage} or {@link PropertySource}.
* @return {@code true} if this method has been able to find an editor, or
* {@code false} otherwise.
*/
protected boolean showSourceEditor(final Object selection) {
if (imageProperties == null) {
imageProperties = new ImageProperties();
cards.add(imageProperties, IMAGE);
}
((CardLayout) cards.getLayout()).show(cards, IMAGE);
if (selection instanceof RenderedImage) {
imageProperties.setImage((RenderedImage) selection);
return true;
}
if (selection instanceof RenderableImage) {
imageProperties.setImage((RenderableImage) selection);
return true;
}
if (selection instanceof PropertySource) {
imageProperties.setImage((PropertySource) selection);
return true;
}
imageProperties.setImage((PropertySource) null);
return false;
}
/**
* Invoked when the user clicks on a parameter node in the operation tree (left pane).
* This method show a properties panel in the right pane appropriate for the given
* selection.
*
* @param selection The user selection. This object is usually an instance of
* {@link Number}, {@link KernelJAI}, {@link LookupTableJAI} or some other
* parameter object.
* @return {@code true} if this method has been able to find an editor, or
* {@code false} otherwise.
*/
protected boolean showParameterEditor(final Object selection) {
if (parameterEditor == null) {
parameterEditor = new ParameterEditor();
parameterEditor.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(9, 9, 9, 9), parameterEditor.getBorder()));
cards.add(parameterEditor, PARAMETER);
}
((CardLayout) cards.getLayout()).show(cards, PARAMETER);
parameterEditor.setParameterValue(selection);
return true;
}
/**
* Forces the current values to be taken from the editable fields and set them as the
* current values. The default implementation does nothing since there is no editable
* fields in this widget.
*
* @since 3.12
*/
@Override
public void commitEdit() throws ParseException {
}
/**
* {@inheritDoc}
*
* @param title The dialog box title, or {@code null} for a default title.
*/
@Override
public boolean showDialog(final Component owner, String title) {
if (title == null) {
title = Vocabulary.getResources(getLocale()).getString(Vocabulary.Keys.Operations);
}
return SwingUtilities.showDialog(owner, this, title);
}
/**
* Implementation of public {@link #show} methods.
*/
private void showFrame(final Object image) {
SwingUtilities.show(this, Classes.getShortClassName(this) + " - " + getName(image));
}
/**
* The tree cell renderer, which select icons according the selected object type.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.3
* @module
*/
@SuppressWarnings("serial")
private static final class CellRenderer extends DefaultTreeCellRenderer {
/** The icon for folder. */
private final Icon open, closed;
/** The icon for images, or {@code null} if none. */
private static Icon image;
/** The icon for parameters, or {@code null} if none. */
private static Icon parameter;
/**
* Creates a cell renderer.
*/
private CellRenderer() {
open = getDefaultOpenIcon();
closed = getDefaultClosedIcon();
if (image == null) {
final IconFactory icons = IconFactory.DEFAULT;
image = icons.getIcon("toolbarButtonGraphics/general/Properties16.gif");
parameter = icons.getIcon("toolbarButtonGraphics/general/Preferences16.gif");
}
}
/**
* Configures the renderer based on the passed in components.
*/
@Override
public Component getTreeCellRendererComponent(final JTree tree, final Object value,
final boolean selelected, final boolean expanded,
final boolean leaf, final int row, final boolean hasFocus)
{
if (((TreeNode) value).getUserObject() instanceof RenderedImage) {
if (image != null) {
setOpenIcon (image);
setClosedIcon(image);
setLeafIcon (image);
} else {
setLeafIcon(null);
}
} else if (parameter != null) {
setOpenIcon (open);
setClosedIcon(closed);
setLeafIcon (parameter);
}
return super.getTreeCellRendererComponent(tree, value, selected, expanded,
leaf, row, hasFocus);
}
}
}