view dwtx/core/commands/operations/TriggeredOperations.d @ 104:04b47443bb01

Reworked the collection uses to make use of a wrapper collection that is compatible to the Java Collections. These new wrappers now use the tango.util.containers instead of the tango.util.collections.
author Frank Benoit <benoit@tionex.de>
date Thu, 07 Aug 2008 15:01:33 +0200
parents ea8ff534f622
children
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2005, 2006 IBM Corporation 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:
 *     IBM Corporation - initial API and implementation
 * Port to the D programming language:
 *     Frank Benoit <benoit@tionex.de>
 *******************************************************************************/
module dwtx.core.commands.operations.TriggeredOperations;

import dwtx.core.commands.ExecutionException;
import dwtx.core.runtime.IAdaptable;
import dwtx.core.runtime.IProgressMonitor;
import dwtx.core.runtime.IStatus;
import dwtx.core.runtime.OperationCanceledException;
import dwtx.core.runtime.Status;

import dwtx.core.commands.operations.AbstractOperation;
import dwtx.core.commands.operations.ICompositeOperation;
import dwtx.core.commands.operations.IAdvancedUndoableOperation;
import dwtx.core.commands.operations.IContextReplacingOperation;
import dwtx.core.commands.operations.IUndoableOperation;
import dwtx.core.commands.operations.IOperationHistory;
import dwtx.core.commands.operations.IUndoContext;
import dwtx.core.commands.operations.OperationHistoryEvent;

import dwt.dwthelper.utils;
import dwtx.dwtxhelper.Collection;

/**
 * Triggered operations are a specialized implementation of a composite
 * operation that keeps track of operations triggered by the execution of some
 * primary operation. The composite knows which operation was the trigger for
 * subsequent operations, and adds all triggered operations as children. When
 * execution, undo, or redo is performed, only the triggered operation is
 * executed, undone, or redone if it is still present. If the trigger is removed
 * from the triggered operations, then the child operations will replace the
 * triggered operations in the history.
 * <p>
 * This class may be instantiated by clients.
 * </p>
 *
 * @since 3.1
 */
