/* * File : BoxBag.java * Created : 10-sep-2001 10:13 * 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.Activity; import edu.xtec.jclic.Constants; import java.awt.Dimension; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.ImageObserver; import java.util.ArrayList; import javax.swing.JComponent; /** * A BoxBag is a class derived from {@link edu.xtec.jclic.boxes.AbstractBox} that contains * a collection of "boxes" (objects also derived from AbstractBox). The boxes are stores into * a protected {@link java.util.ArrayList}. The class implements methods to add, remove and * retrieve boxes, and to manage some of its properties like visibility, status, location * and size. * @author Francesc Busquets (fbusquets@xtec.cat) * @version 13.09.23 */ public class BoxBag extends AbstractBox implements Cloneable, Resizable { protected ArrayList<AbstractBox> cells= new ArrayList<AbstractBox>(12); protected Rectangle2D preferredBounds=new Rectangle2D.Double(); protected AbstractBox backgroundBox=null; /** Creates new ActiveBoxBag */ public BoxBag(AbstractBox parent, JComponent container, BoxBase boxBase) { super(parent, container, boxBase); preferredBounds.setRect(getBounds()); } @Override public Object clone(){ BoxBag dBB=(BoxBag)super.clone(); dBB.preferredBounds=(Rectangle2D)preferredBounds.clone(); dBB.cells=new ArrayList<AbstractBox>(); for(int i=0; i<cells.size(); i++) dBB.cells.add((AbstractBox)getBox(i).clone()); if(backgroundBox!=null){ dBB.backgroundBox=(AbstractBox)backgroundBox.clone(); dBB.backgroundBox.setParent(dBB); } return dBB; } @Override public void setContainer(JComponent newContainer){ super.setContainer(newContainer); //for(int i=0; i<cells.size(); i++) // getBox(i).setContainer(newContainer); //if(backgroundBox!=null) // backgroundBox.setContainer(newContainer); } @Override public void end(){ for(int i=0; i<cells.size(); i++) getBox(i).end(); if(backgroundBox!=null){ backgroundBox.end(); backgroundBox=null; } cells.clear(); super.end(); } public Dimension getPreferredSize(){ return preferredBounds.getBounds().getSize(); } public Dimension getMinimumSize(){ Dimension d = getPreferredSize(); return new Dimension( Math.max(Constants.MIN_CELL_SIZE, d.width), Math.max(Constants.MIN_CELL_SIZE, d.height) ); } public Dimension getScaledSize(double scale){ Dimension d=getPreferredSize(); return new Dimension((int)(scale*d.width), (int)(scale*d.height)); } public void ensureCapacity(int n){ cells.ensureCapacity(n); } public void addBox(AbstractBox bx){ cells.add(bx); bx.setParent(this); if(cells.size()==1){ super.setBounds(bx); } else{ add(bx); } preferredBounds.setRect(getBounds()); } public int boxIndex(Object bx){ return bx==null ? -1 : cells.indexOf(bx); } public AbstractBox getBox(int id){ return (id<0 || id>=cells.size()) ? null : (AbstractBox)cells.get(id); } public AbstractBox getBackgroundBox(){ return backgroundBox; } public void setBackgroundBox(AbstractBox bx){ backgroundBox=bx; if(backgroundBox!=null){ add(backgroundBox); backgroundBox.setParent(this); } preferredBounds.setRect(getBounds()); } public void recalcSize(){ Rectangle2D r=new Rectangle2D.Double(x, y, 0, 0); if(backgroundBox!=null) r.add(backgroundBox); for(int i=0; i<cells.size(); i++) r.add((AbstractBox)(cells.get(i))); preferredBounds.setRect(r); x=r.getX(); y=r.getY(); width=r.getWidth(); height=r.getHeight(); } public int getNumCells(){ return cells.size(); } @Override public void setBorder(boolean newVal){ for(int i=0; i<cells.size(); i++) getBox(i).setBorder(newVal); } @Override public void setVisible(boolean newVal){ for(int i=0; i<cells.size(); i++) getBox(i).setVisible(newVal); //if(backgroundBox!=null) // backgroundBox.setVisible(newVal); } @Override public void setAlternative(boolean newVal){ super.setAlternative(newVal); for(int i=0; i<cells.size(); i++) getBox(i).setAlternative(newVal); } @Override public void setBoxBase(BoxBase setBb){ super.setBoxBase(setBb); //if(cells!=null) // for(int i=0; i<cells.size(); i++) // getBox(i).setBoxBase(setBb); //if(backgroundBox!=null) // backgroundBox.setBoxBase(setBb); } @Override public void setBounds(Rectangle2D r){ //if(width>0 && height>0 && (x!=newX || y!=newY || width!=newWidth || height!=newHeight)){ if(!r.isEmpty() && !r.equals(this)){ double scaleW=r.getWidth()/width; double scaleH=r.getHeight()/height; double dx=r.getX()-x; double dy=r.getY()-y; for(int i=0; i<cells.size(); i++){ AbstractBox bx=getBox(i); Point2D.Double p=new Point2D.Double(bx.x-x, bx.y-y); bx.setBounds(dx+x+scaleW*p.x, dy+y+scaleH*p.y, scaleW*bx.width, scaleH*bx.height); } if(backgroundBox!=null){ AbstractBox bx=backgroundBox; Point2D.Double p=new Point2D.Double(bx.x-x, bx.y-y); bx.setBounds(dx+x+scaleW*p.x, dy+y+scaleH*p.y, scaleW*bx.width, scaleH*bx.height); } } super.setBounds(r); } @Override public boolean update(Graphics2D g2, Rectangle dirtyRegion, ImageObserver io){ if(isEmpty() || !isVisible() || isTemporaryHidden()) return false; if(!intersects(dirtyRegion)) return false; if(backgroundBox!=null) backgroundBox.update(g2, dirtyRegion, io); AbstractBox bx; for(int i=0; i<cells.size(); i++) if(!((bx=getBox(i)).isMarked())) bx.update(g2, dirtyRegion, io); for(int i=0; i<cells.size(); i++) if((bx=getBox(i)).isMarked()) bx.update(g2, dirtyRegion, io); return true; } public boolean updateContent(Graphics2D g2, Rectangle dirtyRegion, ImageObserver io){ return true; } public AbstractBox findBox(Point2D p){ for(int i=cells.size()-1; i>=0; i--){ AbstractBox bx=getBox(i); if(bx.isVisible() && bx.contains(p)) return bx; } return null; } public int countInactiveCells(){ int n=0; for(int i=0; i<cells.size(); i++){ if(getBox(i).isInactive()) n++; } return n; } public static Dimension layoutSingle(Dimension preferredMaxSize, Resizable rs, int margin){ // Avoid exceptions when rs is null if(rs==null) return preferredMaxSize; // optimal dimension Dimension d=rs.getPreferredSize(); // minimal dimension Dimension minSize=rs.getMinimumSize(); // maximal dimension Dimension maxSize=preferredMaxSize; // remove margins maxSize.width-=2*margin; maxSize.height-=2*margin; // correct maxSize if less than minSize if(minSize.width>maxSize.width || minSize.height>maxSize.height){ maxSize=minSize; } // compute scale factor double scale=1; if(d.width>maxSize.width){ scale=(double)maxSize.width/d.width; } if((scale*d.height)>maxSize.height){ scale=(double)maxSize.height/d.height; } // resize bg d=rs.getScaledSize(scale); rs.setBounds(margin, margin, d.width, d.height); // restore margins d.width+=2*margin; d.height+=2*margin; return d; } public static Dimension layoutDouble(Dimension desiredMaxSize, Resizable rsA, Resizable rsB, int boxGridPos, int margin){ // number of horizontally and vertically grids boolean isHLayout=false; int nbh=1, nbv=1; switch(boxGridPos){ case Activity.AB: case Activity.BA: nbh=2; nbv=1; isHLayout=true; break; case Activity.AUB: case Activity.BUA: nbh=1; nbv=2; isHLayout=false; break; } Rectangle2D ra=rsA.getBounds2D(); Rectangle2D rb=rsB.getBounds2D(); // optimal dimensions Dimension da=rsA.getPreferredSize(); Dimension db=rsB.getPreferredSize(); Dimension d=new Dimension( isHLayout ? da.width+db.width : Math.max(da.width, db.width), isHLayout ? Math.max(da.height, db.height) : da.height+db.height ); // minimal dimensions Dimension minSizeA=rsA.getMinimumSize(); Dimension minSizeB=rsB.getMinimumSize(); Dimension minSize=new Dimension( isHLayout ? minSizeA.width+minSizeB.width : Math.max(minSizeA.width, minSizeB.width), isHLayout ? Math.max(minSizeA.height, minSizeB.height) : minSizeA.height+minSizeB.height ); // maximal dimension Dimension maxSize=desiredMaxSize; // remove margins maxSize.width-=(1+nbh)*margin; maxSize.height-=(1+nbv)*margin; // correct maxSize if less than minSize if(minSize.width>maxSize.width || minSize.height>maxSize.height){ maxSize.setSize(minSize); } // compute scale factor double scale=1; if(d.width>maxSize.width){ scale=(double)maxSize.width/d.width; } if((scale*d.height)>maxSize.height){ scale=(double)maxSize.height/d.height; } // correct possible minimal infractions // ... // resize da=rsA.getScaledSize(scale); db=rsB.getScaledSize(scale); // margins to center one box relatove to the other int dah, dav, dbh, dbv; dah=db.width>da.width ? (db.width-da.width)/2 : 0; dbh=da.width>db.width ? (da.width-db.width)/2 : 0; dav=db.height>da.height ? (db.height-da.height)/2 : 0; dbv=da.height>db.height ? (da.height-db.height)/2 : 0; switch(boxGridPos){ case Activity.AB: rsA.setBounds(margin, margin+dav, da.width, da.height); rsB.setBounds(2*margin+da.width, margin+dbv, db.width, db.height); break; case Activity.BA: rsB.setBounds(margin, margin+dbv, db.width, db.height); rsA.setBounds(2*margin+db.width, margin+dav, da.width, da.height); break; case Activity.AUB: rsA.setBounds(margin+dah, margin, da.width, da.height); rsB.setBounds(margin+dbh, 2*margin+da.height, db.width, db.height); break; case Activity.BUA: rsB.setBounds(margin+dbh, margin, db.width, db.height); rsA.setBounds(margin+dah, 2*margin+db.height, da.width, da.height); break; default: rsA.setBounds((int)(margin+scale*ra.getX()), (int)(margin+scale*ra.getY()), da.width, da.height); rsB.setBounds((int)(margin+scale*rb.getX()), (int)(margin+scale*rb.getY()), da.width, da.height); break; } // recompute d adding margins Rectangle r=new Rectangle(rsA.getBounds()); r.add(rsB.getBounds()); d.width=r.width+2*margin; d.height=r.height+2*margin; return d; } }