/******************************************************************************* * Copyright (c) 2000, 2015 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 *******************************************************************************/ package org.eclipse.jface.action; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.runtime.Assert; import org.eclipse.jface.util.Policy; /** * 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 implements IContributionManager { // Internal debug flag. // protected static final boolean DEBUG = false; /** * The list of contribution items. */ private List<IContributionItem> contributions = new ArrayList<>(); /** * Indicates whether the widgets are in sync with the contributions. */ private boolean 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 ContributionManager() { // Do nothing. } @Override public void add(IAction action) { Assert.isNotNull(action, "Action must not be null"); //$NON-NLS-1$ add(new ActionContributionItem(action)); } @Override public void add(IContributionItem item) { Assert.isNotNull(item, "Item must not be null"); //$NON-NLS-1$ if (allowItem(item)) { contributions.add(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, boolean append) { int i; Iterator<IContributionItem> items = contributions.iterator(); for (i = 0; items.hasNext(); i++) { IContributionItem o = items.next(); if (o.isGroupMarker()) { String id = o.getId(); if (id != null && id.equalsIgnoreCase(groupName)) { i++; if (append) { for (; items.hasNext(); i++) { IContributionItem ci = items .next(); if (ci.isGroupMarker()) { break; } } } if (allowItem(item)) { contributions.add(i, item); itemAdded(item); } return; } } } throw new IllegalArgumentException("Group not found: " + groupName);//$NON-NLS-1$ } @Override public void appendToGroup(String groupName, IAction action) { addToGroup(groupName, new ActionContributionItem(action), true); } @Override 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 boolean 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 != null) { size = contributions.size(); } System.out.println(this.toString()); System.out.println(" Number of elements: " + size);//$NON-NLS-1$ int sum = 0; for (int i = 0; i < size; i++) { if (contributions.get(i).isVisible()) { sum++; } } System.out.println(" Number of visible elements: " + sum);//$NON-NLS-1$ System.out.println(" Is dirty: " + isDirty()); //$NON-NLS-1$ } @Override public IContributionItem find(String id) { Iterator<IContributionItem> e = contributions.iterator(); while (e.hasNext()) { IContributionItem item = e.next(); String itemId = item.getId(); if (itemId != null && itemId.equalsIgnoreCase(id)) { return item; } } return null; } @Override public IContributionItem[] getItems() { IContributionItem[] items = new IContributionItem[contributions.size()]; contributions.toArray(items); return items; } /** * 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 */ @Override public IContributionManagerOverrides getOverrides() { if (overrides == null) { overrides = new IContributionManagerOverrides() { @Override public Boolean getEnabled(IContributionItem item) { return null; } @Override public Integer getAccelerator(IContributionItem item) { return null; } @Override public String getAcceleratorText(IContributionItem item) { return null; } @Override public String getText(IContributionItem item) { return null; } @Override public Boolean getVisible(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 boolean 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 = contributions.get(i); String itemId = item.getId(); if (itemId != 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) { return contributions.indexOf(item); } /** * 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( "inserting " + item.getId() + " at " + index); //$NON-NLS-1$ //$NON-NLS-2$ } if (allowItem(item)) { contributions.add(index, item); itemAdded(item); } } @Override public void insertAfter(String ID, IAction action) { insertAfter(ID, new ActionContributionItem(action)); } @Override public void insertAfter(String ID, IContributionItem item) { IContributionItem ci = find(ID); if (ci == null) { throw new IllegalArgumentException("can't find ID" + ID);//$NON-NLS-1$ } int ix = contributions.indexOf(ci); if (ix >= 0) { // System.out.println("insert after: " + ix); if (allowItem(item)) { contributions.add(ix + 1, item); itemAdded(item); } } } @Override public void insertBefore(String ID, IAction action) { insertBefore(ID, new ActionContributionItem(action)); } @Override public void insertBefore(String ID, IContributionItem item) { IContributionItem ci = find(ID); if (ci == null) { throw new IllegalArgumentException("can't find ID " + ID);//$NON-NLS-1$ } int ix = contributions.indexOf(ci); if (ix >= 0) { // System.out.println("insert before: " + ix); if (allowItem(item)) { contributions.add(ix, item); itemAdded(item); } } } @Override public boolean isDirty() { if (isDirty) { return true; } if (hasDynamicItems()) { for (IContributionItem item : contributions) { if (item.isDirty()) { return true; } } } return false; } @Override public boolean isEmpty() { return contributions.isEmpty(); } /** * 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--; } } @Override public void markDirty() { setDirty(true); } @Override public void prependToGroup(String groupName, IAction action) { addToGroup(groupName, new ActionContributionItem(action), false); } @Override public void prependToGroup(String groupName, IContributionItem item) { addToGroup(groupName, item, false); } @Override public IContributionItem remove(String ID) { IContributionItem ci = find(ID); if (ci == null) { return null; } return remove(ci); } @Override public IContributionItem remove(IContributionItem item) { if (contributions.remove(item)) { itemRemoved(item); return item; } return null; } @Override public void removeAll() { IContributionItem[] items = getItems(); contributions.clear(); for (IContributionItem item : items) { 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 org.eclipse.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 boolean replaceItem(final String identifier, final IContributionItem replacementItem) { if (identifier == null) { return false; } final int index = indexOf(identifier); if (index < 0) { return false; // couldn't find the item. } // Remove the old item. final IContributionItem oldItem = contributions .get(index); itemRemoved(oldItem); // Add the new item. contributions.set(index, replacementItem); itemAdded(replacementItem); // throws NPE if (replacementItem == null) // Go through and remove duplicates. for (int i = contributions.size() - 1; i > index; i--) { IContributionItem item = contributions.get(i); if ((item != null) && (identifier.equals(item.getId()))) { if (Policy.TRACE_TOOLBAR) { System.out .println("Removing duplicate on replace: " + identifier); //$NON-NLS-1$ } contributions.remove(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(boolean 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 (IContributionItem item : items) { if (allowItem(item)) { contributions.add(item); } } } }