/**
* This file is part of VisiCut.
* Copyright (C) 2011 - 2013 Thomas Oster <thomas.oster@rwth-aachen.de>
* RWTH Aachen University - 52062 Aachen, Germany
*
* VisiCut is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* VisiCut 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with VisiCut. If not, see <http://www.gnu.org/licenses/>.
**/
package com.t_oster.visicut.model.graphicelements;
import com.t_oster.visicut.misc.Helper;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
/**
*
* @author Thomas Oster <thomas.oster@rwth-aachen.de>
*/
public class GraphicSet extends LinkedList<GraphicObject>
{
public static String translateAttVal(Object att)
{
if (att == null)
{
return null;
}
if (att instanceof Number)
{
return "" + ((int) (((Number) att).doubleValue()*100))/100d;
}
String attribute = att.toString();
try
{
return ResourceBundle.getBundle("com/t_oster/visicut/model/graphicelements/resources/AttributeTranslations").getString(attribute.toUpperCase());
}
catch (MissingResourceException ex)
{
return attribute;
}
}
protected AffineTransform basicTransform = new AffineTransform();
/**
* Get the value of basicTransform
*
* @return the value of basicTransform
*/
public AffineTransform getBasicTransform()
{
return basicTransform;
}
/**
* Set the value of basicTransform
* This value shall be used only for the import and for resetting
* the transform of this object. So it should be written only
* once after creation of the set
*
* @param basicTransform new value of basicTransform
*/
public void setBasicTransform(AffineTransform basicTransform)
{
this.basicTransform = basicTransform;
this.setTransform(new AffineTransform(basicTransform));
}
public AffineTransform transform = null;
public static final String PROP_TRANSFORM = "transform";
/**
* Get the value of transform
*
* @return the value of transform
*/
public AffineTransform getTransform()
{
return transform;
}
/**
* Set the value of transform
*
* @param transform new value of transform
*/
public void setTransform(AffineTransform transform)
{
AffineTransform oldTransform = this.transform;
this.transform = transform;
this.boundingBoxCache = null;
firePropertyChange(PROP_TRANSFORM, oldTransform, transform);
}
private List<PropertyChangeListener> pcls = new LinkedList<PropertyChangeListener>();
private Rectangle2D originalBoundingBoxCache = null;
/**
* Returns the BoundingBox of this Set ignoring the Transform,
* also ignoring the basicTransform
* @return
*/
public Rectangle2D getOriginalBoundingBox()
{
if (originalBoundingBoxCache == null)
{
for (GraphicObject o : this)
{
Rectangle2D current = o.getBoundingBox();
if (originalBoundingBoxCache == null)
{
originalBoundingBoxCache = current;
}
else
{
Rectangle2D.union(originalBoundingBoxCache, current, originalBoundingBoxCache);
}
}
}
return originalBoundingBoxCache;
}
private Rectangle2D boundingBoxCache = null;
/**
* Returns the BoundingBox of this Set when rendered with the current
* Transformation.
* @return
*/
public Rectangle2D getBoundingBox()
{
if (boundingBoxCache == null)
{
for (GraphicObject o : this)
{
Rectangle2D current = o.getBoundingBox();
if (current != null)
{
if (this.transform != null)
{
current = Helper.transform(current, this.transform);
}
if (boundingBoxCache == null)
{
boundingBoxCache = current;
}
else
{
Rectangle2D.union(boundingBoxCache, current, boundingBoxCache);
}
}
}
if (boundingBoxCache == null)
{
return new Rectangle2D.Double();
}
}
return boundingBoxCache;
}
/**
* Add PropertyChangeListener.
*
* @param listener
*/
public void addPropertyChangeListener(PropertyChangeListener listener)
{
pcls.add(listener);
}
/**
* Remove PropertyChangeListener.
*
* @param listener
*/
public void removePropertyChangeListener(PropertyChangeListener listener)
{
pcls.remove(listener);
}
@Override
public boolean add(GraphicObject o)
{
this.boundingBoxCache = null;
if (this.attributesCache != null)
{
this.attributesCache.addAll(o.getAttributes());
}
this.interestingAttributesCache = null;
return super.add(o);
}
public boolean remove(GraphicObject o)
{
this.boundingBoxCache = null;
if (this.attributesCache != null)
{
this.attributesCache.removeAll(o.getAttributes());
}
this.interestingAttributesCache = null;
return super.remove(o);
}
@Override
public GraphicSet clone()
{
GraphicSet result = new GraphicSet();
result.addAll(this);
result.setTransform(this.getTransform());
result.boundingBoxCache = boundingBoxCache;
result.originalBoundingBoxCache = originalBoundingBoxCache;
result.basicTransform = basicTransform;
return result;
}
private Map<String, Set<Object>> attributeValueCache = null;
public Set<Object> getAttributeValues(String attribute)
{
if (attributeValueCache == null)
{
attributeValueCache = new LinkedHashMap<String, Set<Object>>();
}
if (!attributeValueCache.containsKey(attribute))
{
Set<Object> values = new LinkedHashSet<Object>();
for(GraphicObject o : this)
{
values.addAll(o.getAttributeValues(attribute));
}
attributeValueCache.put(attribute, values);
}
return attributeValueCache.get(attribute);
}
private Set<String> attributesCache = null;
public Iterable<String> getAttributes()
{
if (attributesCache == null)
{
attributesCache = new LinkedHashSet();
for(GraphicObject o:this)
{
attributesCache.addAll(o.getAttributes());
}
}
return attributesCache;
}
private Set<String> interestingAttributesCache = null;
/**
* Returns only those attributes, where at least two different
* values are present
* @return
*/
public Iterable<String> getInterestingAttributes()
{
if (interestingAttributesCache == null)
{
interestingAttributesCache = new LinkedHashSet();
for (String attribute : this.getAttributes())
{
//only makes sense if at least two properties are present
if (this.getAttributeValues(attribute).size() > 1)
{
interestingAttributesCache.add(attribute);
}
}
}
return interestingAttributesCache;
}
public void rotateRelative(double angle)
{
Rectangle2D bb = this.getBoundingBox();
//move back
AffineTransform tr = AffineTransform.getTranslateInstance(bb.getCenterX(), bb.getCenterY());
//rotate
tr.concatenate(AffineTransform.getRotateInstance(angle));
//center
tr.concatenate(AffineTransform.getTranslateInstance(-bb.getCenterX(), -bb.getCenterY()));
//apply current
tr.concatenate(transform);
this.setTransform(tr);
}
public void rotateAbsolute(double angle)
{
double old = transform != null ? Helper.getRotationAngle(transform) : 0;
this.rotateRelative(angle-old);
}
private void firePropertyChange(String prop, Object oldValue, Object value)
{
PropertyChangeEvent e = new PropertyChangeEvent(this, prop, oldValue, value);
for (PropertyChangeListener l : pcls)
{
l.propertyChange(e);
}
}
}