// Copyright 2000-2005, FreeHEP package org.freehep.swing.graphics; import java.awt.AlphaComposite; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import javax.swing.JComponent; import javax.swing.JLayeredPane; import javax.swing.border.Border; import org.freehep.swing.layout.StackedLayout; /** * StackedPanel defines an extension to JLayeredPane which allows a * set of equally-sized, overlayed panels to form a single 2D surface * on which to draw. * * @author Charles Loomis * @version $Id: StackedPanel.java 8584 2006-08-10 23:06:37Z duns $ */ public class StackedPanel extends JLayeredPane implements java.io.Serializable, PropertyChangeListener { /** * This marks the current component which is being printed. */ private Component componentToPrint = null; /** * The graphics context to use for printing. */ Graphics printGraphics = null; /** * This flag indicates that the JInternalFrame which contains this * component wants painting inhibited. This is generally done * during a resizing operation. */ private boolean inhibitRepaint = false; /** * An error message indicating that the class passed into * addGraphicalSelectionPanelOfClass method was not a type of * GraphicalSelectionPanel. */ final public static String NEED_GRAPHICAL_SELECTION_PANEL = "Class must be a type of GraphicalSelectionPanel"; /** * The AbstractPanelArtist which provides the graphics content for * this panel. */ private AbstractPanelArtist panelArtist = null; /** * Create one update task for reuse. */ private UpdateTask updateTask; /** * The Timer used to schedule periodic updates of the display. */ private javax.swing.Timer timer; /** * Layer intended for interaction with the user. (e.g. zooming, * picking, etc.) Positioned just behind the palette layer. */ public static final Integer INTERACTION_LAYER = new Integer(PALETTE_LAYER.intValue()-1); /** * The lowest allowed value for components. */ public static final Integer MINIMUM_LAYER = DEFAULT_LAYER; /** * One larger than the maximum allowed layer for components. */ public static final Integer MAXIMUM_LAYER = INTERACTION_LAYER; /** * A LinkedList containing the layers defined by the user. */ private LinkedList panelList; /** * A hash table which maps the layer name to the component(s). */ private HashMap panelHash; /** * A flag to indicate whether redrawing is needed at the next * repaint. */ private boolean redrawNeeded; /** * This constructor makes a new stacked panel which has no * PanelArtist associated with it. */ public StackedPanel() { this(null); } /** * The constructor allocates all of the panels for drawing. */ public StackedPanel(AbstractPanelArtist panelArtist) { // Allocate space for the panel list. panelList = new LinkedList(); // Allocate space for the component HashMap. This looks up a // particular pair of components based on the layer name. panelHash = new HashMap(15); // Make an UpdateTask for reuse; set initial period to 300ms. updateTask = new UpdateTask(); // Make a Timer. timer = new javax.swing.Timer(300, updateTask); // This panel must be redrawn at the next repaint. setRedrawNeeded(true); // Set the layout manager for this panel to a StackedLayout. setLayout(new StackedLayout()); // Set the panel artist. Make sure that this is done after // all of the rest of the initialization. setPanelArtist(panelArtist); } /** * A routine which adds a new layer to this StackedPanel. By * default, the layer is added above all others. Each layer will * contain the given number of sublayers. If the number of * sublayers is larger than one and the layer should be opaque, * only the lowest sublayer will be opaque, all others will be * transparent. When retrieving the graphics context for the * sublayers, the topmost layer is listed first. If the number of * sublayers is non-positive, then an IllegalArgumentException * will be thrown. */ public void addLayer(String layerName, int sublayers, boolean opaque) { // Check that the number of sublayers is reasonable. if (sublayers<=0) throw new IllegalArgumentException(); // Create the backed panels. JComponent[] panels = new JComponent[sublayers]; for (int i=0; i<sublayers; i++) { panels[i] = new BackedPanel(opaque && (i==sublayers-1)); } addLayer(layerName, panels); } /** * adds a Panel as layer to this StackedPanel */ public void addLayer(String layerName, JComponent panel) { addLayer(layerName, new JComponent[] {panel}); } /** * adds a set of Panels as layer and sublayers to this StackedPanel */ public void addLayer(String layerName, JComponent[] panels) { // Create the panel array. PanelArray panelArray = new PanelArray(panels); // Check to see if this layer already exists. If so, // implicitly remove it from the component. if (panelList.contains(layerName)) removeLayer(layerName); // Add these panels into this component. for (int i=0; i<panels.length; i++) { add(panels[i]); } // Now add it to the list. panelList.add(layerName); panelHash.put(layerName, panelArray); // Reorder all of the layers. reorderLayers(); } /** * Remove a layer from the stacked panel. */ public void removeLayer(String layerName) { PanelArray panelArray = (PanelArray) panelHash.get(layerName); if (panelArray!=null) { Component[] comps = panelArray.getComponents(); for (int i=0; i<comps.length; i++) { remove(comps[i]); } // Remove the references in the panel list and panel // hash. panelArray.clear(); panelList.remove(layerName); panelHash.remove(layerName); // Finally, reorder the layers. reorderLayers(); } } /** * Reorder the layers. */ public void reorderLayers() { // Start at the minimum layer number. int layer = MINIMUM_LAYER.intValue(); // Loop over all of the layers. Iterator i = panelList.iterator(); while (i.hasNext()) { String key = (String) i.next(); PanelArray panelArray = (PanelArray) panelHash.get(key); if (panelArray!=null) { Component[] comps = panelArray.getComponents(); for (int j=comps.length-1; j>=0; j--) { setLayer(comps[j],layer++); } } } } /** * Add an interaction component. Initially the component is * inactive (invisible). */ public void addGraphicalSelectionPanel(GraphicalSelectionPanel panel) { panel.setVisible(false); add(panel, INTERACTION_LAYER); } /** * Remove an interaction component. */ public void removeGraphicalSelectionPanel(GraphicalSelectionPanel panel) { remove(panel); } /** * Make the GraphicalSelectionPanel of the given class active. */ public void activateGraphicalSelectionPanelOfClass(Class c) throws InstantiationException, IllegalAccessException { // Make sure that the class given is a type of // GraphicalSelectionPanel. if (!(GraphicalSelectionPanel.class).isAssignableFrom(c)) { throw new IllegalArgumentException(NEED_GRAPHICAL_SELECTION_PANEL); } // Get an array of interaction components. Component[] components = getComponentsInLayer(INTERACTION_LAYER.intValue()); // Loop over components and see if one if of the correct // class. boolean create = true; for (int i=0; i<components.length; i++) { boolean test = (c==components[i].getClass()); components[i].setVisible(test); if (test) create = false; } // If a component of the correct class was not found, then // make an instance of this class and make it visible. if (create) { GraphicalSelectionPanel newSelector = (GraphicalSelectionPanel) c.newInstance(); addGraphicalSelectionPanel(newSelector); newSelector.setVisible(true); // Make the panel artist a listener for this. if (panelArtist!=null) newSelector.addGraphicalSelectionListener(panelArtist); } } /** * Make a given GraphicalSelectionPanel active. */ // FIXME: this method does not do all stuff that the above method seem to do, such as handling listener subscription public void activateGraphicalSelectionPanel(GraphicalSelectionPanel panel) { // Get an array of interaction components. Component[] components = getComponentsInLayer(INTERACTION_LAYER.intValue()); // Loop over components and make the selected one visible. // Others should be make invisible. for (int i=0; i<components.length; i++) { components[i].setVisible(components[i]==panel); } } /** * Make all GraphicalSelectionPanels inactive. */ public void deactivateGraphicalSelectionPanels() { // Get an array of interaction components. Component[] components = getComponentsInLayer(INTERACTION_LAYER.intValue()); // Loop over components and make them all invisible. for (int i=0; i<components.length; i++) { components[i].setVisible(false); } } /** * Tell the component whether or not a given layer is visible. * * @param layerName String describing a particular layer * @param visible true if layer should be visible, false otherwise * */ public void setVisible(String layerName, boolean[] visible) { PanelArray panelArray = (PanelArray) panelHash.get(layerName); if (panelArray!=null) { Component[] comps = panelArray.getComponents(); // Check for mismatch in the size of the arrays. if (visible.length<comps.length) throw new IllegalArgumentException(); for (int i=0; i<comps.length; i++) { comps[i].setVisible(visible[i]); } } } /** * Set which PanelArtist will control the graphics content of this * panel. If the PanelArtist is not yet available, then null can * be passed in here. * * @param panelArtist PanelArtist which provides the graphics * content */ public void setPanelArtist(AbstractPanelArtist panelArtist) { // Get a list of the interaction components. Component[] components = getComponentsInLayer(INTERACTION_LAYER.intValue()); // Add the panel artist as the receiver of any graphical // selections. Remove the current panelArtist and add the new // one as Graphical Selection Listeners. for (int i=0; i<components.length; i++) { GraphicalSelectionPanel selector = (GraphicalSelectionPanel) components[i]; if (this.panelArtist!=null) selector.removeGraphicalSelectionListener(this.panelArtist); if (panelArtist!=null) selector.addGraphicalSelectionListener(panelArtist); } // First check to see if a PanelArtist is already being used. // If so, remove it. Note that the PanelArtist is responsible // for stopping any drawing which is taking place on this // component. if (this.panelArtist!=null) this.panelArtist.setStackedPanel(null); // Save which PanelArtist to use. Tell the panel artist that // it is controlling this stacked panel. this.panelArtist = panelArtist; if (panelArtist!=null) { panelArtist.setStackedPanel(this); if (panelArtist instanceof MouseListener) { addMouseListener((MouseListener) panelArtist); } } } /** * Return the PanelArtist which controls the graphics content of * this panel. Note this can return null if no PanelArtist has * been set. * * @return the PanelArtist which control the graphics content */ public AbstractPanelArtist getPanelArtist() { return panelArtist; } /** * Return a boolean indicating whether or not this panel will be * completely redrawn at the next repaint. */ public boolean isRedrawNeeded() { return redrawNeeded; } /** * Set the redraw flag. If set to true, the entire panel will be * redrawn at the next repaint. (The paintComponent method will * call drawPanel() rather than flushing the saved image to the * screen. */ public void setRedrawNeeded(boolean redrawNeeded) { this.redrawNeeded = redrawNeeded; } /** * This provides an additional getGraphics method which returns * the graphics object associated with the image of the given * layer. This will return the graphics object associated with a * given layer even if that layer is hidden. */ public void getGraphics(String layerName, Graphics[] g) { // Get the associated components. PanelArray panelArray = (PanelArray) panelHash.get(layerName); if (panelArray!=null) { Component[] comps = panelArray.getComponents(); if (g.length<comps.length) throw new IllegalArgumentException(); for (int i=0; i<comps.length; i++) { if (componentToPrint!=null && comps[i]==componentToPrint) { g[i] = (Graphics) printGraphics; } else if (componentToPrint!=null) { g[i] = null; } else if (comps[i]!=null) { g[i] = comps[i].getGraphics(); } } } } /** * This provides a method for PanelArtists to clear all layers. */ public void clearAllLayers() { Iterator iterator = panelHash.keySet().iterator(); while (iterator.hasNext()) { clearLayer((String)iterator.next()); } } /** * This provides a method for PanelArtists to clear a layer. This * conditionally clears a particular layer depending on whether or * not the images are being printed. */ public void clearLayer(String layerName) { if (componentToPrint==null) { Graphics2D[] g = new Graphics2D[2]; getGraphics(layerName,g); Insets insets = getInsets(); int w = getWidth()-(insets.left+insets.right); int h = getHeight()-(insets.top+insets.bottom); for (int i=0; i<2; i++) { Graphics2D g2d = g[i]; if (g2d!=null) { g2d.setComposite(AlphaComposite.Clear); // long t0 = System.currentTimeMillis(); g2d.fillRect(0,0,w,h); g2d.setComposite(AlphaComposite.Src); // long t1 = System.currentTimeMillis(); // System.out.println("SubClear "+layerName+" took: "+(t1-t0)+" ms."); } } } } /** * This sets the anti-aliasing on or off for the given layer. */ public void setAntialias(String layerName, boolean antialias) { if (componentToPrint==null) { Graphics[] g = new Graphics[2]; getGraphics(layerName,g); for (int i=0; i<2; i++) { Graphics2D g2d = (Graphics2D) g[i]; if (g2d!=null) { if (antialias) { g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } else { g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); } } } } } public void paint(Graphics g) { super.paint(g); // try { Thread.sleep(5000); } catch (InterruptedException ie) {} System.out.println("Done"); } public void paintChildren(Graphics g) { System.out.println("Children..."); long t0 = System.currentTimeMillis(); super.paintChildren(g); long t1 = System.currentTimeMillis(); System.out.println("Children took: "+(t1-t0)+" ms."); } /** * Override of the paintComponent() method ensures that the * drawPanel() method will be called if a complete redraw is * needed. */ public void paintComponent(Graphics g) { // Always call the parent's method. super.paintComponent(g); // Check if a redraw needs to be done from scratch. if (redrawNeeded && !inhibitRepaint) { setRedrawNeeded(false); stopPeriodicUpdate(); // If drawPanel() returns false, then the drawing will // occur in another thread and is not yet complete. // Periodically update the panel in this case. if (panelArtist!=null && !panelArtist.drawPanel()) startPeriodicUpdate(); } } /** * This prints this StackedPanel. */ public void printComponent(Graphics g) { if (panelArtist!=null) { // Make a sub-context which is correctly shifted to // account for the borders of this component. This is // done automatically for the various component layers, // but we must do it by hand for the printing. Insets insets = getInsets(); int w = getWidth(); int h = getHeight(); printGraphics = g.create(insets.left,insets.top, w-(insets.left+insets.right), h-(insets.top+insets.bottom)); // Get the list of components and sort according to the // ordering. We must do this because the order is not // guaranteed to be correct from getComponents(). Component[] clist = getComponents(); ComponentIndex[] indexed = new ComponentIndex[clist.length]; for (int i=0; i<clist.length; i++) { Component c = clist[i]; indexed[i] = new ComponentIndex(c,getLayer(c),getPosition(c)); } Arrays.sort(indexed); // Do the printing. for (int i=0; i<indexed.length; i++) { componentToPrint = indexed[i].getComponent(); if (componentToPrint.isVisible()) { if (componentToPrint instanceof BackedPanel) { panelArtist.drawPanel(); } else if (componentToPrint instanceof JComponent) { componentToPrint.print(g); } else { componentToPrint.paintAll(g); } } } // Clean-up after the printing. componentToPrint = null; printGraphics.dispose(); printGraphics = null; } } /** * This callback routine is intended to be used by the controlling * PanelArtist after it has returned false from the drawPanel() * method, indicating that the redraw of the panel has not yet * completed. This signals that the redraw is now complete. */ public void drawComplete() { stopPeriodicUpdate(); updateTask.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_LAST, "timer.stop")); } /** * Set the delay for the periodic update of the display. */ public void setUpdatePeriod(int period) { timer.setDelay(Math.max(0,period)); } /** * Get the delay for the periodic update of the display. */ public long getUpdatePeriod() { return timer.getDelay(); } /** * Start a periodic update of this panel. This is called * internally when the controlling PanelArtist is drawing into the * images from another, background thread. */ protected void startPeriodicUpdate() { timer.restart(); } /** * Stop the periodic update of this panel. */ protected void stopPeriodicUpdate() { timer.stop(); } /** * Set the border of this component. This is intercepted so that * if the size of the border changes, the redraw flag can be * set. */ public void setBorder(Border border) { // Get the old and the new insets. Insets insets = getInsets(); Insets borderInsets = null; if (border!=null) { borderInsets = border.getBorderInsets(this); } else { borderInsets = new Insets(0,0,0,0); } // Check to see if they have changed. if (insets.left!=borderInsets.left || insets.top!=borderInsets.top || insets.right!=borderInsets.right || insets.bottom!=borderInsets.bottom) setRedrawNeeded(true); // Now let my parent do it's processing. super.setBorder(border); } /** * Intercept a resize event so that the redraw flag can be * set. (Swing internally calls reshape() which is then forwarded * to setBounds().) */ public void setBounds(int x, int y, int w, int h) { // Do not forward messages about the resize if it is currently // in progress. Wait for the inhibit to be withdrawn. if (!inhibitRepaint) { super.setBounds(x,y,w,h); // Notify the panel artist that the size has changed. if (panelArtist!=null) panelArtist.panelResized(); } } /** * This inner class is the task which is executed to periodically * update the display. */ class UpdateTask implements ActionListener { public void actionPerformed(ActionEvent event) { repaint(); } } /** * Look for changes in property values. If a property is set on a * component which has a key "InhibitRepaint" and a value of true, * repainting of the component will be inhibited. */ public void propertyChange(PropertyChangeEvent evt) { if (evt.getPropertyName().equals("InhibitRepaint")) { if (evt.getNewValue()==null) { // Turn off the inhibit. inhibitRepaint = false; // Force this component to be resized if necessary. invalidate(); validate(); // Finally, repaint if necessary. repaint(); } else { inhibitRepaint = true; } } } /** * A protected class which simply wraps the set of Panels * which form the sublayers of a particular layer. */ protected class PanelArray { private JComponent[] panels; public PanelArray(JComponent[] panels) { this.panels = panels; } public Component[] getComponents() { return (Component[]) panels; } public JComponent getComponent(int index) { return panels[index]; } public void clear() { for (int i=0; i<panels.length; i++) { panels[i] = null; } panels = null; } } /** * This private class takes a component, its layer, and its * position. It is used to sort the components into the display * order (lowest layer, highest position). */ private class ComponentIndex implements Comparable { private Component component; private int layer; private int position; public ComponentIndex(Component component, int layer, int position) { this.component = component; this.layer = layer; this.position = position; } public Component getComponent() { return component; } public int hashcode() { return (layer ^ position); } public boolean equals(Object o) { // Cast this to the correct type. This will throw a class // cast exception if this isn't possible. ComponentIndex other = (ComponentIndex) o; return (layer==other.layer && position==other.position); } public int compareTo(Object o) { // Cast this to the correct type. This will throw a class // cast exception if this isn't possible. ComponentIndex other = (ComponentIndex) o; // Now do the ordering. if (layer<other.layer) { return -1; } else if (layer>other.layer) { return 1; } else { if (position>other.position) { return -1; } else if (position<other.position) { return 1; } } return 0; } } }