/* * File : ActiveBox.java * Created : 12-dec-2000 17:05 * By : fbusquets * * JClic - Authoring and playing system for educational activities * * Copyright (C) 2000 - 2005 Francesc Busquets & Departament * d'Educacio de la Generalitat de Catalunya * * This program 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. * * This program 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 (see the LICENSE file). */ package edu.xtec.jclic.boxes; import edu.xtec.jclic.PlayStation; import edu.xtec.jclic.media.ActiveMediaPlayer; import edu.xtec.jclic.misc.Utils; import edu.xtec.util.JDomUtility; import edu.xtec.util.Options; import edu.xtec.util.ResourceBridge; import edu.xtec.util.StrUtils; import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.font.FontRenderContext; import java.awt.font.LineBreakMeasurer; import java.awt.font.TextAttribute; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import java.text.AttributedString; import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.RepaintManager; import javax.swing.SwingUtilities; /** * Objects of this class are widely used in JClic activities: cells in puzzles and * associations, messages and other objects are active boxes. The specific * content, size and location of <CODE>ActiveBox</CODE> is determined by its * {@link edu.xtec.jclic.boxes.ActiveBoxContent} members. Most ActiveBoxes have only * one content, but some of them can have a secondary or "alternative" content, indicated * by the <CODE>altContent</CODE> member. This content is used only when the <CODE>alternative</CODE> * flag of the <CODE>ActiveBox</CODE> is on. * Active boxes can host video and interactive media content (specified in the mediaContent member of * the {@link edu.xtec.jclic.boxes.ActiveBoxContent}) through the <CODE>hostedMediaPlayer</CODE> member. * @author Francesc Busquets (fbusquets@xtec.cat) * @version 13.08.28 */ public class ActiveBox extends AbstractBox implements Cloneable { public static boolean compressImages=true; public static boolean USE_TRANSFORM=false; public int idOrder, idLoc, idAss; protected ActiveBoxContent content; protected ActiveBoxContent altContent; public boolean hasHostedComponent; protected ActiveMediaPlayer hostedMediaPlayer; /** Creates new ActiveBox */ public ActiveBox(AbstractBox parent, JComponent container, BoxBase boxBase) { super(parent, container, boxBase); clear(); idLoc=-1; idOrder=-1; idAss=-1; hasHostedComponent=false; } public ActiveBox(AbstractBox parent, JComponent container, int setIdLoc, Rectangle2D r, BoxBase boxBase){ super(parent, container, boxBase); clear(); idLoc=setIdLoc; //idAss=-1; setBounds(r); } public void setHostedMediaPlayer(ActiveMediaPlayer amp){ ActiveMediaPlayer old=hostedMediaPlayer; hostedMediaPlayer=amp; if(old!=null && old!=amp) old.linkTo(null); } public ActiveBoxContent getCurrentContent(){ return isAlternative() ? altContent : content; } public ActiveBoxContent getContent(){ if(content==null) setContent(new ActiveBoxContent()); return content; } public void clear(){ content=null; altContent=null; idOrder=-1; setInactive(true); if(!hasHostedComponent) setHostedComponent(null); setHostedMediaPlayer(null); } public boolean isEquivalent(ActiveBox bx, boolean checkCase){ return bx!=null && content!=null && content.isEquivalent(bx.content, checkCase); } public boolean isCurrentContentEquivalent(ActiveBox bx, boolean checkCase){ return bx!=null && getCurrentContent()!=null && getCurrentContent().isEquivalent(bx.getCurrentContent(), checkCase); } public void exchangeLocation(ActiveBox bx){ Point2D.Double pt=new Point2D.Double(x, y); int idLoc0=idLoc; setLocation(bx.getLocation()); bx.setLocation(pt); idLoc=bx.idLoc; bx.idLoc=idLoc0; } public void copyContent(ActiveBox bx){ idOrder=bx.idOrder; idAss=bx.idAss; content=bx.content; altContent=bx.altContent; if(content!=null){ if(content.bb!=null) setBoxBase(content.bb); if(content.border!=null && bx.hasBorder()!=content.border.booleanValue()) setBorder(content.border.booleanValue()); if(content.img!=null && content.animated) Utils.refreshAnimatedImage(content.img); } setInactive(bx.isInactive()); setInverted(bx.isInverted()); setAlternative(bx.isAlternative()); setHostedComponent(bx.getHostedComponent()); hasHostedComponent=bx.hasHostedComponent; setHostedMediaPlayer(bx.hostedMediaPlayer); if(hostedMediaPlayer!=null) hostedMediaPlayer.setVisualComponentVisible(!isInactive() && isVisible()); } public void exchangeContent(ActiveBox bx){ ActiveBox bx0=new ActiveBox(getParent(), getContainerX(), getBoxBaseX()); bx0.copyContent(this); copyContent(bx); bx.copyContent(bx0); } public void setTextContent(String tx){ // only plain text! if(tx==null) tx=""; if(content==null) content=new ActiveBoxContent(); content.rawText=tx; content.text=tx; content.mediaContent=null; content.img=null; setHostedComponent(null); setInactive(false); checkHostedComponent(); setHostedMediaPlayer(null); } public void setIdOrder(int newIdOrder){ idOrder=newIdOrder; } public void setIdAss(int newIdAss){ idAss=newIdAss; } public void setDefaultIdAss(){ idAss=(content==null ? -1 : content.id); } public boolean isAtPlace(){ return idOrder==idLoc; } public void setContent(ActiveBoxContent abc){ setHostedComponent(null); setHostedMediaPlayer(null); content=abc; if(content!=null) { if(content.bb!=getBoxBaseX()) setBoxBase(content.bb); if(content.border!=null && hasBorder()!=content.border.booleanValue()) setBorder(content.border.booleanValue()); setInactive(false); if(abc.img!=null && abc.animated) Utils.refreshAnimatedImage(abc.img); checkHostedComponent(); checkAutoStartMedia(); } else clear(); } public void checkAutoStartMedia(){ ActiveBoxContent cnt=getContent(); if(cnt!=null && cnt.mediaContent!=null && cnt.mediaContent.autoStart==true && cnt.amp!=null){ final ActiveMediaPlayer amp=cnt.amp; SwingUtilities.invokeLater(new Runnable(){ public void run(){ amp.play(ActiveBox.this); } }); } } public void setAltContent(ActiveBoxContent abc){ altContent=abc; checkHostedComponent(); if(isAlternative() && hostedMediaPlayer!=null) setHostedMediaPlayer(null); } public void setCurrentContent(ActiveBoxContent abc){ if(isAlternative()) setAltContent(abc); else setContent(abc); repaint(); } public void setContent(ActiveBagContent abc, int i){ if(i<0) i=idOrder; if(abc==null || i>=abc.getNumCells()) return; if(abc.bb!=getBoxBaseX()) setBoxBase(abc.bb); setContent(abc.getActiveBoxContent(i)); } public void setAltContent(ActiveBagContent abc, int i){ if(i<0) i=idOrder; if(abc==null || abc.isEmpty() || i>abc.getNumCells()) return; setAltContent(abc.getActiveBoxContent(i)); } public boolean switchToAlt(ResourceBridge rb){ if(isAlternative() || altContent==null || altContent.isEmpty()) return false; setHostedComponent(null); setHostedMediaPlayer(null); setAlternative(true); checkHostedComponent(); checkAutoStartMedia(); return true; } @Override protected void checkHostedComponent(){ if(hasHostedComponent) return; ActiveBoxContent abc= getCurrentContent(); BoxBase bb=getBoxBaseResolve(); Component jc=null; if(!isInactive() && abc!=null && abc.htmlText!=null){ String s=abc.htmlText; if(abc.innerHtmlText!=null){ Color backColor=isInactive() ? bb.inactiveColor : isInverted() ? bb.textColor : bb.backColor; Color foreColor=isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor; Font f=bb.getOriginalFont(); s="<html>"+ "<body bgcolor=\"#"+ Integer.toHexString(backColor.getRGB()&0xFFFFFF) + "\">"+ "<div style=\""+ "background: #"+Integer.toHexString(backColor.getRGB()&0xFFFFFF)+"; "+ "color: #"+Integer.toHexString(foreColor.getRGB()&0xFFFFFF)+"; "+ "font-family: "+f.getFontName()+"; "+ "font-size: "+f.getSize()+"pt; "+ (f.isBold() ? "font-weight: bold;" : "") + "text-align: "+ (abc.txtAlign[0]==JDomUtility.ALIGN_LEFT ? "left" : abc.txtAlign[0]==JDomUtility.ALIGN_RIGHT ? "right" : "center") + "; " + "width: 100%; "+ "\">"+ abc.innerHtmlText + "</div>"+ "</body></html>"; } jc=getHostedComponent(); if(jc!=null && jc instanceof JLabel){ ((JLabel)jc).setText(s); return; } else{ jc=new JLabel(s); } } setHostedComponent(jc); } public boolean updateContent(Graphics2D g2, Rectangle dirtyRegion, ImageObserver io){ ActiveBoxContent abc=getCurrentContent(); BoxBase bb=getBoxBaseResolve(); if(isInactive() || abc==null || width<2 || height<2) return true; Rectangle2D.Double imgRect=null; if(abc.img!=null){ if(abc.imgClip!=null){ Rectangle r=abc.imgClip.getBounds(); /* * We have two methods to draw: * # Using AffineTransform * * Compatible with Mac OS-X 10.1, JRE 1.3.1 rev 1. * * Possibly more slow ? * * # Using Graphics.drawImage * * Doesn't work with Mac OSX 10.1 Java 1.3.1 rev. 1 * * Comment one of the two, or apply method 1 only to objects of class: * com.apple.mrj.internal.awt.graphics.VImage */ // Method 1 if(USE_TRANSFORM){ if(r.width>0 && r.height>0){ AffineTransform at; if(r.width!=width || r.height!=height){ double sx=width/r.width; double sy=height/r.height; at=AffineTransform.getScaleInstance(sx, sy); at.translate(x/sx-r.x, y/sy-r.y); } else at=AffineTransform.getTranslateInstance(x-r.x, y-r.y); g2.drawImage(abc.img, at, io); } } else{ // Method 2 g2.drawImage(abc.img, (int)x, (int)y, (int)(x+width), (int)(y+height), (int)r.x, (int)r.y, (int)(r.x+r.width), (int)(r.y+r.height), io); } } else { double imgw, imgh; boolean compress=false; if((imgw=abc.img.getWidth(io))==0) imgw=width; if((imgh=abc.img.getHeight(io))==0) imgh=height; double scale=1.0; if(compressImages==true && (width>0 && height>0) && (imgw>width || imgh>height)){ scale=Math.min(width/imgw, height/imgh); imgw*=scale; imgh*=scale; compress=true; } double xs = (abc.imgAlign[0]==JDomUtility.ALIGN_LEFT ? 0 : abc.imgAlign[0]==JDomUtility.ALIGN_RIGHT ? width-imgw : (width-imgw)/2); double ys = (abc.imgAlign[1]==JDomUtility.ALIGN_TOP ? 0 : abc.imgAlign[1]==JDomUtility.ALIGN_BOTTOM ? height-imgh : (height-imgh)/2); if(compress){ if(USE_TRANSFORM){ // Method 1: AffineTransform at=AffineTransform.getScaleInstance(scale, scale); at.translate((x+xs)/scale, (y+ys)/scale); g2.drawImage(abc.img, at, io); } else{ // Method 2: g2.drawImage(abc.img, (int)(x+xs), (int)(y+ys), (int)imgw, (int)imgh, io); } } else g2.drawImage(abc.img, (int)(x+xs), (int)(y+ys), io); if(abc.avoidOverlapping && abc.text!=null /*&& !JDomUtility.isDefaultAlign(abc.imgAlign)*/) imgRect=new Rectangle2D.Double(Math.max(0.0,xs), Math.max(0.0,ys), Math.min(width, imgw), Math.min(height, imgh)); } } if(abc.text!=null && abc.text.length()>0){ double px=this.x; double py=this.y; double pWidth=this.width; double pHeight=this.height; if(imgRect!=null){ double[] prx=new double[]{0, imgRect.x, imgRect.x+imgRect.width, pWidth}; double[] pry=new double[]{0, imgRect.y, imgRect.y+imgRect.height, pHeight}; Rectangle2D.Double[] rr=new Rectangle2D.Double[]{ new Rectangle2D.Double(prx[0], pry[0], prx[3], pry[1]), new Rectangle2D.Double(prx[0], pry[2], prx[3], pry[3]-pry[2]), new Rectangle2D.Double(prx[0], pry[0], prx[1], pry[3]), new Rectangle2D.Double(prx[2], pry[0], prx[3]-prx[2], pry[3])}; Rectangle2D.Double rmax=rr[0]; double maxSurface=rmax.width*rmax.height; for(int i=1; i<rr.length; i++){ double s=rr[i].width*rr[i].height; if(s>maxSurface-1){ if(Math.abs(s-maxSurface)<=1){ boolean b=false; switch(i){ case 1: b=(abc.txtAlign[1]==JDomUtility.ALIGN_BOTTOM); break; case 2: b=(abc.txtAlign[0]==JDomUtility.ALIGN_LEFT); break; case 3: b=(abc.txtAlign[0]==JDomUtility.ALIGN_RIGHT); break; } if(!b) continue; } maxSurface=s; rmax=rr[i]; } } px+=rmax.x; py+=rmax.y; pWidth=rmax.width; pHeight=rmax.height; } double w, wtl, h, hl, dx, dy; double ww=Math.max(5.0, pWidth-2*bb.textMargin); AttributedString atext; TextLayout layout; int rt; FontRenderContext frc=g2.getFontRenderContext(); LineBreakMeasurer lbm; int maxLines=StrUtils.countSpaces(abc.text)+1; for(;;){ int i; atext=new AttributedString(abc.text); atext.addAttribute(TextAttribute.FONT, bb.getFont()); lbm=new LineBreakMeasurer(atext.getIterator(), frc); w=0; h=0; hl=0; lbm.setPosition(0); rt=abc.text.indexOf('\n'); for(i=0; lbm.getPosition()<abc.text.length() && h<pHeight ;i++){ if(rt>lbm.getPosition()){ layout=lbm.nextLayout((float)ww, rt, false); rt=abc.text.indexOf('\n', lbm.getPosition()); } else layout=lbm.nextLayout((float)ww); if(layout==null) break; wtl=layout.getVisibleAdvance(); if(wtl>w) w=wtl; hl=layout.getLeading(); h+= layout.getAscent()+layout.getDescent()+hl; } h-=hl; if((h<=pHeight && i<=maxLines) || bb.reduceFont()==false) break; JComponent jc=getContainerResolve(); if(jc!=null) RepaintManager.currentManager(jc).markCompletelyDirty(jc); } dy= py + (abc.txtAlign[1]==JDomUtility.ALIGN_TOP ? 0 : abc.txtAlign[1]==JDomUtility.ALIGN_BOTTOM ? pHeight-h : (pHeight-h)/2); g2.setColor(isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor); lbm.setPosition(0); rt=abc.text.indexOf('\n'); h=0; while(lbm.getPosition()<abc.text.length() && h<pHeight){ if(rt>lbm.getPosition()){ layout=lbm.nextLayout((float)ww, rt, false); rt=abc.text.indexOf('\n', lbm.getPosition()); } else layout=lbm.nextLayout((float)ww); if(layout==null) break; wtl=layout.getVisibleAdvance(); dx= px + bb.textMargin + (abc.txtAlign[0]==JDomUtility.ALIGN_LEFT ? 0 : abc.txtAlign[0]==JDomUtility.ALIGN_RIGHT ? ww-wtl : (ww-wtl)/2); // 21-dec-1009 // Removed: right-to-left writting is automatically computed // by TextLayout! // //if(!layout.isLeftToRight()){ // dx-=wtl; //} h+=layout.getAscent(); if(bb.shadow){ g2.setColor(bb.shadowColor); layout.draw(g2, (float)(dx+bb.getDynFontSize()/10), (float)(dy+h+bb.getDynFontSize()/10)); g2.setColor(isInverted() ? bb.backColor : isAlternative() ? bb.alternativeColor : bb.textColor); } layout.draw(g2, (float)dx, (float)(dy+h)); h+=layout.getDescent()+layout.getLeading(); } } return true; } public boolean playMedia(PlayStation ps){ ActiveBoxContent abc=getCurrentContent(); // ORIGINAL /* if(abc!=null && abc.mediaContent!=null){ ac.playMedia(abc.mediaContent, this); return true; } */ // MODIFIED TO REPAINT ANIMATED GIFS if(abc!=null){ if(abc.animated){ Utils.refreshAnimatedImage(abc.img); repaint(); } if(abc.mediaContent!=null){ ps.playMedia(abc.mediaContent, this); return true; } } return false; } public String getDescription(){ return content==null ? "" : content.getDescription(); } @Override public void setBounds(Rectangle2D r){ super.setBounds(r); if(hostedMediaPlayer!=null) hostedMediaPlayer.checkVisualComponentBounds(this); } @Override public void end(){ clear(); super.end(); } public static void checkOptions(Options options){ if(!options.getBoolean(Options.JAVA14) && options.getBoolean(Options.MAC)){ USE_TRANSFORM=true; } } }