/* Copyright (C) 2005-2011 Fabio Riccardi */
package com.lightcrafts.ui.region;
import com.lightcrafts.model.Contour;
import com.lightcrafts.model.Region;
import com.lightcrafts.ui.mode.Mode;
import com.lightcrafts.ui.action.ToggleAction;
import com.lightcrafts.utils.xml.XMLException;
import com.lightcrafts.utils.xml.XmlNode;
import javax.swing.*;
import javax.swing.event.MouseInputListener;
import javax.swing.event.UndoableEditListener;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.util.*;
import java.util.List;
/** A container for two kinds of Component: CurveComponents, and a single
* given underlay. The CurveComponent regions are drawn on top but are not
* opaque. All child Components have bounds set to the preferred size of the
* underlay.
* <p>
* A cookie mechanism is provided so that an arbitrary number of different
* CurveComponents can be remembered and swapped around. Only one can be
* active at a time.
* <p>
* The current overlay CurveComponent will intercept all MouseEvents. There
* is no hope of the underlay getting any user events.
*/
public class RegionOverlay extends JPanel implements Mode {
private CurveComponent overlay;
private Map overlays;
private Rectangle underlayBounds;
private RegionModel model;
private RegionChangeMulticaster multicaster;
private AffineTransform xform;
private boolean m_entered;
private Collection mouseListeners; // External MouseMotionListeners
private ShowHideRegionsAction showHideToggle; // Can show/hide the overlays
public RegionOverlay() {
setLayout(null);
overlays = new HashMap();
model = new RegionModel(this);
showHideToggle = new ShowHideRegionsAction(this);
multicaster = new RegionChangeMulticaster();
mouseListeners = new LinkedList();
addPopupListener();
}
public void enter() {
m_entered = true;
}
public void exit() {
m_entered = false;
}
public boolean isIn() {
return m_entered;
}
public ToggleAction getShowHideAction() {
return showHideToggle;
}
public void finishEditingCurve() {
if (overlay != null)
overlay.finishEditingCurve();
}
// Should be called only from ShowHideRegionsAction, to maintain
// consistency among all the controls for this feature.
void setRegionsVisible(boolean visible) {
if (visible) {
addOverlay();
}
else {
removeOverlay();
}
}
public void setCurveType(int type) {
model.setNewCurveType(type);
}
// Accessors for states that are hooked to menu items:
public int getCurveType() {
return model.getNewCurveType();
}
// End accessors for states hooked to menu items.
public void setCookie(Object cookie) {
setCookie(cookie, false, false);
}
public void setCookie(Object cookie, boolean clonePts, boolean spotCurves) {
if (overlay != null) {
if (showHideToggle.isRegionsVisible()) {
model.setMajorMode(new NewCurveMode(model, overlay));
removeOverlay();
}
}
CurveSelection selection = model.getSelection();
selection.setCookie(cookie);
if (cookie == null) {
overlay = null;
return;
}
if (overlays.containsKey(cookie)) {
overlay = (CurveComponent) overlays.get(cookie);
}
else {
overlay = new CurveComponent(model, clonePts, spotCurves);
overlay.setOpaque(false);
overlays.put(cookie, overlay);
overlay.setCookie(cookie);
model.addComponent(overlay);
}
model.setMajorMode(new NewCurveMode(model, overlay));
if (xform != null) {
overlay.setTransform(xform);
}
if (showHideToggle.isRegionsVisible()) {
addOverlay();
}
}
public boolean hasCookie(Object cookie) {
return overlays.containsKey(cookie);
}
public boolean isCookie(Object cookie) {
return cookie.equals(overlay.getCookie());
}
public Region getRegion(Object cookie) {
if (cookie != null) {
CurveComponent comp = (CurveComponent) overlays.get(cookie);
Set curves = model.getCurves(comp);
if (! curves.isEmpty()) {
ArrayList contours = new ArrayList();
for (Iterator i=curves.iterator(); i.hasNext(); ) {
Curve curve = (Curve) i.next();
Contour contour = model.getContour(curve);
contours.add(contour);
}
Region region = new ContourRegion(contours);
return region;
}
}
return null;
}
public Collection getShapes(Object cookie) {
if (cookie == null) {
return null;
}
CurveComponent comp = (CurveComponent) overlays.get(cookie);
Set curves = model.getCurves(comp);
Set shapes = new HashSet();
for (Iterator i=curves.iterator(); i.hasNext(); ) {
Curve curve = (Curve) i.next();
if (curve.isValidShape()) {
shapes.add(curve);
}
}
return shapes;
}
public Collection getAllShapes() {
Set curves = model.getAllCurves();
Set shapes = new HashSet();
for (Iterator i=curves.iterator(); i.hasNext(); ) {
Curve curve = (Curve) i.next();
if (curve.isValidShape()) {
shapes.add(curve);
}
}
return shapes;
}
public void shareShape(Object cookie, SharedShape shape, boolean clone) {
Curve curve = (Curve) shape;
if (clone) {
curve = (Curve) curve.clone();
}
CurveComponent comp = (CurveComponent) overlays.get(cookie);
model.addCurve(comp, curve);
}
public void unShareShape(Object cookie, SharedShape shape) {
Curve curve = (Curve) shape;
CurveComponent comp = (CurveComponent) overlays.get(cookie);
model.removeCurve(comp, curve);
}
public void removeShape(SharedShape shape) {
model.removeCurve((Curve) shape);
}
/** This method saves CubicCurves and their associations with cookie
* Objects as identified by their order in the argument. It saves no
* information about the cookies.
*/
public void save(List cookies, XmlNode node) {
LinkedList comps = new LinkedList();
for (Iterator i=cookies.iterator(); i.hasNext(); ) {
CurveComponent comp = (CurveComponent) overlays.get(i.next());
comps.add(comp);
}
model.save(comps, node);
}
/**
* This method restores CubicCurves and reforms their assocations with
* cookie Objects as identified by their order in the argument. The
* cookies must already be restored and added to the overlay before this
* is called.
*/
public void restore(List cookies, XmlNode node) throws XMLException {
LinkedList comps = new LinkedList();
for (Iterator i=cookies.iterator(); i.hasNext(); ) {
CurveComponent comp = (CurveComponent) overlays.get(i.next());
comps.add(comp);
}
model.restore(comps, node);
}
/**
* This works like restore(), except it doesn't clear out existing cookies
* or region data.
*/
public void addSaved(List cookies, XmlNode node) throws XMLException {
LinkedList comps = new LinkedList();
for (Iterator i=cookies.iterator(); i.hasNext(); ) {
CurveComponent comp = (CurveComponent) overlays.get(i.next());
comps.add(comp);
}
model.addSaved(comps, node);
}
public void setTransform(AffineTransform xform) {
this.xform = xform;
if (overlay != null) {
overlay.setTransform(xform);
}
}
public void setUnderlayBounds(Rectangle bounds) {
underlayBounds = bounds;
}
public Rectangle getUnderlayBounds() {
return underlayBounds;
}
public void doLayout() {
if (overlay != null) {
Dimension size = getSize();
overlay.setLocation(0, 0);
overlay.setSize(size);
}
}
void regionBatchStart(Collection comps) {
Collection cookies = getCookies(comps);
multicaster.regionBatchStart(cookies);
}
void regionChanged(Collection comps, Curve curve) {
if (curve.isValidShape()) {
Collection cookies = getCookies(comps);
multicaster.regionChanged(cookies, curve);
}
}
void regionsBatchEnd(Collection comps) {
Collection cookies = getCookies(comps);
multicaster.regionBatchEnd(cookies);
}
private Collection getCookies(Collection comps) {
Set cookies = new HashSet();
for (Iterator i=comps.iterator(); i.hasNext(); ) {
CurveComponent comp = (CurveComponent) i.next();
Object cookie = comp.getCookie();
cookies.add(cookie);
}
return cookies;
}
public void addRegionListener(RegionListener listener) {
multicaster.add(listener);
}
public void removeRegionListener(RegionListener listener) {
multicaster.remove(listener);
}
public void addSelectionListener(CurveSelection.Listener listener) {
CurveSelection selection = model.getSelection();
selection.addSelectionListener(listener);
}
public void removeSelectionListener(CurveSelection.Listener listener) {
CurveSelection selection = model.getSelection();
selection.removeSelectionListener(listener);
}
public void addUndoableEditListener(UndoableEditListener listener) {
model.addUndoableEditListener(listener);
}
public void removeUndoableEditListener(UndoableEditListener listener) {
model.removeUndoableEditListener(listener);
}
private void addOverlay() {
if (overlay != null) {
add(overlay);
for (Iterator i=mouseListeners.iterator(); i.hasNext(); ) {
MouseInputListener listener = (MouseInputListener) i.next();
overlay.addMouseListener(listener);
overlay.addMouseMotionListener(listener);
}
validate();
repaint();
}
}
private void removeOverlay() {
if (overlay != null) {
remove(overlay);
for (Iterator i=mouseListeners.iterator(); i.hasNext(); ) {
MouseInputListener listener = (MouseInputListener) i.next();
overlay.removeMouseListener(listener);
overlay.removeMouseMotionListener(listener);
}
Rectangle bounds = overlay.getBounds();
repaint(bounds);
}
}
// A mouse listener for our popup, which just offers to unhide the overlay.
private void addPopupListener() {
addMouseListener(
new MouseAdapter() {
public void mousePressed(MouseEvent event) {
if (event.isPopupTrigger()) {
showPopup(event);
}
}
public void mouseReleased(MouseEvent event) {
if (event.isPopupTrigger()) {
showPopup(event);
}
}
}
);
}
private void showPopup(MouseEvent event) {
if (event.isPopupTrigger() && ! showHideToggle.getState()) {
JMenuItem item = new JMenuItem("Show Regions");
item.addActionListener(
new ActionListener() {
public void actionPerformed(ActionEvent event) {
showHideToggle.setState(true);
}
}
);
JPopupMenu popup = new JPopupMenu();
popup.add(item);
popup.show(event.getComponent(), event.getX(), event.getY());
}
}
public JComponent getOverlay() {
return this;
}
public void addMouseInputListener(MouseInputListener listener) {
mouseListeners.add(listener);
if (overlay != null) {
overlay.addMouseListener(listener);
overlay.addMouseMotionListener(listener);
}
addMouseListener(listener);
addMouseMotionListener(listener);
}
public void removeMouseInputListener(MouseInputListener listener) {
mouseListeners.remove(listener);
if (overlay != null) {
overlay.removeMouseListener(listener);
overlay.removeMouseMotionListener(listener);
}
removeMouseListener(listener);
removeMouseMotionListener(listener);
}
public boolean wantsAutocroll() {
return true;
}
public void dispose() {
// nothing to dispose of
}
}