/* Copyright (C) 2007 Christian Schneider * * This file is part of JTheme. * * Nomad is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Nomad 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Nomad; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ /* * Created on Jan 20, 2007 */ package net.sf.nmedit.jtheme.component; import java.awt.AlphaComposite; import java.awt.Component; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Insets; import java.awt.Transparency; import java.awt.image.BufferedImage; import javax.swing.JComponent; import javax.swing.border.Border; import javax.swing.plaf.ComponentUI; import net.sf.nmedit.jtheme.JTContext; import net.sf.nmedit.jtheme.component.plaf.JTComponentUI; /** * A specialized version of JTBaseComponent. This class is * intended for the module component and it's child components. * * This class uses several optimizations for better performance: * <ul> * <li>PropertyChangeListeners are not supported. Related * methods are not implemented.</li> * <li>Layout manager support is removed. Related methods * are not implemented: ({@link #doLayout()}, * {@link #invalidate()}, {@link #revalidate()}, * {@link #validate()}, {@link #validateTree()})</li> * <li>Uses optionally a non volatile double buffer * (enabled by default). </li> * <li>{@link #paintComponent(Graphics)} paints the * two layers of this component. The static layer contains * the rendered parts that do not change at runtime * and the dynamic layer containing the rendered parts * which can change at runtime. <br/> * This makes it possible to replace the rendering * method of the static layer with a image. This * can be done using {@link #setStaticLayerBackingStore(Image)}. * </li> * <li>If {@link #isReducible()} is true and the static layer * backing store is available the component does not have to * be created since it already appears in the image. * </li> * </ul> * * @see net.sf.nmedit.jtheme.component.plaf.JTComponentUI * @author Christian Schneider */ public class JTComponent extends JTBaseComponent { private static final long serialVersionUID = 3945939382230355343L; // the static layer backing store image //private Image staticLayerBackingStore; // the non-volatile double buffer //private transient DoubleBuffer doubleBuffer; /** * Creates a new component. */ public JTComponent(JTContext context) { super(context); setOpaque(true); } /** * Returns the component's ui delegate. */ public JTComponentUI getUI() { return (JTComponentUI)ui; } /** * Sets the look and feel delegate for this component. * * @throws ClassCastException if the specified argument is not * instanceof {@link JTComponentUI} * @see JComponent#setUI(javax.swing.plaf.ComponentUI) */ public void setUI(ComponentUI ui) { setUI((JTComponentUI) ui); } protected void setUI(JTComponentUI ui) { super.setUI(ui); } /** * Sets the static layer backing store image. * If the image is not null, then it is used instead of the * custom {@link #paintStaticLayer(Graphics2D)} implementation * to paint the static layer. * * If the specified argument is not null, the opaque value * of this component is set to true. * * @param staticLayerBackingStore the static layer replacement */ /*public void setStaticLayerBackingStore(Image staticLayerBackingStore) { this.staticLayerBackingStore = staticLayerBackingStore; setOpaque(isOpaque()); }*/ /** * Returns the static layer backing store image. * @return the static layer backing store image */ /*public final Image getStaticLayerBackingStore() { return staticLayerBackingStore; }*/ /** * Returns true if the static layer backing store image is set. * @return true if the static layer backing store image is set */ /*public final boolean hasStaticLayerBackingStore() { return staticLayerBackingStore != null; }*/ /** * Returns the x-offset of this component * relative to the origin of the backing store image. */ protected int getStaticLayerBackingStoreOffsetX() { return getX(); } /** * Returns the y-offset of this component * relative to the origin of the backing store image. */ protected int getStaticLayerBackingStoreOffsetY() { return getY(); } /** * Sets the opacity of this component. If the static layer backing * store image is set, then this will set the opaque value to true. */ public void setOpaque(boolean isOpaque) { super.setOpaque(opacityOverwrite(isOpaque)); } protected boolean opacityOverwrite(boolean isOpaque) { return isOpaque /*|| staticLayerBackingStore!=null*/; } /** * Returns true if the component does not paint the dynamic layer * and if it can be removed at runtime. * * @return returns false. */ public boolean isReducible() { return false; } /** * Returns true if this component uses a non-volatile double buffer * to store it's appearance. * * If enabled repainting with a valid double buffer is much faster * but it also needs more memory. * * Only subclasses that have complex painting code should return * true, others should overwrite this method and return false. * * Components that are {@link #isReducible() reducible} should * return false. * * In an environment where a parent container paints * components above JTComponent components should return * true, otherwise false. */ protected boolean isNonVolatileDoubleBufferEnabled() { return (!isReducible()) && getContext().hasModuleContainerOverlay(); } /** * If the double buffer is used it sets the flag * indicating that the double buffer has to be updated. */ /*private final void setDoubleBufferNeedsUpdateFlag() { if (doubleBuffer != null) doubleBuffer.needsUpdate = true; }*/ /** * Sets the double buffer needs update flag. * @see Component#repaint() */ /*public void repaint() { // double buffer must be updated // we have to set the flag here because we can't // be sure that repaint(long,int,int,int,int) // is called by super.repaint() setDoubleBufferNeedsUpdateFlag(); super.repaint(0,0,0,getWidth(), getHeight()); }*/ /** * Sets the double buffer needs update flag. * @see Component#repaint(long) */ /*public void repaint(long tm) { // double buffer must be updated // we have to set the flag here because we can't // be sure that repaint(long,int,int,int,int) // is called by super.repaint(long) setDoubleBufferNeedsUpdateFlag(); super.repaint(tm,0,0,getWidth(),getHeight()); }*/ /** * Sets the double buffer needs update flag. * @see Component#repaint(int, int, int, int) */ /*public void repaint(int x, int y, int width, int height) { // double buffer must be updated // we have to set the flag here because we can't // be sure that repaint(long,int,int,int,int) // is called by super.repaint(int,int,int,int) setDoubleBufferNeedsUpdateFlag(); super.repaint(0, x, y, width, height); }*/ /** * Sets the double buffer needs update flag. * @see JComponent#repaint(java.awt.Rectangle) */ /*public void repaint(Rectangle r) { // double buffer must be updated // we have to set the flag here because we can't // be sure that repaint(long,int,int,int,int) // is called by super.repaint(Rectangle) setDoubleBufferNeedsUpdateFlag(); super.repaint(0,r.x,r.y,r.width,r.height); }*/ /** * Sets the double buffer needs update flag. * @see Component#repaint(long, int, int, int, int) */ /*public void repaint(long tm, int x, int y, int width, int height) { // double buffer must be updated setDoubleBufferNeedsUpdateFlag(); super.repaint(tm, x, y, width, height); }*/ /** * Calls paint. * @see JComponent#update(java.awt.Graphics) */ public void update(Graphics g) { // call paint directly paint(g); } /** * Paints the component. * @see JComponent#paint(java.awt.Graphics) */ public void paint(Graphics g) { // we do not change anything here super.paint(g); } /** * Paints the component's children. * @see JComponent#paintChildren(java.awt.Graphics) */ protected void paintChildren(Graphics g) { // no change super.paintChildren(g); } /** * Paints the component's border. If * {@link #hasStaticLayerBackingStore()} is true * the border is not painted because the component * assumes, that the static layer backing store image * already contains the painted border. * * Overriding this method is not possible, instead * overwrite {@link #renderBorder(Graphics)}. * * @see #renderBorder(Graphics) * @see JComponent#paintBorder(java.awt.Graphics) */ protected void paintBorder(Graphics g) { // paint(java.awt.Graphics) calls // paintComponent(java.awt.Graphics) // then paintBorder(java.awt.Graphics) // If the static layer image is available it // contains the border and we do not have to // paint it again. /* if (hasStaticLayerBackingStore()) return; */ renderBorder(g); } /** * Paints the component's border. * @see #paintBorder(Graphics) */ protected void renderBorder(Graphics g) { super.paintBorder(g); } /** * Paints the static and dynamic layers of this component. * @see JComponent#paintComponent(java.awt.Graphics) */ protected void paintComponent(Graphics g) { paintComponentWithoutDoubleBuffer((Graphics2D)g); /* if (doubleBuffer == null && isNonVolatileDoubleBufferEnabled() && isDisplayable()) doubleBuffer = new DoubleBuffer(); else { paintComponentWithoutDoubleBuffer((Graphics2D)g); return; } doubleBuffer.prepareNonVolatile(this); if (doubleBuffer.needsUpdate) { Graphics2D dbGraphics = doubleBuffer.createOffscreenGraphics(); try { dbGraphics.setFont(g.getFont()); dbGraphics.setColor(g.getColor()); doubleBuffer.needsUpdate = false; paintComponentWithoutDoubleBuffer(dbGraphics); } finally { dbGraphics.dispose(); } } doubleBuffer.flip(g); */ } // double buffer private transient DoubleBuffer db; protected void setDoubleBufferNeedsUpdate() { if (db != null) db.needsUpdate = true; } protected void paintComponentWithDoubleBuffer(Graphics g) { if (db == null) db = new DoubleBuffer(false); db.prepareNonVolatile(this); if (db.needsUpdate) { Graphics2D bufferG2 = db.createOffscreenGraphics(); try { bufferG2.setFont(getFont()); bufferG2.setColor(getBackground()); paintComponentWithoutDoubleBuffer(bufferG2); } finally { bufferG2.dispose(); } db.needsUpdate = false; } db.flip(g); } /** * Paints the static and dynamic layers of this component without * double buffering. */ protected final void paintComponentWithoutDoubleBuffer(Graphics2D g2) { // paintStaticLayer paintStaticLayerOrBackingStore(g2); // paintDynamicLayer paintDynamicLayer(g2); } /** * Paints the static layer of this component. Uses * the static layer backing store image instead if available. */ protected void paintStaticLayerOrBackingStore(Graphics2D g2) {/* if (hasStaticLayerBackingStore()) { int r = getWidth(); int b = getHeight(); int ox = getStaticLayerBackingStoreOffsetX(); int oy = getStaticLayerBackingStoreOffsetY(); g2.drawImage( // image staticLayerBackingStore, // destination 0, 0, r, b, // source ox, oy, ox+r, oy+b, // ImageObserver null); } else*/ { paintStaticLayer(g2); } } /** * Calls the UI delegate's * {@link JTComponentUI#paintStaticLayer(Graphics2D, JComponent)} method. */ protected void paintStaticLayer(Graphics2D g2) { if (ui != null) { Graphics2D scratchGraphics = g2 == null ? null : (Graphics2D) g2.create(); try { ((JTComponentUI)ui).paintStaticLayer(scratchGraphics, this); } finally { if (scratchGraphics != null) scratchGraphics.dispose(); } } } /** * Calls the UI delegate's * {@link JTComponentUI#paintDynamicLayer(Graphics2D, JComponent)} method. */ protected void paintDynamicLayer(Graphics2D g2) { if (ui != null) { Graphics2D scratchGraphics = g2 == null ? null : (Graphics2D) g2.create(); try { ((JTComponentUI)ui).paintDynamicLayer(scratchGraphics, this); } finally { if (scratchGraphics != null) scratchGraphics.dispose(); } } } /** * Renderes the static layer of this component and of each * child that is subclass of JTComponent using the specified graphics. */ protected void renderStaticLayerImage(Graphics2D g) { g.setFont(getFont()); g.setColor(getForeground()); paintStaticLayer(g); renderBorder(g); for (int i=getComponentCount()-1;i>=0;i--) { Component c = getComponent(i); if (c instanceof JTComponent) { Graphics2D gchild = (Graphics2D) g.create(c.getX(), c.getY(), c.getWidth(), c.getHeight()); try { gchild.setFont(c.getFont()); gchild.setColor(c.getForeground()); ((JTComponent) c).paintStaticLayer(gchild); ((JTComponent) c).renderBorder(gchild); } finally { gchild.dispose(); } } } } /** * The non volatile double buffer. */ private static class DoubleBuffer { private GraphicsConfiguration gc; private BufferedImage image; boolean needsUpdate; int w; int h; private boolean containsBorder; private Insets insets = new Insets(0,0,0,0); public DoubleBuffer(boolean containsBorder) { this.containsBorder = containsBorder; } public void prepareNonVolatile(JTComponent c) { int oldw = w; int oldh = h; w = c.getWidth(); h = c.getHeight(); if (w<1) w=1; if (h<1) h=1; if (image == null || oldw!=w || oldh!=h) { flush(); image = createNonVolatileImage(c, w, h); } else if (needsUpdate && (containsBorder && !c.isOpaque())) { erase(image, w, h); } } public Graphics2D createOffscreenGraphics() { return image.createGraphics(); } private void erase(Image img, int w, int h) { Graphics2D g2 = (Graphics2D) img.getGraphics(); try { g2.setComposite(AlphaComposite.Clear); g2.fillRect(0, 0, w, h); } finally { g2.dispose(); } } private BufferedImage createNonVolatileImage( JTComponent c, int w, int h ) { if (gc == null) { gc = GraphicsEnvironment .getLocalGraphicsEnvironment() .getDefaultScreenDevice() .getDefaultConfiguration(); } insets = c.getInsets(insets); boolean opaque = c.isOpaque(); if (containsBorder) { Border b = c.getBorder(); if (b != null) opaque &= b.isBorderOpaque(); } // Create a non-volatile image that can be optimally blitted. // The image is translucent depending on the components opacity setting. return gc.createCompatibleImage(w, h, opaque ? Transparency.OPAQUE : Transparency.TRANSLUCENT); } public void flush() { needsUpdate = true; if (image != null) { image.flush(); image = null; } } public void flip( Graphics g ) { int t = insets.top; int b = insets.bottom; int l = insets.left; int r = insets.right; int dx2 = Math.max(0, w-r-l); int dy2 = Math.max(0, h-b-t); g.drawImage(image, l, t, l+dx2, t+dy2, l, t, l+dx2, t+dy2, null); } } }