package org.archstudio.bna.logics.editing;
import static org.archstudio.sysutils.SystemUtils.firstOrNull;
import java.util.Collection;
import org.archstudio.bna.BNAModelEvent;
import org.archstudio.bna.IBNAModelListener;
import org.archstudio.bna.IBNAWorld;
import org.archstudio.bna.IThing;
import org.archstudio.bna.facets.IHasMutableReferencePoint;
import org.archstudio.bna.facets.IHasSelected;
import org.archstudio.bna.keys.IThingKey;
import org.archstudio.bna.keys.ThingKey;
import org.archstudio.bna.logics.AbstractThingLogic;
import org.archstudio.bna.logics.events.DragMoveEvent;
import org.archstudio.bna.logics.events.DragMoveEventsLogic;
import org.archstudio.bna.logics.events.IDragMoveListener;
import org.archstudio.bna.logics.tracking.ThingValueTrackingLogic;
import org.archstudio.bna.things.shapes.ReshapeHandleThing;
import org.archstudio.bna.utils.Assemblies;
import org.archstudio.bna.utils.BNAUtils;
import org.archstudio.bna.utils.UserEditableUtils;
import org.archstudio.sysutils.Finally;
import org.archstudio.sysutils.SystemUtils;
import org.eclipse.swt.graphics.Point;
import com.google.common.collect.Lists;
public abstract class AbstractReshapeLogic<R extends IThing, D> extends AbstractThingLogic
implements IBNAModelListener, IDragMoveListener {
private final IThingKey<D> HANDLE_DATA_KEY = ThingKey.create(AbstractReshapeLogic.class);
protected final ThingValueTrackingLogic valueLogic;
protected final Class<R> reshapingThingClass;
private R reshapingThing = null;
private Runnable initialSnapshot = null;
private Point initialPosition = null;
private final Collection<ReshapeHandleThing> handles = Lists.newArrayList();
public AbstractReshapeLogic(IBNAWorld world, Class<R> reshapingThingClass) {
super(world);
this.reshapingThingClass = reshapingThingClass;
valueLogic = logics.addThingLogic(ThingValueTrackingLogic.class);
logics.addThingLogic(DragMoveEventsLogic.class);
}
@Override
public void dispose() {
BNAUtils.checkLock();
untrack();
super.dispose();
}
private void track(R reshapingThing) {
try (Finally bulkChange = model.beginBulkChange()) {
if (this.reshapingThing != null) {
untrack();
}
this.reshapingThing = reshapingThing;
addHandles(this.reshapingThing);
updateHandles(this.reshapingThing);
}
}
private void untrack() {
try (Finally bulkChange = model.beginBulkChange()) {
if (reshapingThing != null) {
removeHandles(reshapingThing);
reshapingThing = null;
}
}
}
abstract protected void addHandles(R reshapingThing);
protected ReshapeHandleThing addHandle(R reshapingThing, ReshapeHandleThing handle, D data) {
handle.set(HANDLE_DATA_KEY, data);
handles.add(handle);
UserEditableUtils.addEditableQualities(handle, IHasMutableReferencePoint.USER_MAY_MOVE);
return handle;
}
private void updateHandles(R reshapingThing) {
for (ReshapeHandleThing handle : handles) {
updateHandle(reshapingThing, handle, handle.get(HANDLE_DATA_KEY));
}
}
abstract protected void updateHandle(R reshapingThing, ReshapeHandleThing handle, D data);
protected void removeHandles(R reshapingThing) {
try (Finally bulkChange = model.beginBulkChange()) {
for (ReshapeHandleThing handle : handles) {
Assemblies.removeRootAndParts(model, handle);
}
handles.clear();
}
}
@Override
public void bnaModelChanged(BNAModelEvent evt) {
BNAUtils.checkLock();
switch (evt.getEventType()) {
case THING_REMOVED:
if (SystemUtils.nullEquals(evt.getTargetThing(), reshapingThing)) {
untrack();
}
break;
case THING_CHANGED:
if (evt.getThingEvent().getPropertyName().equals(IHasSelected.SELECTED_KEY)) {
checkHandles(reshapingThing);
}
else if (SystemUtils.nullEquals(evt.getTargetThing(), reshapingThing)) {
checkHandles(reshapingThing);
}
default:
// do nothing
}
}
protected void checkHandles(R reshapingThing) {
try (Finally bulkChange = model.beginBulkChange()) {
R selectedThing = null;
Collection<IThing> selectedThings = valueLogic.getThings(IHasSelected.SELECTED_KEY, true);
if (selectedThings.size() == 1) {
selectedThing = firstOrNull(selectedThings, reshapingThingClass);
}
if (selectedThing == null || selectedThing != reshapingThing) {
untrack();
}
if (selectedThing != null && selectedThing != reshapingThing) {
track(selectedThing);
}
if (reshapingThing != null) {
updateHandles(reshapingThing);
}
}
}
@Override
public void dragStarted(DragMoveEvent evt) {
BNAUtils.checkLock();
IThing movedThing = evt.getInitialThing();
if (handles.contains(movedThing)) {
D data = movedThing.get(HANDLE_DATA_KEY);
initialSnapshot = takeSnapshot(reshapingThing);
initialPosition = ((ReshapeHandleThing) movedThing).getReferencePoint();
handleMoveStarted(reshapingThing, (ReshapeHandleThing) movedThing, data, evt);
checkHandles(reshapingThing);
}
}
@Override
public void dragMoved(DragMoveEvent evt) {
BNAUtils.checkLock();
IThing movedThing = evt.getInitialThing();
if (handles.contains(movedThing)) {
D data = movedThing.get(HANDLE_DATA_KEY);
handleMoved(reshapingThing, (ReshapeHandleThing) movedThing, data, evt);
checkHandles(reshapingThing);
}
}
@Override
public void dragFinished(DragMoveEvent evt) {
BNAUtils.checkLock();
IThing movedThing = evt.getInitialThing();
if (handles.contains(movedThing)) {
D data = movedThing.get(HANDLE_DATA_KEY);
handleMoveFinished(reshapingThing, (ReshapeHandleThing) movedThing, data, evt);
if (!initialPosition.equals(((ReshapeHandleThing) movedThing).getReferencePoint())) {
final Runnable undoSnapshot = initialSnapshot;
final Runnable redoSnapshot = takeSnapshot(reshapingThing);
BNAOperations.runnable("Reshape", undoSnapshot, redoSnapshot, false);
}
initialSnapshot = null;
}
}
protected void resetHandles() {
if (reshapingThing != null) {
removeHandles(reshapingThing);
addHandles(reshapingThing);
updateHandles(reshapingThing);
}
}
protected void handleMoveStarted(R reshapingThing, ReshapeHandleThing handle, D data, DragMoveEvent evt) {
}
abstract protected void handleMoved(R reshapingThing, ReshapeHandleThing handle, D data, DragMoveEvent evt);
protected void handleMoveFinished(R reshapingThing, ReshapeHandleThing handle, D data, DragMoveEvent evt) {
}
/*
* Record an operation that will restore the thing to its current shape. Used for undo/redo.
*/
abstract protected Runnable takeSnapshot(R reshapingThing);
}