/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* 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 3 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.swing.figure;
import com.bc.ceres.core.Assert;
import com.bc.ceres.swing.figure.support.ScaleHandle;
import com.bc.ceres.swing.figure.support.StyleDefaults;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
/**
* Base class for all {@link Figure} implementations.
* <p/>
* Provides support for the following properties:
* {@code selectable}, {@code selected}, {@code normalStyle}, {@code selectedStyle}.
*
* @author Norman Fomferra
* @since Ceres 0.10
*/
public abstract class AbstractFigure implements Figure {
protected static final Figure[] NO_FIGURES = new Figure[0];
protected static final Handle[] NO_HANDLES = new Handle[0];
private List<FigureChangeListener> listenerList;
private static final String OPERATION_NOT_SUPPORTED = "Operation not supported.";
private boolean selectable;
private boolean selected;
private FigureStyle normalStyle;
private FigureStyle selectedStyle;
protected AbstractFigure() {
}
protected AbstractFigure(FigureStyle normalStyle, FigureStyle selectedStyle) {
Assert.notNull(normalStyle, "normalStyle");
Assert.notNull(selectedStyle, "selectedStyle");
this.normalStyle = normalStyle;
this.selectedStyle = selectedStyle;
}
/**
* The default implementation returns {@code false}.
*
* @return Always {@code false}.
*/
@Override
public boolean isSelectable() {
return selectable;
}
public void setSelectable(boolean selectable) {
if (this.selectable != selectable) {
this.selectable = selectable;
fireFigureChanged();
}
}
@Override
public boolean isSelected() {
return selected;
}
@Override
public void setSelected(boolean selected) {
if (this.selected != selected) {
this.selected = selected;
fireFigureChanged();
}
}
@Override
public FigureStyle getNormalStyle() {
return normalStyle;
}
public void setNormalStyle(FigureStyle normalStyle) {
Assert.notNull(normalStyle, "normalStyle");
if (!normalStyle.equals(this.normalStyle)) {
this.normalStyle = normalStyle;
fireFigureChanged();
}
}
@Override
public FigureStyle getSelectedStyle() {
return selectedStyle;
}
public void setSelectedStyle(FigureStyle selectedStyle) {
Assert.notNull(selectedStyle, "selectedStyle");
if (!selectedStyle.equals(this.selectedStyle)) {
this.selectedStyle = selectedStyle;
fireFigureChanged();
}
}
@Override
public FigureStyle getEffectiveStyle() {
return isSelected() ? getSelectedStyle() : getNormalStyle();
}
/**
* The default implementation returns {@code false}.
*
* @return Always {@code false}.
*/
@Override
public boolean contains(Figure figure) {
return false;
}
/**
* The default implementation returns {@code 0}.
*
* @return Always {@code 0}.
*/
@Override
public int getFigureCount() {
return 0;
}
/**
* The default implementation returns {@code 0}.
*
* @return Always {@code 0}.
*/
@Override
public int getFigureIndex(Figure figure) {
return 0;
}
/**
* The default implementation returns {@code null}.
*
* @return Always {@code null}.
*/
@Override
public Figure getFigure(Point2D p, AffineTransform m2v) {
return null;
}
/**
* The default implementation returns an empty array.
*
* @param shape The shape defining the area in which the figures must be contained.
* @return Always an empty array.
*/
@Override
public Figure[] getFigures(Shape shape) {
return NO_FIGURES;
}
/**
* The default implementation returns an empty array.
*
* @return Always an empty array.
*/
@Override
public Figure[] getFigures() {
return NO_FIGURES;
}
@Override
public synchronized boolean addFigure(Figure figure) {
return addFigure(getFigureCount(), figure);
}
@Override
public boolean addFigure(int index, Figure figure) {
if (!contains(figure)) {
boolean added = addFigureImpl(index, figure);
if (added) {
fireFiguresAdded(figure);
}
return added;
}
return false;
}
@Override
public synchronized Figure[] addFigures(Figure... figures) {
Figure[] added = addFiguresImpl(figures);
if (added.length > 0) {
fireFiguresAdded(added);
}
return added;
}
@Override
public synchronized boolean removeFigure(Figure figure) {
boolean removed = removeFigureImpl(figure);
if (removed) {
fireFiguresRemoved(figure);
}
return removed;
}
@Override
public Figure[] removeFigures(Figure... figures) {
if (getFigureCount() > 0 && figures.length > 0) {
Figure[] removed = removeFiguresImpl(figures);
fireFiguresRemoved(removed);
return removed;
}
return NO_FIGURES;
}
@Override
public Figure[] removeAllFigures() {
if (getFigureCount() > 0) {
Figure[] figures = removeFiguresImpl();
fireFiguresRemoved(figures);
return figures;
}
return NO_FIGURES;
}
/**
* The default implementation throws an [@code IllegalStateException}.
*
* @return Does never return normally.
*/
@Override
public Figure getFigure(int index) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
/**
* The default implementation throws an [@code IllegalStateException}.
*
* @param index The index.
* @param figure The figure.
* @return Does never return normally.
*/
protected boolean addFigureImpl(int index, Figure figure) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
protected boolean addFigureImpl(Figure figure) {
return addFigureImpl(getFigureCount(), figure);
}
protected Figure[] addFiguresImpl(Figure[] figures) {
ArrayList<Figure> added = new ArrayList<Figure>(figures.length);
for (Figure figure : figures) {
if (addFigureImpl(figure)) {
added.add(figure);
}
}
return added.toArray(new Figure[added.size()]);
}
/**
* The default implementation throws an [@code IllegalStateException}.
*
* @param figure The figure.
* @return Does never return normally.
*/
protected boolean removeFigureImpl(Figure figure) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
protected Figure[] removeFiguresImpl(Figure[] figures) {
ArrayList<Figure> removed = new ArrayList<Figure>(figures.length);
for (Figure figure : figures) {
if (removeFigureImpl(figure)) {
removed.add(figure);
}
}
return removed.toArray(new Figure[removed.size()]);
}
protected Figure[] removeFiguresImpl() {
return removeFiguresImpl(getFigures());
}
@Override
public void move(double dx, double dy) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public void scale(Point2D point, double sx, double sy) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public void rotate(Point2D point, double theta) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public double[] getSegment(int index) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public void setSegment(int index, double[] segment) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public void addSegment(int index, double[] segment) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
@Override
public void removeSegment(int index) {
throw new IllegalStateException(OPERATION_NOT_SUPPORTED);
}
/**
* The default implementation returns zero.
*
* @return Always {@code 0}.
*/
@Override
public int getMaxSelectionStage() {
return 0;
}
/**
* The default implementation returns an empty array.
* Clients should override in order to create an array of handles suitable for the given selection stage.
*
* @param selectionStage The selection stage. {@code 1 <= selectionLevel <=} {@link #getMaxSelectionStage()}
*/
@Override
public Handle[] createHandles(int selectionStage) {
return NO_HANDLES;
}
/**
* The default implementation returns [@code null}.
* Clients should override in order to produce something more meaningful.
*
* @return Always {@code null}.
*/
@Override
public Object createMemento() {
return null;
}
/**
* The default implementation does nothing.
* Clients should override in order to evaluate the passed in {@code memento}. object.
*
* @param memento A memento object.
*/
@Override
public void setMemento(Object memento) {
}
@Override
public synchronized void addChangeListener(FigureChangeListener listener) {
if (listenerList == null) {
listenerList = new ArrayList<FigureChangeListener>(3);
}
listenerList.add(listener);
}
@Override
public synchronized void removeChangeListener(FigureChangeListener listener) {
if (listenerList != null) {
listenerList.remove(listener);
}
}
@Override
public synchronized FigureChangeListener[] getChangeListeners() {
if (listenerList != null) {
return listenerList.toArray(new FigureChangeListener[listenerList.size()]);
} else {
return new FigureChangeListener[0];
}
}
@Override
public synchronized void dispose() {
if (listenerList != null) {
listenerList.clear();
listenerList = null;
}
}
@Override
public Object clone() {
try {
final AbstractFigure figure = (AbstractFigure) super.clone();
figure.listenerList = null;
return figure;
} catch (CloneNotSupportedException e) {
throw new IllegalStateException(e);
}
}
protected void fireFiguresAdded(Figure... figures) {
fireFigureChanged(new FigureChangeEvent(this,
FigureChangeEvent.FIGURES_ADDED,
figures));
}
protected void fireFiguresRemoved(Figure... figures) {
fireFigureChanged(new FigureChangeEvent(this,
FigureChangeEvent.FIGURES_REMOVED,
figures));
}
protected void fireFigureChanged() {
fireFigureChanged(new FigureChangeEvent(this,
FigureChangeEvent.FIGURE_CHANGED,
null));
}
protected void fireFigureChanged(FigureChangeEvent event) {
for (FigureChangeListener listener : getChangeListeners()) {
listener.figureChanged(event);
}
}
protected Handle[] createScaleHandles(double distance) {
final ArrayList<Handle> handleList = new ArrayList<Handle>(8);
FigureStyle handleStyle = getHandleStyle();
// TODO - reactivate RotateHandle and fix rotation implementation in 4.7-FINAL
// handleList.add(new RotateHandle(this, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.NW, -distance, -distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.NE, +distance, -distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.SE, +distance, +distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.SW, -distance, +distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.N, 0.0, -distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.E, +distance, 0.0, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.S, 0.0, +distance, handleStyle));
handleList.add(new ScaleHandle(this, ScaleHandle.W, -distance, 0.0, handleStyle));
return handleList.toArray(new Handle[handleList.size()]);
}
protected FigureStyle getHandleStyle() {
return StyleDefaults.HANDLE_STYLE;
}
protected FigureStyle getSelectedHandleStyle() {
return StyleDefaults.SELECTED_HANDLE_STYLE;
}
}