view dwtx/jface/action/ContributionManager.d @ 90:7ffeace6c47f

Update 3.4M7 to 3.4
author Frank Benoit <benoit@tionex.de>
date Sun, 06 Jul 2008 23:30:07 +0200
parents 5df4896124c7
children 04b47443bb01
line wrap: on
line source

/*******************************************************************************
 * Copyright (c) 2000, 2008 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.jface.action.ContributionManager;

import dwtx.jface.action.ActionContributionItem;

import dwtx.jface.action.IContributionManager;
import dwtx.jface.action.IContributionItem;
import dwtx.jface.action.IContributionManagerOverrides;
import dwtx.jface.action.IAction;

import tango.util.collection.ArraySeq;
import tango.util.collection.model.Seq;

import dwtx.core.runtime.Assert;
import dwtx.jface.util.Policy;

import dwt.dwthelper.utils;
import tango.io.Stdout;
import tango.core.Exception;
import tango.text.convert.Format;

/**
 * Abstract base class for all contribution managers, and standard
 * implementation of <code>IContributionManager</code>. This class provides
 * functionality common across the specific managers defined by this framework.
 * <p>
 * This class maintains a list of contribution items and a dirty flag, both as
 * internal state. In addition to providing implementations of most
 * <code>IContributionManager</code> methods, this class automatically
 * coalesces adjacent separators, hides beginning and ending separators, and
 * deals with dynamically changing sets of contributions. When the set of
 * contributions does change dynamically, the changes are propagated to the
 * control via the <code>update</code> method, which subclasses must
 * implement.
 * </p>
 * <p>
 * Note: A <code>ContributionItem</code> cannot be shared between different
 * <code>ContributionManager</code>s.
 * </p>
 */
public abstract class ContributionManager : IContributionManager {

    // Internal debug flag.
    // protected static final bool DEBUG = false;

    /**
     * The list of contribution items.
     */
    private Seq!(IContributionItem) contributions;

    /**
     * Indicates whether the widgets are in sync with the contributions.
     */
    private bool isDirty_ = true;

    /**
     * Number of dynamic contribution items.
     */
    private int dynamicItems = 0;

    /**
     * The overrides for items of this manager
     */
    private IContributionManagerOverrides overrides;

