/* * (c) 2000-2010 Carlos G�mez Rodr�guez, todos los derechos reservados / all rights reserved. * Licencia en license/bsd.txt / License in license/bsd.txt */ package eu.irreality.age.swing; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Insets; import java.util.ArrayList; import java.util.List; import java.awt.Rectangle; import java.awt.image.BufferedImage; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JTextPane; import javax.swing.LookAndFeel; import javax.swing.UIManager; import javax.swing.text.AbstractDocument; import javax.swing.text.Element; import javax.swing.text.MutableAttributeSet; import javax.swing.text.SimpleAttributeSet; import javax.swing.text.StyleConstants; import javax.swing.text.StyledDocument; import com.kitfox.svg.app.beans.SVGIcon; import eu.irreality.age.ImageConstants; public class FancyJTextPane extends JTextPane implements ImageConstants { private static final boolean VECTOR_TO_RASTER = true; //optimization: vector images will be converted to raster, conversion will hold while panel // size not altered //todo: the false variant really makes no sense. This should probably just be assumed. private ImageIcon rasterBackgroundImage; private SVGIcon vectorBackgroundImage; /**The vector background image, converted to a raster image for efficiency*/ //private BufferedImage convertedVectorBackgroundImage; /**This is used to know when to update the raster->vector conversion (when window has been moved)*/ private Rectangle lastVisibleRect = null; //for the top margin (issue 200 avoidance) private BufferedImage upperSubImage; //for the bottom margin private BufferedImage lowerSubImage; //if true, top-bottom margins act on viewable area instead of on whole document/text area contents private boolean marginsOnViewableArea = false; //however, setMargins() in ColoredSwingClient will set it to true by default private void updateVectorToRasterConversion ( ) { if ( vectorBackgroundImage == null ) return; Rectangle rect = getVisibleRect(); if ( rect == null ) return; if ( lastVisibleRect == null || ( rect.width != lastVisibleRect.width || rect.height != lastVisibleRect.height || rasterBackgroundImage == null ) ) //window moved/resized, or image changed { lastVisibleRect = rect; BufferedImage conversion = new BufferedImage(rect.width,rect.height,BufferedImage.TYPE_INT_ARGB); vectorBackgroundImage.setPreferredSize(new Dimension(rect.width,rect.height)); vectorBackgroundImage.setScaleToFit(true); vectorBackgroundImage.paintIcon(null, conversion.getGraphics(), 0, 0); rasterBackgroundImage = new ImageIcon(conversion); } } public void setMarginsOnViewableArea ( boolean value ) { marginsOnViewableArea = value; } public ImageIcon getRasterBackgroundImage() { return rasterBackgroundImage; } /* * returns Icon because if we return SVGIcon, checkCoalescing() (internal java method) * creates a dependency with the SVG Salamander library. */ public Icon getVectorBackgroundImage() { return vectorBackgroundImage; } //private int scalingMode = FIT_BOTH; private void refreshUpperSubImage() { Rectangle rect = getVisibleRect(); upperSubImage = new BufferedImage(rect.width,getMargin().top,BufferedImage.TYPE_INT_ARGB); Graphics tempG = upperSubImage.createGraphics(); tempG.drawImage(rasterBackgroundImage.getImage(),0,0,rect.width,rect.height,this); tempG.dispose(); } private void refreshLowerSubImage() { Rectangle rect = getVisibleRect(); lowerSubImage = new BufferedImage(rect.width,getMargin().bottom,BufferedImage.TYPE_INT_ARGB); Graphics tempG = lowerSubImage.createGraphics(); tempG.drawImage(rasterBackgroundImage.getImage(),0,-rect.height+getMargin().bottom,rect.width,rect.height,this); tempG.dispose(); } public void setRasterBackgroundImage(ImageIcon i) { this.rasterBackgroundImage = i; if ( rasterBackgroundImage != null || vectorBackgroundImage != null ) setOpaque(false); else setOpaque(true); if ( rasterBackgroundImage != null ) { refreshUpperSubImage(); refreshLowerSubImage(); } //repaint so that change takes place javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { repaint(); } }); } public void setVectorBackgroundImage(Icon i) //this should really be a SVGIcon, but declaring it as Icon removes applet dependency //(there is some reflection method in jcomponent that looks at the component's method's //arguments and complains if classes are not in the classpath, for some //reason) { this.vectorBackgroundImage = (SVGIcon) i; if ( vectorBackgroundImage != null || rasterBackgroundImage != null ) setOpaque(false); else setOpaque(true); vectorBackgroundImage.setAntiAlias(true); rasterBackgroundImage = null; //new conversion will have to be made //repaint so that change takes place javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { repaint(); } }); } public void setBackgroundImage(Icon ic) { if ( ic == null ) { if ( vectorBackgroundImage != null ) setVectorBackgroundImage(null); if ( rasterBackgroundImage != null ) setRasterBackgroundImage(null); return; } if ( !(ic instanceof ImageIcon) && !(ic instanceof SVGIcon) ) throw new UnsupportedOperationException("setBackgroundImage only supports ImageIcon or SVGIcon"); else if ( ic instanceof ImageIcon ) { vectorBackgroundImage = null; setRasterBackgroundImage((ImageIcon)ic); } else if ( ic instanceof SVGIcon ) { rasterBackgroundImage = null; setVectorBackgroundImage((SVGIcon)ic); } } public FancyJTextPane() { super(); LookAndFeel laf = UIManager.getLookAndFeel(); if ( laf.getID().equals("Nimbus") ) { //fix so that we can change background in Nimbus setUI(new javax.swing.plaf.basic.BasicEditorPaneUI()); } //setMargin(new Insets(80,80,80,80)); //setOpaque(false); setDocument ( new FancyStyledDocument() ); setBackground(new Color(0,0,0,0)); } //scaling options not yet implemented /* private Rectangle getDrawingCoordinates ( int baseHeight , int baseWidth , Rectangle viewport ) { int imageHeight = (int) baseHeight; int imageWidth = (int) baseWidth; int panelHeight = viewport.height; int panelWidth = viewport.width; //FIT_BOTH int drawX = viewport.x; int drawY = viewport.y; int drawW = panelWidth; int drawH = panelHeight; //theVectorImage.setScaleToFit(false); if ( scalingMode == NO_SCALING ) { drawX = panelWidth/2 - imageWidth/2; drawY = panelHeight/2 - imageHeight/2; drawW = imageWidth; drawH = imageHeight; //theVectorImage.setScaleToFit(false); } if ( scalingMode == FIT_WIDTH ) { drawX = 0; drawW = panelWidth; drawH = (int) ( panelWidth * ( (double) imageHeight / (double) imageWidth ) ); drawY = panelHeight/2 - drawH/2; } if ( scalingMode == FIT_HEIGHT ) { drawY = 0; drawH = panelHeight; drawW = (int) ( panelHeight * ( (double) imageWidth / (double) imageHeight ) ); drawX = panelWidth/2 - drawW/2; } // theVectorImage.setPreferredSize(new Dimension(drawW,drawH)); //theVectorImage.setScaleToFit(false); //theVectorImage.setPreferredSize(new Dimension(200,200)); //System.err.println(theVectorImage.getPreferredSize()); //yeah, gets the nominal size //theVectorImage.setScaleToFit(true); //we can set another pref. size to scale //theVectorImage.paintIcon(this, g, drawX, drawY); return new Rectangle(drawX,drawY,drawW,drawH); } */ //these variables are kept so that we know when to refresh the top margin subimage private int lastWidth=-1; private int lastHeight=-1; //change to paintComponent to avoid exceptions? done (was paint, also in super call) public void paintComponent(Graphics g) { if ( VECTOR_TO_RASTER ) updateVectorToRasterConversion(); //this is done only if applicable (checks inside method) //super.paint(g); //g.setXORMode(Color.white); Rectangle rect = null; rect = getVisibleRect(); if ( rasterBackgroundImage != null ) { //rect = getVisibleRect(); g.drawImage(rasterBackgroundImage.getImage(),rect.x,rect.y,rect.width,rect.height,this); } else if ( vectorBackgroundImage != null ) { //rect = getVisibleRect(); vectorBackgroundImage.setPreferredSize(new Dimension(rect.width,rect.height)); vectorBackgroundImage.setScaleToFit(true); vectorBackgroundImage.paintIcon(this, g, rect.x, rect.y); } if ( rasterBackgroundImage == null && vectorBackgroundImage == null && marginsOnViewableArea && (getMargin().top > 0 || getMargin().bottom > 0) ) { //in this case, we must repaint the background, or java will reuse our margin rectangles when scrolling, creating a mess! //(1) -> we need the component to be non-opaque so that java does not resuse our stuff setOpaque(false); Color oldColor = g.getColor(); g.setColor(getBackground()); //g.setColor(Color.ORANGE); g.fillRect(rect.x,rect.y,rect.width,rect.height); g.setColor(oldColor); } //g.drawImage(backgroundImage,0, 0, this); //g.setColor(Color.RED); //g.drawOval(5, 5, 100, 100); super.paintComponent(g); //esto si queremos que el margen superior sea "non-scrolling": if ( marginsOnViewableArea && (getMargin().top > 0 || getMargin().bottom > 0) ) { if ( rasterBackgroundImage != null ) { //Rectangle oldArea = g.getClipBounds(); //g.setClip(rect.x,rect.y,rect.width,getMargin().top); //System.err.println("Clipping rectangle: " + rect.x + " " + rect.y + " " + rect.width + " " + getMargin().top); //g.drawImage(rasterBackgroundImage.getImage(),rect.x,rect.y,rect.width,rect.height,this); //g.setClip(oldArea); //g.fillRect(rect.x, rect.y, rect.width, getMargin().top); if ( lastWidth != rect.width || lastHeight != rect.height ) { if ( getMargin().top > 0 ) refreshUpperSubImage(); if ( getMargin().bottom > 0 ) refreshLowerSubImage(); } lastWidth = rect.width; lastHeight = rect.height; if ( getMargin().top > 0 ) g.drawImage(upperSubImage,rect.x,rect.y,rect.width,getMargin().top,this); if ( getMargin().bottom > 0 ) g.drawImage(lowerSubImage,rect.x,rect.y+rect.height-getMargin().bottom,rect.width,getMargin().bottom,this); } else if ( vectorBackgroundImage != null ) { if ( getMargin().top > 0 ) { g.setClip(rect.x,rect.y,rect.width,getMargin().top); vectorBackgroundImage.setPreferredSize(new Dimension(rect.width,rect.height)); vectorBackgroundImage.paintIcon(this, g, rect.x, rect.y); } if ( getMargin().bottom > 0 ) { g.setClip(rect.x,rect.y+rect.height-getMargin().bottom,rect.width,getMargin().bottom); vectorBackgroundImage.setPreferredSize(new Dimension(rect.width,rect.height)); vectorBackgroundImage.paintIcon(this, g, rect.x, rect.y); } } if ( rasterBackgroundImage == null && vectorBackgroundImage == null ) { //draw rectangles of background colour on margin areas. This needs the component to be non-opaque, see (1) Color oldColor = g.getColor(); g.setColor(getBackground()); //g.setColor(Color.RED); g.fillRect(rect.x,rect.y,rect.width,getMargin().top); //g.setColor(Color.RED); g.fillRect(rect.x,rect.y+rect.height-getMargin().bottom,rect.width,getMargin().bottom); g.setColor(oldColor); } } } public void scaleFonts ( double scaleFactor ) { applyFontSizeTransform ( new FontSizeTransform ( FontSizeTransform.MULTIPLY , scaleFactor ) ); } /** * Zooms in/out by applying an offset or factor to font sizes throughout the document. * @param t Transform to apply to all font sizes in the document. */ private void applyFontSizeTransform ( FontSizeTransform t ) { StyledDocument sd = (StyledDocument) this.getDocument(); Element [] rootElts = sd.getRootElements(); List elements = new ArrayList(); for ( int i = 0 ; i < rootElts.length ; i++ ) { populateElementList ( elements , rootElts[i] ); } for ( int i = 0 ; i < elements.size() ; i++ ) { applyFontSizeTransform( t , (Element) elements.get(i) , sd ); } } /** * Adds the leaf elements that are descendant of the given element to the given list. * @param elements * @param elt */ private void populateElementList ( List elements , Element elt ) { if ( elt.isLeaf() ) elements.add(elt); //apply recursively to children for ( int i = 0 ; i < elt.getElementCount() ; i++ ) { populateElementList ( elements , elt.getElement(i) ); } } /** * Changes the font size of a document element. * @param t A transform to apply to font size. * @param elt An element of the document. * @param sd The styled document containing the element elt. */ private void applyFontSizeTransform ( FontSizeTransform t , Element elt , StyledDocument sd ) { //System.err.println("Element: " + elt); //System.err.println("Attributes: " + elt.getAttributes()); //System.err.println("Old Font size: " + elt.getAttributes().getAttribute(StyleConstants.FontSize)); Object attr = elt.getAttributes().getAttribute(StyleConstants.FontSize); if ( attr != null ) { int fontSize = StyleConstants.getFontSize(elt.getAttributes()); int start = elt.getStartOffset(); int end = elt.getEndOffset(); SimpleAttributeSet sizeOnly = new SimpleAttributeSet(); StyleConstants.setFontSize( sizeOnly , t.apply(fontSize) ); //System.err.println("New Font size: " + (fontSize+offset)); sd.setCharacterAttributes(start, end-start, sizeOnly, false); //StyleConstants.setFontSize( (MutableAttributeSet) elt.getAttributes(), fontSize+offset); } } }