/* * (c) Copyright 2010-2011 AgileBirds * * This file is part of OpenFlexo. * * OpenFlexo 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 3 of the License, or * (at your option) any later version. * * OpenFlexo 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 OpenFlexo. If not, see <http://www.gnu.org/licenses/>. * */ package org.openflexo.fge.view; import java.awt.Component; import java.awt.Container; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsEnvironment; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Transparency; import java.awt.image.BufferedImage; import java.util.HashSet; import java.util.Vector; import java.util.WeakHashMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.JComponent; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; import org.openflexo.fge.FGEConstants; import org.openflexo.fge.GraphicalRepresentation; import org.openflexo.fge.ShapeGraphicalRepresentation; import org.openflexo.fge.controller.DrawingController; public class FGEPaintManager { private static final boolean ENABLE_CACHE_BY_DEFAULT = true; private static final Logger logger = Logger.getLogger(FGEPaintManager.class.getPackage().getName()); protected static final Logger paintPrimitiveLogger = Logger.getLogger("PaintPrimitive"); protected static final Logger paintRequestLogger = Logger.getLogger("PaintRequest"); protected static final Logger paintStatsLogger = Logger.getLogger("PaintStats"); private boolean _paintingCacheEnabled; private static final int DEFAULT_IMAGE_TYPE = BufferedImage.TYPE_INT_RGB; private static FGERepaintManager repaintManager; static { initFGERepaintManager(); /* Debug purposes paintPrimitiveLogger.setLevel(Level.FINE); paintRequestLogger.setLevel(Level.FINE); paintStatsLogger.setLevel(Level.FINE); */ } private DrawingView<?> _drawingView; private BufferedImage _paintBuffer; private HashSet<GraphicalRepresentation<?>> _temporaryObjects; public FGEPaintManager(DrawingView<?> drawingView) { super(); _drawingView = drawingView; _paintBuffer = null; _temporaryObjects = new HashSet<GraphicalRepresentation<?>>(); if (ENABLE_CACHE_BY_DEFAULT) { enablePaintingCache(); } else { disablePaintingCache(); } } public DrawingView<?> getDrawingView() { return _drawingView; } public DrawingController<?> getDrawingController() { return _drawingView.getController(); } public boolean isPaintingCacheEnabled() { return _paintingCacheEnabled; } public void enablePaintingCache() { if (logger.isLoggable(Level.FINE)) { logger.fine("Painting cache: ENABLED"); } _paintingCacheEnabled = true; } public void disablePaintingCache() { if (logger.isLoggable(Level.FINE)) { logger.fine("Painting cache: DISABLED"); } _paintingCacheEnabled = false; } public HashSet<GraphicalRepresentation<?>> getTemporaryObjects() { return _temporaryObjects; } public boolean containsTemporaryObject(GraphicalRepresentation<?> gr) { if (gr == null) { return false; } if (isTemporaryObject(gr)) { return true; } if (gr.getContainedGraphicalRepresentations() == null) { return false; } for (GraphicalRepresentation<?> child : gr.getContainedGraphicalRepresentations()) { if (containsTemporaryObject(child)) { return true; } } return false; } public boolean isTemporaryObject(GraphicalRepresentation<?> gr) { return _temporaryObjects.contains(gr); } public boolean isTemporaryObjectOrParentIsTemporaryObject(GraphicalRepresentation<?> gr) { if (isTemporaryObject(gr)) { return true; } if (gr.getContainerGraphicalRepresentation() != null) { return isTemporaryObjectOrParentIsTemporaryObject(gr.getContainerGraphicalRepresentation()); } return false; } public void addToTemporaryObjects(GraphicalRepresentation<?> gr) { if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("addToTemporaryObjects() " + gr); } if (!_temporaryObjects.contains(gr)) { _temporaryObjects.add(gr); } } public void removeFromTemporaryObjects(GraphicalRepresentation<?> gr) { _temporaryObjects.remove(gr); } // CPU-expensive because it will ask to recreate the whole buffer public void invalidate(GraphicalRepresentation<?> object) { if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("CALLED invalidate on FGEPaintManager"); } _paintBuffer = null; // repaintManager.clearTemporaryRepaintArea(); } public void clearPaintBuffer() { if (paintRequestLogger.isLoggable(Level.INFO)) { paintRequestLogger.info("CALLED clear paint buffer on FGEPaintManager"); } _paintBuffer = null; } public void repaint(FGEView<?> view, Rectangle bounds) { if (!_drawingView.contains(view)) { return; } if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("Called REPAINT for view " + view + " for " + bounds); } ((JComponent) view).repaint(bounds.x, bounds.y, bounds.width, bounds.height); // repaintManager.repaintTemporaryRepaintAreas((JComponent)view); repaintManager.repaintTemporaryRepaintAreas(_drawingView); } public void addTemporaryRepaintArea(Rectangle r, JComponent view) { repaintManager.addTemporaryRepaintArea(r, view); } public void repaint(final FGEView view) { if (view.isDeleted()) { return; } if (!SwingUtilities.isEventDispatchThread()) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { repaint(view); } }); return; } if (!_drawingView.contains(view)) { return; } if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("Called REPAINT for view " + view); } if (view == _drawingView) { // clearTemporaryRepaintArea(); // paintRequestLogger.warning("Called repaint on whole DrawingView. Is it really necessary ?"); } repaintManager.repaintTemporaryRepaintAreas(_drawingView); ((JComponent) view).repaint(); if (view.getGraphicalRepresentation().hasFloatingLabel()) { LabelView<?> label = view.getLabelView(); if (label != null) { label.repaint(); } } // repaintManager.repaintTemporaryRepaintAreas((JComponent)view); if (view instanceof ShapeView /*&& isPaintingCacheEnabled()*/) { Container parent = ((Component) view).getParent(); if (parent == null) { return; } // What may happen here ? // Control points displayed focus or selection might changed, and to be refresh correctely // we must assume that a request to an extended area embedding those control points // must be performed (in case of border is not sufficient) ShapeGraphicalRepresentation<?> gr = ((ShapeView<?>) view).getGraphicalRepresentation(); int requiredControlPointSpace = FGEConstants.CONTROL_POINT_SIZE; if (gr.getBorder().top * view.getScale() < requiredControlPointSpace) { Rectangle repaintAlsoThis = new Rectangle(-requiredControlPointSpace, -requiredControlPointSpace, ((Component) view).getWidth() + requiredControlPointSpace * 2, requiredControlPointSpace * 2); repaintAlsoThis = SwingUtilities.convertRectangle((Component) view, repaintAlsoThis, parent); parent.repaint(repaintAlsoThis.x, repaintAlsoThis.y, repaintAlsoThis.width, repaintAlsoThis.height); // System.out.println("Repaint "+repaintAlsoThis+" for "+((Component)view).getParent()); } if (gr.getBorder().bottom * view.getScale() < requiredControlPointSpace) { Rectangle repaintAlsoThis = new Rectangle(-requiredControlPointSpace, ((Component) view).getHeight() - requiredControlPointSpace, ((Component) view).getWidth() + requiredControlPointSpace * 2, requiredControlPointSpace * 2); repaintAlsoThis = SwingUtilities.convertRectangle((Component) view, repaintAlsoThis, parent); parent.repaint(repaintAlsoThis.x, repaintAlsoThis.y, repaintAlsoThis.width, repaintAlsoThis.height); // System.out.println("Repaint "+repaintAlsoThis+" for "+((Component)view).getParent()); } if (gr.getBorder().left * view.getScale() < requiredControlPointSpace) { Rectangle repaintAlsoThis = new Rectangle(-requiredControlPointSpace, -requiredControlPointSpace, requiredControlPointSpace * 2, ((Component) view).getHeight() + requiredControlPointSpace * 2); repaintAlsoThis = SwingUtilities.convertRectangle((Component) view, repaintAlsoThis, parent); parent.repaint(repaintAlsoThis.x, repaintAlsoThis.y, repaintAlsoThis.width, repaintAlsoThis.height); // System.out.println("Repaint "+repaintAlsoThis+" for "+((Component)view).getParent()); } if (gr.getBorder().right * view.getScale() < requiredControlPointSpace) { Rectangle repaintAlsoThis = new Rectangle(((Component) view).getWidth() - requiredControlPointSpace, -requiredControlPointSpace, requiredControlPointSpace * 2, ((Component) view).getHeight() + requiredControlPointSpace * 2); repaintAlsoThis = SwingUtilities.convertRectangle((Component) view, repaintAlsoThis, parent); parent.repaint(repaintAlsoThis.x, repaintAlsoThis.y, repaintAlsoThis.width, repaintAlsoThis.height); // System.out.println("Repaint "+repaintAlsoThis+" for "+((Component)view).getParent()); } } } public void repaint(GraphicalRepresentation<?> gr) { if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("Called REPAINT for graphical representation " + gr); } FGEView<?> view = _drawingView.viewForObject(gr); if (view != null) { repaint(view); } } public BufferedImage getScreenshot(GraphicalRepresentation<?> gr) { /*Component view = getDrawingView(); BufferedImage bufferedImage = new BufferedImage(view.getWidth(), view.getHeight(), DEFAULT_IMAGE_TYPE); Graphics2D g = bufferedImage.createGraphics(); view.print(g); return bufferedImage;*/ FGEView<?> v = getDrawingView().viewForObject(gr); Rectangle rect = new Rectangle(((JComponent) v).getX(), ((JComponent) v).getY(), ((JComponent) v).getWidth(), ((JComponent) v).getHeight()); if (v instanceof ShapeView) { if (v.getLabelView() != null) { rect = rect.union(v.getLabelView().getBounds()); } } return getPaintBuffer().getSubimage(rect.x, rect.y, rect.width, rect.height); } private synchronized BufferedImage bufferDrawingView() { if (paintRequestLogger.isLoggable(Level.FINE)) { paintRequestLogger.fine("Buffering whole DrawingView. Is it really necessary ?"); } Component view = getDrawingView(); GraphicsConfiguration gc = view.getGraphicsConfiguration(); if (gc == null) { gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); } BufferedImage image = gc.createCompatibleImage(view.getWidth(), view.getHeight(), Transparency.TRANSLUCENT); Graphics2D g = image.createGraphics(); getDrawingView().prepareForBuffering(g); view.print(g); g.dispose(); return image; } private synchronized BufferedImage getPaintBuffer() { if (_paintBuffer == null) { _paintBuffer = bufferDrawingView(); } /*try { File f = File.createTempFile("MyScreenshot", new SimpleDateFormat("HH-mm-ss SSS").format(new Date())+".png"); ImageUtils.saveImageToFile(_paintBuffer, f, ImageType.PNG); if (logger.isLoggable(Level.INFO)) logger.info("Saved buffer to "+f.getAbsolutePath()); ToolBox.openFile(f); } catch (Exception e) { e.printStackTrace(); }*/ return _paintBuffer; } // ******************************************************************************* // * Repaint manager * // ******************************************************************************* static class FGERepaintManager extends RepaintManager { public static final boolean MANAGE_DIRTY_REGIONS = true; // For now temporary repaint areas are registered only for DrawingView !!!!!! // Later, we might extend this scheme to the whole view hierarchy // Using for example Hashtable<JComponent,Vector<Rectangle>> structure private WeakHashMap<JComponent, Vector<Rectangle>> temporaryRepaintAreas; public FGERepaintManager() { temporaryRepaintAreas = new WeakHashMap<JComponent, Vector<Rectangle>>(); } public synchronized void addTemporaryRepaintArea(Rectangle r, JComponent view) { if (MANAGE_DIRTY_REGIONS) { Vector<Rectangle> allRect = temporaryRepaintAreas.get(view); if (allRect == null) { allRect = new Vector<Rectangle>(); temporaryRepaintAreas.put(view, allRect); } allRect.add(r); if (paintRequestLogger.isLoggable(Level.FINER)) { paintRequestLogger.finer("addTemporaryRepaintArea(" + r + ") for " + view.getClass().getSimpleName() + " temporaryRepaintAreas size=" + allRect.size()); } } } private void repaintTemporaryRepaintAreas(JComponent component) { if (MANAGE_DIRTY_REGIONS) { Vector<Rectangle> allRect = temporaryRepaintAreas.get(component); if (allRect != null) { for (Rectangle r : allRect) { component.repaint(r); if (paintRequestLogger.isLoggable(Level.FINER)) { paintRequestLogger.finer("repaint(" + r + ") for " + component.getClass().getSimpleName()); } } allRect.clear(); } } } @Override public synchronized void addDirtyRegion(JComponent c, int x, int y, int w, int h) { if (paintRequestLogger.isLoggable(Level.FINEST)) { paintRequestLogger.finest("adding DirtyRegion: " + c.getName() + ", " + x + "," + y + " " + w + "x" + h); } // paintRequestLogger.warning("adding DirtyRegion: "+c.getName()+", "+x+","+y+" "+w+"x"+h); super.addDirtyRegion(c, x, y, w, h); /*if (MANAGE_DIRTY_REGIONS) { Rectangle r2 = new Rectangle(x,y,w,h); Iterator<Rectangle> it = temporaryRepaintAreas.iterator(); while(it.hasNext()) { Rectangle next = it.next(); if (r2.contains(next)) { if (paintRequestLogger.isLoggable(Level.FINEST)) paintRequestLogger.finer("Remove temporary repaint area "+next); it.remove(); } } }*/ } @Override public void paintDirtyRegions() { // Unfortunately most of the RepaintManager state is package // private and not accessible from the subclass at the moment, // so we can't print more info about what's being painted. if (paintRequestLogger.isLoggable(Level.FINEST)) { paintRequestLogger.finest("painting DirtyRegions"); } super.paintDirtyRegions(); } } public static void initFGERepaintManager() { logger.info("@@@@@@@@@@@@@@@@ initFGERepaintManager()"); repaintManager = new FGERepaintManager(); RepaintManager.setCurrentManager(repaintManager); } /** * * @param g * @param renderingBounds * @param gr * @return */ /*protected boolean renderUsingBuffer(Graphics g, Rectangle renderingBounds, GraphicalRepresentation gr) { // Use buffer Image buffer = getPaintBuffer(); Point p1 = renderingBounds.getLocation(); Point p2 = new Point(renderingBounds.x+renderingBounds.width,renderingBounds.y+renderingBounds.height); if ((p1.x < 0) || (p1.x > buffer.getWidth(null)) || (p1.y < 0) || (p1.y > buffer.getHeight(null)) || (p2.x < 0) || (p2.x > buffer.getWidth(null)) || (p2.y < 0) || (p2.y > buffer.getHeight(null))) { // We have here a request for render outside cached image // We cannot do that, so skip buffer use and do normal painting if (FGEPaintManager.paintPrimitiveLogger.isLoggable(Level.FINE)) FGEPaintManager.paintPrimitiveLogger.fine("GraphicalRepresentation:"+gr+" / request to render outside image buffer, use normal rendering clip="+renderingBounds); invalidate(gr); return false; } else { // OK, we are in our bounds if (FGEPaintManager.paintPrimitiveLogger.isLoggable(Level.FINE)) FGEPaintManager.paintPrimitiveLogger.fine("DrawingView: use image buffer, copy area "+renderingBounds); g.drawImage(buffer, p1.x,p1.y,p2.x,p2.y, p1.x,p1.y,p2.x,p2.y, null); return true; } }*/ /** * * @param g * @param renderingBounds * @param gr * @return */ protected boolean renderUsingBuffer(Graphics2D g, Rectangle renderingBounds, GraphicalRepresentation gr, double scale) { if (renderingBounds == null) { return false; } // Use buffer BufferedImage buffer = getPaintBuffer(); Rectangle viewBoundsInDrawingView = GraphicalRepresentation.convertRectangle(gr, renderingBounds, gr.getDrawingGraphicalRepresentation(), scale); Point dp1 = renderingBounds.getLocation(); Point dp2 = new Point(renderingBounds.x + renderingBounds.width, renderingBounds.y + renderingBounds.height); Point sp1 = viewBoundsInDrawingView.getLocation(); Point sp2 = new Point(viewBoundsInDrawingView.x + viewBoundsInDrawingView.width, viewBoundsInDrawingView.y + viewBoundsInDrawingView.height); if (sp1.x < 0 || sp1.x > buffer.getWidth() || sp1.y < 0 || sp1.y > buffer.getHeight() || sp2.x < 0 || sp2.x > buffer.getWidth() || sp2.y < 0 || sp2.y > buffer.getHeight()) { // We have here a request for render outside cached image // We cannot do that, so skip buffer use and do normal painting if (FGEPaintManager.paintPrimitiveLogger.isLoggable(Level.FINE)) { FGEPaintManager.paintPrimitiveLogger.fine("GraphicalRepresentation:" + gr + " / request to render outside image buffer, use normal rendering clip=" + renderingBounds); } // invalidate(gr); return false; } else { // OK, we are in our bounds if (FGEPaintManager.paintPrimitiveLogger.isLoggable(Level.FINE)) { FGEPaintManager.paintPrimitiveLogger.fine("DrawingView: use image buffer, copy area " + renderingBounds); } // Below was the previous implementation, using i think a too complex drawing primitive // (image was resized and so on) /*g.drawImage(buffer, dp1.x,dp1.y,dp2.x,dp2.y, sp1.x,sp1.y,sp2.x,sp2.y, null);*/ // Alternative implementation: improve performances (hope so) Graphics2D newGraphics = (Graphics2D) g.create(); /** Unactivation of anti-aliasing */ newGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); newGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); /** Fast rendering required here */ newGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED); newGraphics.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_SPEED); newGraphics.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_OFF); newGraphics.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_DISABLE); BufferedImage partialImage = buffer.getSubimage(sp1.x, sp1.y, viewBoundsInDrawingView.width, viewBoundsInDrawingView.height); newGraphics.drawImage(partialImage, dp1.x, dp1.y, null); newGraphics.dispose(); return true; } } }