    /**
     * Creates a new contribution manager.
     */
    protected this() {
        contributions = new ArraySeq!(IContributionItem);
        // Do nothing.
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void add(IAction action) {
        Assert.isNotNull( cast(Object)action, "Action must not be null"); //$NON-NLS-1$
        add(new ActionContributionItem(action));
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void add(IContributionItem item) {
        Assert.isNotNull( cast(Object)item, "Item must not be null"); //$NON-NLS-1$
        if (allowItem(item)) {
            contributions.append(item);
            itemAdded(item);
        }
    }

    /**
     * Adds a contribution item to the start or end of the group with the given
     * name.
     *
     * @param groupName
     *            the name of the group
     * @param item
     *            the contribution item
     * @param append
     *            <code>true</code> to add to the end of the group, and
     *            <code>false</code> to add the beginning of the group
     * @exception IllegalArgumentException
     *                if there is no group with the given name
     */
    private void addToGroup(String groupName, IContributionItem item,
            bool append) {
        int i;
        auto items = contributions.elements();
        for (i = 0; items.more(); i++) {
            IContributionItem o = cast(IContributionItem) items.get();
            if (o.isGroupMarker()) {
                String id = o.getId();
                if (id !is null && id.equalsIgnoreCase(groupName)) {
                    i++;
                    if (append) {
                        for (; items.more(); i++) {
                            IContributionItem ci = cast(IContributionItem) items
                                    .get();
                            if (ci.isGroupMarker()) {
                                break;
                            }
                        }
                    }
                    if (allowItem(item)) {
                        //TODO: does this corrupt the iterator?
                        contributions.addAt(i, item);
                        itemAdded(item);
                    }
                    return;
                }
            }
        }
        throw new IllegalArgumentException("Group not found: " ~ groupName);//$NON-NLS-1$
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void appendToGroup(String groupName, IAction action) {
        addToGroup(groupName, new ActionContributionItem(action), true);
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void appendToGroup(String groupName, IContributionItem item) {
        addToGroup(groupName, item, true);
    }

    /**
     * This method allows subclasses of <code>ContributionManager</code> to
     * prevent certain items in the contributions list.
     * <code>ContributionManager</code> will either block or allow an addition
     * based on the result of this method call. This can be used to prevent
     * duplication, for example.
     *
     * @param itemToAdd
     *            The contribution item to be added; may be <code>null</code>.
     * @return <code>true</code> if the addition should be allowed;
     *         <code>false</code> otherwise. The default implementation allows
     *         all items.
     * @since 3.0
     */
    protected bool allowItem(IContributionItem itemToAdd) {
        return true;
    }

    /**
     * Internal debug method for printing statistics about this manager to
     * <code>System.out</code>.
     */
    protected void dumpStatistics() {
        int size = 0;
        if (contributions !is null) {
            size = contributions.size();
        }

        Stdout.formatln(this.toString());
        Stdout.formatln("   Number of elements: {}", size);//$NON-NLS-1$
        int sum = 0;
        for (int i = 0; i < size; i++) {
            if ((cast(IContributionItem) contributions.get(i)).isVisible()) {
                sum++;
            }
        }
        Stdout.formatln("   Number of visible elements: {}", sum);//$NON-NLS-1$
        Stdout.formatln("   Is dirty: {}", isDirty()); //$NON-NLS-1$
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public IContributionItem find(String id) {
        auto e = contributions.elements();
        while (e.more()) {
            IContributionItem item = cast(IContributionItem) e.get();
            String itemId = item.getId();
            if (itemId !is null && itemId.equalsIgnoreCase(id)) {
                return item;
            }
        }
        return null;
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public IContributionItem[] getItems() {
        return contributions.toArray();
    }

    /**
     * Return the number of contributions in this manager.
     *
     * @return the number of contributions in this manager
     * @since 3.3
     */
    public int getSize() {
        return contributions.size();
    }

    /**
     * The <code>ContributionManager</code> implementation of this method
     * declared on <code>IContributionManager</code> returns the current
     * overrides. If there is no overrides it lazily creates one which overrides
     * no item state.
     *
     * @since 2.0
     */
    public IContributionManagerOverrides getOverrides() {
        if (overrides is null) {
            overrides = new class IContributionManagerOverrides {
                public Boolean getEnabled(IContributionItem item) {
                    return null;
                }

                public ValueWrapperInt getAccelerator(IContributionItem item) {
                    return null;
                }

                public String getAcceleratorText(IContributionItem item) {
                    return null;
                }

                public String getText(IContributionItem item) {
                    return null;
                }
            };
        }
        return overrides;
    }

    /**
     * Returns whether this contribution manager contains dynamic items. A
     * dynamic contribution item contributes items conditionally, dependent on
     * some internal state.
     *
     * @return <code>true</code> if this manager contains dynamic items, and
     *         <code>false</code> otherwise
     */
    protected bool hasDynamicItems() {
        return (dynamicItems > 0);
    }

    /**
     * Returns the index of the item with the given id.
     *
     * @param id
     *            The id of the item whose index is requested.
     *
     * @return <code>int</code> the index or -1 if the item is not found
     */
    public int indexOf(String id) {
        for (int i = 0; i < contributions.size(); i++) {
            IContributionItem item = cast(IContributionItem) contributions.get(i);
            String itemId = item.getId();
            if (itemId !is null && itemId.equalsIgnoreCase(id)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Returns the index of the object in the internal structure. This is
     * different from <code>indexOf(String id)</code> since some contribution
     * items may not have an id.
     *
     * @param item
     *            The contribution item
     * @return the index, or -1 if the item is not found
     * @since 3.0
     */
    protected int indexOf(IContributionItem item) {
        int res = -1;
        int idx = 0;
        foreach( e; contributions ){
            if( e == item ) {
                res = idx;
                break;
            }
            idx++;
        }
        return res;
    }

    /**
     * Insert the item at the given index.
     *
     * @param index
     *            The index to be used for insertion
     * @param item
     *            The item to be inserted
     */
    public void insert(int index, IContributionItem item) {
        if (index > contributions.size()) {
            throw new IndexOutOfBoundsException( Format(
                    "inserting {} at {}", item.getId(), index)); //$NON-NLS-1$ //$NON-NLS-2$
        }
        if (allowItem(item)) {
            contributions.addAt(index, item);
            itemAdded(item);
        }
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void insertAfter(String ID, IAction action) {
        insertAfter(ID, new ActionContributionItem(action));
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void insertAfter(String ID, IContributionItem item) {
        IContributionItem ci = find(ID);
        if (ci is null) {
            throw new IllegalArgumentException(Format("can't find ID{}", ID));//$NON-NLS-1$
        }
        int ix = SeqIndexOf!(IContributionItem)( contributions, ci );
        if (ix >= 0) {
            // System.out.println("insert after: " + ix);
            if (allowItem(item)) {
                contributions.addAt(ix + 1, item);
                itemAdded(item);
            }
        }
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void insertBefore(String ID, IAction action) {
        insertBefore(ID, new ActionContributionItem(action));
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void insertBefore(String ID, IContributionItem item) {
        IContributionItem ci = find(ID);
        if (ci is null) {
            throw new IllegalArgumentException(Format("can't find ID {}", ID));//$NON-NLS-1$
        }
        int ix = SeqIndexOf!(IContributionItem)(contributions,ci);
        if (ix >= 0) {
            // System.out.println("insert before: " + ix);
            if (allowItem(item)) {
                contributions.addAt(ix, item);
                itemAdded(item);
            }
        }
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public bool isDirty() {
        if (isDirty_) {
            return true;
        }
        if (hasDynamicItems()) {
            foreach( e; contributions ){
                IContributionItem item = cast(IContributionItem) e;
                if (item.isDirty()) {
                    return true;
                }
            }
        }
        return false;
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public bool isEmpty() {
        return contributions.drained();
    }

    /**
     * The given item was added to the list of contributions. Marks the manager
     * as dirty and updates the number of dynamic items, and the memento.
     *
     * @param item
     *            the item to be added
     *
     */
    protected void itemAdded(IContributionItem item) {
        item.setParent(this);
        markDirty();
        if (item.isDynamic()) {
            dynamicItems++;
        }
    }

    /**
     * The given item was removed from the list of contributions. Marks the
     * manager as dirty and updates the number of dynamic items.
     *
     * @param item
     *            remove given parent from list of contributions
     */
    protected void itemRemoved(IContributionItem item) {
        item.setParent(null);
        markDirty();
        if (item.isDynamic()) {
            dynamicItems--;
        }
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void markDirty() {
        setDirty(true);
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void prependToGroup(String groupName, IAction action) {
        addToGroup(groupName, new ActionContributionItem(action), false);
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void prependToGroup(String groupName, IContributionItem item) {
        addToGroup(groupName, item, false);
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public IContributionItem remove(String ID) {
        IContributionItem ci = find(ID);
        if (ci is null) {
            return null;
        }
        return remove(ci);
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public IContributionItem remove(IContributionItem item) {
        bool contained = contributions.contains(item);
        contributions.remove(item);
        if (contained) {
            itemRemoved(item);
            return item;
        }
        return null;
    }

    /*
     * (non-Javadoc) Method declared on IContributionManager.
     */
    public void removeAll() {
        IContributionItem[] items = getItems();
        contributions.clear();
        for (int i = 0; i < items.length; i++) {
            IContributionItem item = items[i];
            itemRemoved(item);
        }
        dynamicItems = 0;
        markDirty();
    }

    /**
     * Replaces the item of the given identifier with another contribution item.
     * This can be used, for example, to replace large contribution items with
     * placeholders to avoid memory leaks. If the identifier cannot be found in
     * the current list of items, then this does nothing. If multiple
     * occurrences are found, then the replacement items is put in the first
     * position and the other positions are removed.
     *
     * @param identifier
     *            The identifier to look for in the list of contributions;
     *            should not be <code>null</code>.
     * @param replacementItem
     *            The contribution item to replace the old item; must not be
     *            <code>null</code>. Use
     *            {@link dwtx.jface.action.ContributionManager#remove(java.lang.String) remove}
     *            if that is what you want to do.
     * @return <code>true</code> if the given identifier can be; <code>
     * @since 3.0
     */
    public bool replaceItem(String identifier,
            IContributionItem replacementItem) {
        if (identifier is null) {
            return false;
        }

        int index = indexOf(identifier);
        if (index < 0) {
            return false; // couldn't find the item.
        }

        // Remove the old item.
        IContributionItem oldItem = cast(IContributionItem) contributions
                .get(index);
        itemRemoved(oldItem);

        // Add the new item.
        contributions.replaceAt(index, replacementItem);
        itemAdded(replacementItem); // throws NPE if (replacementItem is null)

        // Go through and remove duplicates.
        for (int i = contributions.size() - 1; i > index; i--) {
            IContributionItem item = cast(IContributionItem) contributions.get(i);
            if ((item !is null) && (identifier.equals(item.getId()))) {
                if (Policy.TRACE_TOOLBAR) {
                    Stdout.formatln("Removing duplicate on replace: {}", identifier); //$NON-NLS-1$
                }
                contributions.removeAt(i);
                itemRemoved(item);
            }
        }

        return true; // success
    }

    /**
     * Sets whether this manager is dirty. When dirty, the list of contributions
     * is not accurately reflected in the corresponding widgets.
     *
     * @param dirty
     *            <code>true</code> if this manager is dirty, and
     *            <code>false</code> if it is up-to-date
     */
    protected void setDirty(bool dirty) {
        isDirty_ = dirty;
    }

    /**
     * Sets the overrides for this contribution manager
     *
     * @param newOverrides
     *            the overrides for the items of this manager
     * @since 2.0
     */
    public void setOverrides(IContributionManagerOverrides newOverrides) {
        overrides = newOverrides;
    }

    /**
     * An internal method for setting the order of the contribution items.
     *
     * @param items
     *            the contribution items in the specified order
     * @since 3.0
     */
    protected void internalSetItems(IContributionItem[] items) {
        contributions.clear();
        for (int i = 0; i < items.length; i++) {
            if (allowItem(items[i])) {
                contributions.append(items[i]);
            }
        }
    }
}