/*******************************************************************************
* Copyright (c) 2014, 2016 itemis AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Matthias Wienand (itemis AG) - initial API and implementation
* Alexander Nyßen (itemis AG) - refactorings
*
*******************************************************************************/
package org.eclipse.gef.mvc.fx.policies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.gef.mvc.fx.models.FocusModel;
import org.eclipse.gef.mvc.fx.operations.AbstractCompositeOperation;
import org.eclipse.gef.mvc.fx.operations.ChangeContentsOperation;
import org.eclipse.gef.mvc.fx.operations.ChangeFocusOperation;
import org.eclipse.gef.mvc.fx.operations.DeselectOperation;
import org.eclipse.gef.mvc.fx.operations.DetachFromContentAnchorageOperation;
import org.eclipse.gef.mvc.fx.operations.ITransactionalOperation;
import org.eclipse.gef.mvc.fx.operations.RemoveContentChildOperation;
import org.eclipse.gef.mvc.fx.operations.ReverseUndoCompositeOperation;
import org.eclipse.gef.mvc.fx.parts.IContentPart;
import org.eclipse.gef.mvc.fx.parts.IRootPart;
import org.eclipse.gef.mvc.fx.parts.IVisualPart;
import org.eclipse.gef.mvc.fx.viewer.IViewer;
import com.google.common.collect.HashMultiset;
import javafx.scene.Node;
/**
* The {@link DeletionPolicy} is an {@link AbstractPolicy} that handles the
* deletion of content.
* <p>
* It handles the deletion of a {@link IContentPart}'s content by initiating the
* removal from the content parent via the {@link ContentPolicy} of the parent
* {@link IContentPart}, as well as the detachment of anchored content elements
* via the {@link ContentPolicy}s of anchored {@link IContentPart}s.
* <p>
* This policy should be registered at an {@link IRootPart}. It depends on
* {@link ContentPolicy}s being registered on all {@link IContentPart}s that are
* affected by the deletion.
*
*
* @author mwienand
* @author anyssen
*
*/
public class DeletionPolicy extends AbstractPolicy {
@Override
protected ITransactionalOperation createOperation() {
ReverseUndoCompositeOperation commit = new ReverseUndoCompositeOperation(
"Delete Content");
// TODO: inline creation of nested operations into createOperation()
IViewer viewer = getHost().getRoot().getViewer();
// unfocus
IContentPart<? extends Node> currentlyFocusedPart = viewer
.getAdapter(FocusModel.class).getFocus();
commit.add(new ChangeFocusOperation(viewer, currentlyFocusedPart));
// deselect
commit.add(new DeselectOperation(viewer,
Collections.<IContentPart<? extends Node>> emptyList()));
// detach anchorages
commit.add(new ReverseUndoCompositeOperation("Detach anchorages"));
// remove children
commit.add(new ReverseUndoCompositeOperation("Remove children"));
return commit;
}
/**
* Deletes the given {@link IContentPart} by removing the
* {@link IContentPart}'s content from the parent {@link IContentPart}'
* content and by detaching the contents of all anchored
* {@link IContentPart}s from the {@link IContentPart}'s content.
*
* @param contentPartToDelete
* The {@link IContentPart} to mark for deletion.
*/
// TODO: offer a bulk operation to improve deselect (can remove all in one
// operation pass)
// this will break if being called one after another without commit
public void delete(IContentPart<? extends Node> contentPartToDelete) {
checkInitialized();
// clear viewer models so that anchoreds are removed
IViewer viewer = getHost().getRoot().getViewer();
getDeselectOperation().getToBeDeselected().add(contentPartToDelete);
FocusModel focusModel = viewer.getAdapter(FocusModel.class);
if (focusModel != null) {
if (focusModel.getFocus() == contentPartToDelete) {
getUnfocusOperation().setNewFocused(null);
}
}
// XXX: Execute operations for changing the viewer models prior to
// detaching anchoreds and removing children, so that no link to the
// viewer is available for the removed part via selection, focus, or
// hover feedback or handles.
locallyExecuteOperation();
// detach all content anchoreds
for (IVisualPart<? extends Node> anchored : HashMultiset
.create(contentPartToDelete.getAnchoredsUnmodifiable())) {
if (anchored instanceof IContentPart) {
ContentPolicy anchoredContentPolicy = anchored
.getAdapter(ContentPolicy.class);
if (anchoredContentPolicy != null) {
anchoredContentPolicy.init();
for (String role : anchored.getAnchoragesUnmodifiable()
.get(contentPartToDelete)) {
anchoredContentPolicy.detachFromContentAnchorage(
contentPartToDelete.getContent(), role);
}
ITransactionalOperation detachFromContentAnchoredOperation = anchoredContentPolicy
.commit();
if (detachFromContentAnchoredOperation != null
&& !detachFromContentAnchoredOperation.isNoOp()) {
getDetachContentAnchoragesOperation()
.add(detachFromContentAnchoredOperation);
}
}
}
}
if (contentPartToDelete.getParent() instanceof IRootPart) {
// remove content from viewer contents
ChangeContentsOperation changeContentsOperation = new ChangeContentsOperation(
viewer);
List<Object> newContents = new ArrayList<>(viewer.getContents());
newContents.remove(contentPartToDelete.getContent());
changeContentsOperation.setNewContents(newContents);
getRemoveContentChildrenOperation().add(changeContentsOperation);
} else {
// remove from content parent
ContentPolicy parentContentPolicy = contentPartToDelete.getParent()
.getAdapter(ContentPolicy.class);
if (parentContentPolicy != null) {
parentContentPolicy.init();
parentContentPolicy
.removeContentChild(contentPartToDelete.getContent());
ITransactionalOperation removeFromParentOperation = parentContentPolicy
.commit();
if (removeFromParentOperation != null
&& !removeFromParentOperation.isNoOp()) {
getRemoveContentChildrenOperation()
.add(removeFromParentOperation);
}
}
}
// TODO: Hover feedback needs to be removed
locallyExecuteOperation();
// verify that all anchoreds were removed
if (!contentPartToDelete.getAnchoredsUnmodifiable().isEmpty()) {
throw new IllegalStateException(
"After deletion of <" + contentPartToDelete
+ "> there are still anchoreds remaining.");
}
}
/**
* Extracts a {@link AbstractCompositeOperation} from the operation created
* by {@link #createOperation()}. The composite operation is used to combine
* individual content change operations.
*
* @return The {@link AbstractCompositeOperation} that is used to combine
* the individual content change operations.
*/
protected AbstractCompositeOperation getCompositeOperation() {
return (AbstractCompositeOperation) getOperation();
}
/**
* Returns the {@link DeselectOperation} used by this {@link DeletionPolicy}
* to deselect the to be deleted parts.
*
* @return The {@link DeselectOperation} that is used.
*/
protected DeselectOperation getDeselectOperation() {
return (DeselectOperation) getCompositeOperation().getOperations()
.get(1);
}
/**
* Returns an {@link AbstractCompositeOperation} that comprises all
* {@link DetachFromContentAnchorageOperation} returned by the delegate
* {@link ContentPolicy}.
*
* @return The {@link AbstractCompositeOperation} that is used for detaching
* anchorages.
*/
private AbstractCompositeOperation getDetachContentAnchoragesOperation() {
return (AbstractCompositeOperation) getCompositeOperation()
.getOperations().get(2);
}
/**
* Returns an {@link AbstractCompositeOperation} that comprises all
* {@link RemoveContentChildOperation} returned by the delegate
* {@link ContentPolicy}.
*
* @return The {@link AbstractCompositeOperation} that is used for removing
* children.
*/
private AbstractCompositeOperation getRemoveContentChildrenOperation() {
return (AbstractCompositeOperation) getCompositeOperation()
.getOperations().get(3);
}
/**
* Returns the {@link ChangeFocusOperation} used by this
* {@link DeletionPolicy} to unfocus the to be deleted parts. .
*
* @return The {@link ChangeFocusOperation} that is used.
*/
protected ChangeFocusOperation getUnfocusOperation() {
return (ChangeFocusOperation) getCompositeOperation().getOperations()
.get(0);
}
}