001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2003, Institut de Recherche pour le Développement
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation; either
010: * version 2.1 of the License, or (at your option) any later version.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.gui.swing.image;
018:
019: import java.util.List;
020: import java.util.Locale;
021: import javax.swing.Icon;
022: import javax.swing.JTree;
023: import javax.swing.JPanel;
024: import javax.swing.JFrame;
025: import javax.swing.JSplitPane;
026: import javax.swing.JScrollPane;
027: import javax.swing.BorderFactory;
028: import javax.swing.tree.TreePath;
029: import javax.swing.tree.TreeModel;
030: import javax.swing.tree.DefaultTreeModel;
031: import javax.swing.tree.TreeSelectionModel;
032: import javax.swing.tree.DefaultTreeCellRenderer;
033: import javax.swing.event.TreeSelectionEvent;
034: import javax.swing.event.TreeSelectionListener;
035: import java.awt.image.renderable.ParameterBlock;
036: import java.awt.image.renderable.RenderableImage;
037: import java.awt.image.RenderedImage;
038: import java.awt.BorderLayout;
039: import java.awt.CardLayout;
040: import java.awt.Container;
041: import java.awt.Component;
042: import java.awt.Dimension;
043: import java.io.File;
044: import java.io.FileNotFoundException;
045: import java.io.IOException;
046: import javax.imageio.ImageIO;
047: import javax.media.jai.RenderedOp;
048: import javax.media.jai.RenderableOp;
049: import javax.media.jai.PropertySource;
050: import javax.media.jai.OperationNode;
051: import javax.media.jai.ParameterList;
052: import javax.media.jai.ParameterListDescriptor;
053: import javax.media.jai.LookupTableJAI;
054: import javax.media.jai.KernelJAI;
055:
056: import org.geotools.resources.Utilities;
057: import org.geotools.resources.SwingUtilities;
058: import org.geotools.resources.i18n.Vocabulary;
059: import org.geotools.resources.i18n.VocabularyKeys;
060: import org.geotools.gui.swing.IconFactory;
061: import org.geotools.gui.swing.ParameterEditor;
062: import org.geotools.gui.swing.tree.Trees;
063: import org.geotools.gui.swing.tree.TreeNode;
064: import org.geotools.gui.swing.tree.NamedTreeNode;
065: import org.geotools.gui.swing.tree.MutableTreeNode;
066: import org.geotools.gui.swing.tree.DefaultMutableTreeNode;
067: import org.geotools.resources.Arguments;
068: import org.geotools.resources.i18n.ErrorKeys;
069: import org.geotools.resources.i18n.Errors;
070:
071: /**
072: * Display a chain of images as a tree. It may be a chain of {@link RenderedImage} or a chain of
073: * {@link RenderableImage}. Those images are often the result of some operation (i.e. are actually
074: * instances of {@link RenderedOp} or {@link RenderableOp}). The image given to the constructor is
075: * the root of the tree. The root contains the following children nodes:
076: *
077: * <ul>
078: * <li>One node for each {@linkplain RenderedImage#getSources source image}, if any.</li>
079: * <li>One node for each {@linkplain OperationNode#getParameterBlock image parameter}, if any.</li>
080: * </ul>
081: *
082: * Each source image can have its own source and parameters. In an analogy to a file system,
083: * {@linkplain RenderedImage#getSources source images} are like directories and
084: * {@linkplain OperationNode#getParameterBlock image parameters} are like files.
085: *
086: * When a tree node is selected in the left pane, the content of the right pane is adjusted
087: * accordingly. If the node is an image, a "preview" tab is show together with an "information"
088: * tab. Informations include the {@linkplain java.awt.image.ColorModel color model},
089: * {@linkplain java.awt.image.SampleModel sample model}, data type, etc. If the selected tree node
090: * is a parameter, then the right pane show the parameter value in {@linkplain ParameterEditor
091: * some widget} appropriate for the parameter type.
092: *
093: * <p> </p>
094: * <p align="center"><img src="doc-files/OperationTreeBrowser.png"></p>
095: * <p> </p>
096: *
097: * @since 2.3
098: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/extension/widgets-swing/src/main/java/org/geotools/gui/swing/image/OperationTreeBrowser.java $
099: * @version $Id: OperationTreeBrowser.java 26890 2007-09-07 11:05:40Z desruisseaux $
100: * @author Martin Desruisseaux
101: * @author Lionel Flahaut
102: *
103: * @see ImageProperties
104: * @see ParameterEditor
105: * @see RegisteredOperationBrowser
106: */
107: public class OperationTreeBrowser extends JPanel {
108: /** Key for {@link PropertySource}. */
109: private static final String IMAGE = "Image";
110: /** Key for parameter card. */
111: private static final String PARAMETER = "Parameter";
112:
113: /**
114: * The image properties panel. Will be constructed only when first needed,
115: * and the added to the card layout with the {@code IMAGE} name.
116: */
117: private ImageProperties imageProperties;
118:
119: /**
120: * The parameter properties panel. Will be constructed only when first needed,
121: * and the added to the card layout with the {@code PARAMETER} name.
122: */
123: private ParameterEditor parameterEditor;
124:
125: /**
126: * The properties panel. The content for this panel depends on
127: * the selected tree item, but usually includes the following:
128: * <ul>
129: * <li>An {@link ImageProperties} instance.</li>
130: * <li>An {@link ParameterEditor} instance.</li>
131: * </ul>
132: */
133: private final Container cards = new JPanel(new CardLayout());
134:
135: /**
136: * Constructs a new browser for the given rendered image.
137: *
138: * @param source The last image from the rendering chain to browse.
139: */
140: public OperationTreeBrowser(final RenderedImage source) {
141: this (getTree(source, getDefaultLocale()));
142: }
143:
144: /**
145: * Constructs a new browser for the given renderable image.
146: *
147: * @param source The last image from the rendering chain to browse.
148: */
149: public OperationTreeBrowser(final RenderableImage source) {
150: this (getTree(source, getDefaultLocale()));
151: }
152:
153: /**
154: * Constructs a new browser for the tree.
155: *
156: * @param model The tree model built from the rendering chain to browse.
157: */
158: private OperationTreeBrowser(final TreeModel model) {
159: super (new BorderLayout());
160: final Listeners listeners = new Listeners();
161: final JTree tree = new JTree(model);
162: tree.getSelectionModel().setSelectionMode(
163: TreeSelectionModel.SINGLE_TREE_SELECTION);
164: tree.setCellRenderer(new CellRenderer());
165: tree.setBorder(BorderFactory.createEmptyBorder(6, 6, 0, 0));
166: tree.addTreeSelectionListener(listeners);
167:
168: final JSplitPane split;
169: split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
170: new JScrollPane(tree), cards);
171: split.setDividerLocation(220);
172: add(split, BorderLayout.CENTER);
173:
174: setPreferredSize(new Dimension(600, 250));
175: }
176:
177: /**
178: * Show the operation tree for the specified rendered image in a frame.
179: * This convenience method is mostly a helper for debugging purpose.
180: */
181: public static void show(final RenderedImage image) {
182: new OperationTreeBrowser(image).showFrame(image);
183: }
184:
185: /**
186: * Show the operation tree for the specified renderable image in a frame.
187: * This convenience method is mostly a helper for debugging purpose.
188: */
189: public static void show(final RenderableImage image) {
190: new OperationTreeBrowser(image).showFrame(image);
191: }
192:
193: /**
194: * Returns a name for the given image. The default implementation returns the operation
195: * name if the image is an instance of {@link RenderedOp}. Otherwise, it returns the
196: * image class.
197: *
198: * @param image The image.
199: * @return A name for the given image.
200: */
201: private static String getName(final Object image) {
202: if (image instanceof OperationNode) {
203: return ((OperationNode) image).getOperationName();
204: }
205: if (image instanceof CharSequence) {
206: return image.toString();
207: }
208: return Utilities.getShortClassName(image);
209: }
210:
211: /**
212: * Prints to the {@linkplain System#out standard output stream} the operation chain for
213: * the specified image. This convenience method is used mostly for debugging purpose.
214: *
215: * @since 2.4
216: */
217: public static void print(final RenderedImage image) {
218: Trees.print(getTree(image, Locale.getDefault()));
219: }
220:
221: /**
222: * Prints to the {@linkplain System#out standard output stream} the operation chain for
223: * the specified image. This convenience method is used mostly for debugging purpose.
224: *
225: * @since 2.4
226: */
227: public static void print(final RenderableImage image) {
228: Trees.print(getTree(image, Locale.getDefault()));
229: }
230:
231: /**
232: * Returns a tree with all sources and parameters for the given rendered image.
233: *
234: * @param image The last image from an operation chain.
235: * @param locale The locale for tree node names.
236: * @return The tree for the given image and all its sources.
237: */
238: public static TreeModel getTree(final RenderedImage image,
239: final Locale locale) {
240: return new DefaultTreeModel(getNode(image, locale));
241: }
242:
243: /**
244: * Returns a tree with all sources and parameters for the given renderable image.
245: *
246: * @param image The last image from an operation chain.
247: * @param locale The locale for tree node names.
248: * @return The tree for the given image and all its sources.
249: */
250: public static TreeModel getTree(final RenderableImage image,
251: final Locale locale) {
252: return new DefaultTreeModel(getNode(image, locale));
253: }
254:
255: /**
256: * Returns the root node of a tree with all sources and parameters for the given source.
257: *
258: * @param image The last image from an operation chain.
259: * @param locale The locale for tree node names.
260: * @return The tree for the given image and all its sources.
261: */
262: private static MutableTreeNode getNode(final RenderedImage image,
263: final Locale locale) {
264: final DefaultMutableTreeNode root = new NamedTreeNode(
265: getName(image), image);
266: final List sources = image.getSources();
267: if (sources != null) {
268: final int n = sources.size();
269: for (int i = 0; i < n; i++) {
270: root
271: .add(getNode((RenderedImage) sources.get(i),
272: locale));
273: }
274: }
275: if (image instanceof OperationNode) {
276: addParameters(root, (OperationNode) image, locale);
277: }
278: return root;
279: }
280:
281: /**
282: * Returns the root node of a tree with all sources and parameters for the given source.
283: *
284: * @param image The last image from an operation chain.
285: * @param locale The locale for tree node names.
286: * @return The tree for the given image and all its sources.
287: */
288: private static MutableTreeNode getNode(final RenderableImage image,
289: final Locale locale) {
290: final DefaultMutableTreeNode root = new NamedTreeNode(
291: getName(image), image);
292: final List sources = image.getSources();
293: if (sources != null) {
294: final int n = sources.size();
295: for (int i = 0; i < n; i++) {
296: root.add(getNode((RenderableImage) sources.get(i),
297: locale));
298: }
299: }
300: if (image instanceof OperationNode) {
301: addParameters(root, (OperationNode) image, locale);
302: }
303: return root;
304: }
305:
306: /**
307: * Add the parameters from the specified operation to the specified tree node.
308: *
309: * @param root The tree node to add parameters to.
310: * @param operation The operation for which to fetch parameters.
311: * @param locale The locale for tree node names.
312: */
313: private static void addParameters(
314: final DefaultMutableTreeNode root,
315: final OperationNode operation, final Locale locale) {
316: final ParameterBlock param = operation.getParameterBlock();
317: final ParameterListDescriptor descriptor;
318: if (param instanceof ParameterList) {
319: descriptor = ((ParameterList) param)
320: .getParameterListDescriptor();
321: } else {
322: final String name = operation.getOperationName();
323: final String mode = operation.getRegistryModeName();
324: descriptor = operation.getRegistry().getDescriptor(mode,
325: name).getParameterListDescriptor(mode);
326: }
327: Vocabulary resources = null;
328: final String[] names = descriptor.getParamNames();
329: final int n = param.getNumParameters();
330: for (int i = 0; i < n; i++) {
331: String name = null;
332: if (names != null && i < names.length) {
333: name = names[i];
334: }
335: if (name == null) {
336: if (resources == null) {
337: resources = Vocabulary.getResources(locale);
338: }
339: name = resources.getString(VocabularyKeys.PARAMETER_$1,
340: new Integer(i));
341: }
342: root.add(new NamedTreeNode(name, param
343: .getObjectParameter(i), false));
344: }
345: }
346:
347: /**
348: * The listener for various event in the {@link OperationTreeBrowser} widget.
349: *
350: * @version $Id: OperationTreeBrowser.java 26890 2007-09-07 11:05:40Z desruisseaux $
351: * @author Martin Desruisseaux
352: */
353: private final class Listeners implements TreeSelectionListener {
354: /**
355: * Called whenever the value of the selection changes. This method uses the
356: * {@link TreeNode#getAllowsChildren} in order to determines if the selection
357: * is a source (allows children = {@code true}) or a parameter
358: * (allows children = {@code false}).
359: */
360: public void valueChanged(final TreeSelectionEvent event) {
361: Object selection = null; // The selected tree element.
362: boolean isSource = false; // Is 'selected' a source or a parameter?
363: OperationNode operation = null; // The parent of the selected element as an op.
364: int paramIndex = -1; // The index of the selected element.
365: final TreePath path = event.getPath();
366: if (path != null) {
367: selection = path.getLastPathComponent();
368: /*
369: * Some of piece of code in the following block can work with the Swing's
370: * TreeNode (i.e. it doesn't require the fixed Geotools's TreeNode).
371: */
372: if (selection instanceof javax.swing.tree.TreeNode) {
373: javax.swing.tree.TreeNode node = (javax.swing.tree.TreeNode) selection;
374: isSource = node.getAllowsChildren();
375: node = node.getParent();
376: if (node instanceof TreeNode) {
377: final Object candidate = ((TreeNode) node)
378: .getUserObject();
379: if (candidate instanceof OperationNode) {
380: operation = (OperationNode) candidate;
381: final int count = node.getChildCount();
382: for (int n = -1, i = 0; i < count; i++) {
383: final javax.swing.tree.TreeNode leaf = node
384: .getChildAt(i);
385: if (!leaf.getAllowsChildren()) {
386: n++; // Count only parameters, not sources.
387: }
388: if (leaf == selection) {
389: paramIndex = n;
390: break;
391: }
392: }
393: }
394: }
395: }
396: if (selection instanceof TreeNode) {
397: selection = ((TreeNode) selection).getUserObject();
398: }
399: }
400: if (isSource) {
401: showSourceEditor(selection);
402: } else {
403: showParameterEditor(selection);
404: }
405: if (parameterEditor != null) {
406: parameterEditor.setDescription(operation, paramIndex);
407: }
408: }
409: }
410:
411: /**
412: * Invoked when the user clicks on a source node in the operation tree (left pane).
413: * This method show a properties panel in the right pane appropriate for the given
414: * selection.
415: *
416: * @param selection The user selection. This object is usually an instance of
417: * {@link RenderedImage}, {@link RenderableImage} or {@link PropertySource}.
418: * @return {@code true} if this method has been able to find an editor, or
419: * {@code false} otherwise.
420: */
421: protected boolean showSourceEditor(final Object selection) {
422: if (imageProperties == null) {
423: imageProperties = new ImageProperties();
424: cards.add(imageProperties, IMAGE);
425: }
426: ((CardLayout) cards.getLayout()).show(cards, IMAGE);
427: if (selection instanceof RenderedImage) {
428: imageProperties.setImage((RenderedImage) selection);
429: return true;
430: }
431: if (selection instanceof RenderableImage) {
432: imageProperties.setImage((RenderableImage) selection);
433: return true;
434: }
435: if (selection instanceof PropertySource) {
436: imageProperties.setImage((PropertySource) selection);
437: return true;
438: }
439: imageProperties.setImage((PropertySource) null);
440: return false;
441: }
442:
443: /**
444: * Invoked when the user clicks on a parameter node in the operation tree (left pane).
445: * This method show a properties panel in the right pane appropriate for the given
446: * selection.
447: *
448: * @param selection The user selection. This object is usually an instance of
449: * {@link Number}, {@link KernelJAI}, {@link LookupTableJAI} or some other
450: * parameter object.
451: * @return {@code true} if this method has been able to find an editor, or
452: * {@code false} otherwise.
453: */
454: protected boolean showParameterEditor(final Object selection) {
455: if (parameterEditor == null) {
456: parameterEditor = new ParameterEditor();
457: cards.add(parameterEditor, PARAMETER);
458: }
459: ((CardLayout) cards.getLayout()).show(cards, PARAMETER);
460: parameterEditor.setParameterValue(selection);
461: return true;
462: }
463:
464: /**
465: * Show the operation chain in the given owner.
466: *
467: * @param owner The owner widget, or {@code null} if none.
468: * @param title The widget title, or {@code null} for a default one.
469: * @return {@code true} if the user clicked on the "Ok" button.
470: */
471: public boolean showDialog(final Component owner, String title) {
472: if (title == null) {
473: title = Vocabulary.getResources(getLocale()).getString(
474: VocabularyKeys.OPERATIONS);
475: }
476: if (SwingUtilities.showOptionDialog(owner, this , title)) {
477: // TODO: User clicked on "Ok".
478: return true;
479: }
480: return false;
481: }
482:
483: /**
484: * Implementation of public {@link #show} methods.
485: */
486: private void showFrame(final Object image) {
487: final JFrame frame = new JFrame(Utilities
488: .getShortClassName(this )
489: + " - " + getName(image));
490: frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
491: frame.getContentPane().add(this );
492: frame.pack();
493: frame.setVisible(true);
494: }
495:
496: /**
497: * Display the properties for the images specified on the command line.
498: *
499: * @throws IOException if an error occured while reading an image.
500: */
501: public static void main(String[] args) throws IOException {
502: final Arguments arguments = new Arguments(args);
503: args = arguments.getRemainingArguments(Integer.MAX_VALUE);
504: for (int i = 0; i < args.length; i++) {
505: final File file = new File(args[i]);
506: final RenderedImage image;
507: try {
508: image = ImageIO.read(file);
509: } catch (FileNotFoundException e) {
510: arguments.out.println(Errors.format(
511: ErrorKeys.FILE_DOES_NOT_EXIST_$1, file));
512: continue;
513: }
514: new OperationTreeBrowser(image).showFrame(file.getName());
515: }
516: }
517:
518: /**
519: * The tree cell renderer, which select icons according the selected object type.
520: *
521: * @version $Id: OperationTreeBrowser.java 26890 2007-09-07 11:05:40Z desruisseaux $
522: * @author Martin Desruisseaux
523: */
524: private static final class CellRenderer extends
525: DefaultTreeCellRenderer {
526: /** The icon for folder. */
527: private final Icon open, closed;
528:
529: /** The icon for images, or {@code null} if none. */
530: private static Icon image;
531:
532: /** The icon for parameters, or {@code null} if none. */
533: private static Icon parameter;
534:
535: /**
536: * Creates a cell renderer.
537: */
538: private CellRenderer() {
539: open = getDefaultOpenIcon();
540: closed = getDefaultClosedIcon();
541: if (image == null) {
542: final IconFactory icons = IconFactory.DEFAULT;
543: image = icons
544: .getIcon("toolbarButtonGraphics/general/Properties16.gif");
545: parameter = icons
546: .getIcon("toolbarButtonGraphics/general/Preferences16.gif");
547: }
548: }
549:
550: /**
551: * Configures the renderer based on the passed in components.
552: */
553: public Component getTreeCellRendererComponent(final JTree tree,
554: final Object value, final boolean selelected,
555: final boolean expanded, final boolean leaf,
556: final int row, final boolean hasFocus) {
557: if (((TreeNode) value).getUserObject() instanceof RenderedImage) {
558: if (image != null) {
559: setOpenIcon(image);
560: setClosedIcon(image);
561: setLeafIcon(image);
562: } else {
563: setLeafIcon(null);
564: }
565: } else if (parameter != null) {
566: setOpenIcon(open);
567: setClosedIcon(closed);
568: setLeafIcon(parameter);
569: }
570: return super.getTreeCellRendererComponent(tree, value,
571: selected, expanded, leaf, row, hasFocus);
572: }
573: }
574: }
|