public final class TriggeredOperations : AbstractOperation,
        ICompositeOperation, IAdvancedUndoableOperation,
        IContextReplacingOperation {

    private IUndoableOperation triggeringOperation;

    private IOperationHistory history;

    private List children;

    /**
     * Construct a composite triggered operations using the specified undoable
     * operation as the trigger. Use the label of this trigger as the label of
     * the operation.
     *
     * @param operation
     *            the operation that will trigger other operations.
     * @param history
     *            the operation history containing the triggered operations.
     */
    public this(IUndoableOperation operation,
            IOperationHistory history) {
        super(operation.getLabel());
        children = new ArrayList();
        triggeringOperation = operation;
        recomputeContexts();
        this.history = history;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#add(dwtx.core.commands.operations.IUndoableOperation)
     */
    public void add(IUndoableOperation operation) {
        children.add(cast(Object)operation);
        recomputeContexts();
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#remove(dwtx.core.commands.operations.IUndoableOperation)
     */
    public void remove(IUndoableOperation operation) {
        if (operation is triggeringOperation) {
            // the triggering operation is being removed, so we must replace
            // this composite with its individual triggers.
            triggeringOperation = null;
            // save the children before replacing the operation, since this
            // operation will be disposed as part of replacing it. We don't want
            // the children to be disposed since they are to replace this
            // operation.
            List childrenToRestore = new ArrayList(children);
            children = new ArrayList(0);
            recomputeContexts();
            operation.dispose();
            // now replace the triggering operation
            history.replaceOperation(this, arraycast!(IUndoableOperation)(childrenToRestore.toArray()));
        } else {
            children.remove(cast(Object)operation);
            operation.dispose();
            recomputeContexts();
        }
    }

    /**
     * Remove the specified context from the receiver. This method is typically
     * invoked when the history is being flushed for a certain context. In the
     * case of triggered operations, if the only context for the triggering
     * operation is being removed, then the triggering operation must be
     * replaced in the operation history with the atomic operations that it
     * triggered. If the context being removed is not the only context for the
     * triggering operation, the triggering operation will remain, and the
     * children will each be similarly checked.
     *
     * @param context
     *            the undo context being removed from the receiver.
     */
    public override void removeContext(IUndoContext context) {

        bool recompute = false;
        // first check to see if we are removing the only context of the
        // triggering operation
        if (triggeringOperation !is null
                && triggeringOperation.hasContext(context)) {
            if (triggeringOperation.getContexts().length is 1) {
                remove(triggeringOperation);
                return;
            }
            triggeringOperation.removeContext(context);
            recompute = true;
        }
        // the triggering operation remains, check all the children
        auto toBeRemoved = new ArrayList();
        for (int i = 0; i < children.size(); i++) {
            IUndoableOperation child = cast(IUndoableOperation) children.get(i);
            if (child.hasContext(context)) {
                if (child.getContexts().length is 1) {
                    toBeRemoved.add(cast(Object)child);
                } else {
                    child.removeContext(context);
                }
                recompute = true;
            }
        }
        for (int i = 0; i < toBeRemoved.size(); i++) {
            remove(cast(IUndoableOperation) toBeRemoved.get(i));
        }
        if (recompute) {
            recomputeContexts();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#execute(dwtx.core.runtime.IProgressMonitor,
     *      dwtx.core.runtime.IAdaptable)
     */
    public override IStatus execute(IProgressMonitor monitor, IAdaptable info) {
        if (triggeringOperation !is null) {
            history.openOperation(this, IOperationHistory.EXECUTE);
            try {
                IStatus status = triggeringOperation.execute(monitor, info);
                history.closeOperation(status.isOK(), false,
                        IOperationHistory.EXECUTE);
                return status;
            } catch (ExecutionException e) {
                history.closeOperation(false, false, IOperationHistory.EXECUTE);
                throw e;
            } catch (RuntimeException e) {
                history.closeOperation(false, false, IOperationHistory.EXECUTE);
                throw e;
            }

        }
        return IOperationHistory.OPERATION_INVALID_STATUS;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#redo(dwtx.core.runtime.IProgressMonitor,
     *      dwtx.core.runtime.IAdaptable)
     */
    public override IStatus redo(IProgressMonitor monitor, IAdaptable info) {
        if (triggeringOperation !is null) {
            history.openOperation(this, IOperationHistory.REDO);
            List childrenToRestore = new ArrayList(children);
            try {
                removeAllChildren();
                IStatus status = triggeringOperation.redo(monitor, info);
                if (!status.isOK()) {
                    children = childrenToRestore;
                }
                history.closeOperation(status.isOK(), false,
                        IOperationHistory.REDO);
                return status;
            } catch (ExecutionException e) {
                children = childrenToRestore;
                history.closeOperation(false, false, IOperationHistory.REDO);
                throw e;
            } catch (RuntimeException e) {
                children = childrenToRestore;
                history.closeOperation(false, false, IOperationHistory.REDO);
                throw e;
            }
        }
        return IOperationHistory.OPERATION_INVALID_STATUS;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#undo(dwtx.core.runtime.IProgressMonitor,
     *      dwtx.core.runtime.IAdaptable)
     */
    public override IStatus undo(IProgressMonitor monitor, IAdaptable info) {
        if (triggeringOperation !is null) {
            history.openOperation(this, IOperationHistory.UNDO);
            List childrenToRestore = new ArrayList(children);
            try {
                removeAllChildren();
                IStatus status = triggeringOperation.undo(monitor, info);
                if (!status.isOK()) {
                    children = childrenToRestore;
                }
                history.closeOperation(status.isOK(), false,
                        IOperationHistory.UNDO);
                return status;
            } catch (ExecutionException e) {
                children = childrenToRestore;
                history.closeOperation(false, false, IOperationHistory.UNDO);
                throw e;
            } catch (RuntimeException e) {
                children = childrenToRestore;
                history.closeOperation(false, false, IOperationHistory.UNDO);
                throw e;
            }
        }
        return IOperationHistory.OPERATION_INVALID_STATUS;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#canUndo()
     */
    public override bool canUndo() {
        if (triggeringOperation !is null) {
            return triggeringOperation.canUndo();
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#canExecute()
     */
    public override bool canExecute() {
        if (triggeringOperation !is null) {
            return triggeringOperation.canExecute();
        }
        return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IUndoableOperation#canRedo()
     */
    public override bool canRedo() {
        if (triggeringOperation !is null) {
            return triggeringOperation.canRedo();
        }
        return false;
    }

    /*
     * Dispose all operations in the receiver.
     */
    public override void dispose() {
        for (int i = 0; i < children.size(); i++) {
            (cast(IUndoableOperation)children.get(i)).dispose();
        }
        if (triggeringOperation !is null) {
            triggeringOperation.dispose();
        }
    }

    /*
     * Recompute contexts in light of some change in the children
     */
    private void recomputeContexts() {
        ArrayList allContexts = new ArrayList();
        if (triggeringOperation !is null) {
            IUndoContext[] contexts = triggeringOperation.getContexts();
            for (int i = 0; i < contexts.length; i++) {
                allContexts.add(cast(Object)contexts[i]);
            }
        }
        for (int i = 0; i < children.size(); i++) {
            IUndoContext[] contexts = (cast(IUndoableOperation)children.get(i))
                    .getContexts();
            for (int j = 0; j < contexts.length; j++) {
                if (!allContexts.contains(cast(Object)contexts[j])) {
                    allContexts.add(cast(Object)contexts[j]);
                }
            }
        }
        contexts = allContexts;

    }

    /*
     * Remove all non-triggering children
     */
    private void removeAllChildren() {
        IUndoableOperation[] nonTriggers = arraycast!(IUndoableOperation)(children
                .toArray());
        for (int i = 0; i < nonTriggers.length; i++) {
            children.remove(cast(Object)nonTriggers[i]);
            nonTriggers[i].dispose();
        }
    }

    /**
     * Return the operation that triggered the other operations in this
     * composite.
     *
     * @return the IUndoableOperation that triggered the other children.
     */
    public IUndoableOperation getTriggeringOperation() {
        return triggeringOperation;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IAdvancedModelOperation#getAffectedObjects()
     */
    public Object[] getAffectedObjects() {
        if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation  ) {
            return trg
                    .getAffectedObjects();
        }
        return null;
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IAdvancedModelOperation#aboutToNotify(dwtx.core.commands.operations.OperationHistoryEvent)
     */
    public void aboutToNotify(OperationHistoryEvent event) {
        if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) {
            trg.aboutToNotify(event);
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IAdvancedUndoableOperation#computeUndoableStatus(dwtx.core.runtime.IProgressMonitor)
     */
    public IStatus computeUndoableStatus(IProgressMonitor monitor) {
        if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) {
            try {
                return trg.computeUndoableStatus(monitor);
            } catch (OperationCanceledException e) {
                return Status.CANCEL_STATUS;
            }
        }
        return Status.OK_STATUS;

    }

    /*
     * (non-Javadoc)
     *
     * @see dwtx.core.commands.operations.IAdvancedUndoableOperation#computeRedoableStatus(dwtx.core.runtime.IProgressMonitor)
     */
    public IStatus computeRedoableStatus(IProgressMonitor monitor) {
        if ( auto trg = cast(IAdvancedUndoableOperation)triggeringOperation ) {
            try {
                return trg.computeRedoableStatus(monitor);
            } catch (OperationCanceledException e) {
                return Status.CANCEL_STATUS;
            }
        }
        return Status.OK_STATUS;

    }

    /**
     * Replace the undo context of the receiver with the provided replacement
     * undo context. In the case of triggered operations, all contained
     * operations are checked and any occurrence of the original context is
     * replaced with the new undo context.
     * <p>
     * This message has no effect if the original undo context is not present in
     * the receiver.
     *
     * @param original
     *            the undo context which is to be replaced
     * @param replacement
     *            the undo context which is replacing the original
     * @since 3.2
     */
    public void replaceContext(IUndoContext original, IUndoContext replacement) {

        // first check the triggering operation
        if (triggeringOperation !is null
                && triggeringOperation.hasContext(original)) {
            if ( auto trg = cast(IContextReplacingOperation)triggeringOperation ) {
                trg.replaceContext(original, replacement);
            } else {
                triggeringOperation.removeContext(original);
                triggeringOperation.addContext(replacement);
            }
        }
        // Now check all the children
        for (int i = 0; i < children.size(); i++) {
            IUndoableOperation child = cast(IUndoableOperation)children.get(i);
            if (child.hasContext(original)) {
                if ( auto c = cast(IContextReplacingOperation)child ) {
                    c.replaceContext(
                            original, replacement);
                } else {
                    child.removeContext(original);
                    child.addContext(replacement);
                }
            }
        }
        recomputeContexts();
    }

    /**
     * Add the specified context to the operation. Overridden in
     * TriggeredOperations to add the specified undo context to the triggering
     * operation.
     *
     * @param context
     *            the context to be added
     *
     * @since 3.2
     */
    public override void addContext(IUndoContext context) {
        if (triggeringOperation !is null) {
            triggeringOperation.addContext(context);
            recomputeContexts();
        }
    }

}