/****************************************************************************** * Copyright (c) 2002, 2005 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.gmf.runtime.common.ui.dialogs; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Vector; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Control; /** * An element that contains a hint, a serializable String ID, a String label, * and an ImageDescriptor label. SeletableElements keep track of its parent * which may or may not be null and a list of children which may be empty. * This allows SelectableElement objects to be optionally represented in tree * viewers. SelectableElements also keep track of their SelectedType, which * describes if the element is selected, unselected, or set to leave. * Therefore, SelectableElement objects typically correspond to elements in the * UI. * * <P>This class contains public convenience methods. For eaxmple, it * includes methods to find SelectableElement objects or their hints based on * their String ID and a method to make copies of SelectableElement objects. * * <P>The SelectableElement class is used in at least 3 places, Show Related * Elements Show Hide Relationships, and Browse Diagrams. * * @author Wayne Diu, wdiu */ public class SelectableElement { /** * Unique identifier for this selectable element. */ private String id; /** * String name of the element */ private String name; /** * Icon for the element */ private ImageDescriptor icon; /** * This element's children */ private Vector children; /** * True if element was checked by the user, false if it wasn't */ private SelectedType selectedType; /** * This element's parent */ private SelectableElement parent; /** * Hint for checking what the type of this element is */ private Object hint; /** * Adds a child to this element * * @param element * the child to add */ public void addChild(SelectableElement element) { children.add(element); element.setParent(this); } /** * Remove all children of this SelectableElement */ public void removeAllChildren() { Iterator i = children.iterator(); while (i.hasNext()) { ((SelectableElement) i.next()).setParent(null); } children = new Vector(); } /** * Constructor to make a new SelectableElement * * @param aName * the String name of the element * @param anIcon * the icon Image for the element * @param aHint * the element type * @deprecated Use the other constructor. */ public SelectableElement(String aName, ImageDescriptor anIcon, Object aHint) { // For now, we will use the name as a unique identifier. this(aName, aName, anIcon, aHint); } /** * Constructor to make a new SelectableElement. * * @param anID * A non-language specific unique identifier for this selectable * element. * @param aName * A user-presentable name for this selectable element. * @param anIcon * The icon image for the element. * @param aHint * A hint associated with the selection of this element. */ public SelectableElement(String anID, String aName, ImageDescriptor anIcon, Object aHint) { children = new Vector(); this.id = anID; this.name = aName; this.icon = anIcon; this.hint = aHint; selectedType = SelectedType.UNSELECTED; } /** * Returns the number of children * * @return int with the number of children this element has */ public int getNumberOfChildren() { return children.size(); } /** * Returns a child * * @param i * with the index of the child of this element * @return SelectableElement which is child i of this element */ public SelectableElement getChild(int i) { assert (i >= 0 && i < children.size()); return (SelectableElement) children.get(i); } /** * Returns the icon of this element to display to the user. * * @return Image with icon of this element to display to the user */ public ImageDescriptor getIconImageDescriptor() { return icon; } /** * Returns the name of this element to display to the user. * * @return String with name of this element to display to the user */ public String getName() { return name; } /** * Sets the icon of this element to display to the user. * * @param anIcon * The icon to set */ public void setIconImageDescriptor(ImageDescriptor anIcon) { this.icon = anIcon; } /** * Sets the name of this element to display to the user. * * @param aName * The name to set */ public void setName(String aName) { this.name = aName; } /** * Returns the parent of this element. * * @return SelectableElement */ public SelectableElement getParent() { return parent; } /** * Sets the parent of this element. * * @param aParent * The parent to set */ public void setParent(SelectableElement aParent) { this.parent = aParent; } /** * Returns the children of this element as an array * * @return SelectableElement[] array of this element's children. */ public SelectableElement[] getChildren() { SelectableElement elements[] = new SelectableElement[getNumberOfChildren()]; for (int i = 0; i < getNumberOfChildren(); i++) { elements[i] = (SelectableElement) children.elementAt(i); } return elements; } /** * Returns if the element was selected. * * @return selectedType from the SelectedType EnumeratedType */ public SelectedType getSelectedType() { return selectedType; } /** * Sets whether or not the element is selected. For example, if the element * is checked in the interface. * * @param aSelectedType * from the SelectedType EnumeratedType */ public void setSelectedType(SelectedType aSelectedType) { this.selectedType = aSelectedType; } /** * Returns the hint, which is an Object. This could be subclassed if type * safety is required. * * @return the element type */ public Object getHint() { return hint; } /** * Sets the SelectedType for a SelectableElement and its children * * @param parent * sets the SelectedType for this SelectableElement * @param selectedType * the SelectedType to set for the SelectableElement and its * children. */ public static void setSelectedTypeForSelecteableElementAndChildren( SelectableElement parent, SelectedType selectedType) { for (int i = 0; i < parent.getNumberOfChildren(); i++) { setSelectedTypeForSelecteableElementAndChildren(parent.getChild(i), selectedType); } parent.setSelectedType(selectedType); } /** * Sets the SelectedType for a SelectableElement and its children that match * the IDs in the List of IDs. * * @param parent * sets the SelectedType for this SelectableElement * @param selectedType * the SelectedType to set for the SelectableElement and its * children. * @param list * List of IDs, not hints */ public static void setSelectedTypeForMatchingSelecteableElementAndChildren( SelectableElement parent, SelectedType selectedType, List list) { for (int i = 0; i < parent.getNumberOfChildren(); i++) { setSelectedTypeForMatchingSelecteableElementAndChildren(parent .getChild(i), selectedType, list); } if (list.contains(parent.getId())) { setSelectedTypeForSelecteableElementAndChildren(parent, selectedType); } } /** * Calculates the longest string length of this element's children for the * text that will be displayed in the control. This method only works for * root SelectableElements. * * @param selectableElement * the SelectableElement to calculate the longest string length. * Also looks at its children. * @param control * the control with the font to use when calculating the font * size * @return int with the string length in pixels */ public static int calculateLongestStringLength( SelectableElement selectableElement, Control control) { int INITIAL_LONGEST_STRING_LENGTH = 0; int INITIAL_ITERATION_LEVEL = -1; //there is a fake element assert (selectableElement.getParent() == null); GC gc = new GC(control); int longestStringLength = calculateLongestStringLength( selectableElement, gc, INITIAL_LONGEST_STRING_LENGTH, INITIAL_ITERATION_LEVEL); gc.dispose(); return longestStringLength; } /** * Calculates the longest string length of this element's children for the * text that will be displayed. * * @param selectableElement * the SelectableElement to calculate the longest string length. * Also looks at its children. * @param gc * the GC to use when calculating the font size * @param longestStringLength * the longest string length for the selectableElement and its * children so far. * @param iterationLevel * how many levels we have gone to keep track of the indents of * the icons * @return int with the string length in pixels */ private static int calculateLongestStringLength( SelectableElement selectableElement, GC gc, int longestStringLength, int iterationLevel) { int ICON_WIDTH = 32; //we don'internationalize these icons, checkbox is 16 and image is 16 Point size = gc.textExtent(selectableElement.getName()); if (size.x + (iterationLevel * ICON_WIDTH) > longestStringLength) longestStringLength = size.x + (iterationLevel * ICON_WIDTH); for (int i = 0; i < selectableElement.getNumberOfChildren(); i++) { longestStringLength = calculateLongestStringLength( selectableElement.getChild(i), gc, longestStringLength, iterationLevel + 1); } return longestStringLength; } /** * Returns the number of children including itself. Includes children that * are children of children, etc. * * @param selectableElement * the SelectableElement that we will find the number of children * for. * @return int the number of children including children that are children * of children, etc, and itself. */ public static int calculateNumberOfChildren( SelectableElement selectableElement) { int numberOfChildren = 0; for (int i = 0; i < selectableElement.getNumberOfChildren(); i++) { numberOfChildren += calculateNumberOfChildren(selectableElement .getChild(i)); } return numberOfChildren + 1; } /** * Recursively checks SelectableElement and its children to determine which * elements have the specified selectedType. If a SelectableElement meets * the type criteria, its hint (RelationshipType) is added to the * matchingElements list. * * @param matchingElements * @param typeToMatch */ private void getMatchingElementTypes(List matchingElements, SelectedType typeToMatch) { for (int i = 0; i < getNumberOfChildren(); i++) { getChild(i).getMatchingElementTypes(matchingElements, typeToMatch); } if (getSelectedType() == typeToMatch && getHint() != null) { if (getHint() instanceof Collection) matchingElements.addAll((Collection) getHint()); else matchingElements.add(getHint()); } } /** * Returns a list of SELECTED RelationshipTypes for a SelectableElement. * * Checks this SelectableElement and the SelectableElement's children. For * each SelectableElement where the selectedType is SELECTED, add the * RelationshipType to a list. * * @return List */ public List getSelectedElementTypes() { List selectedElements = new Vector(); getMatchingElementTypes(selectedElements, SelectedType.SELECTED); return selectedElements; } /** * Returns a list of UNSELECTED RelationshipTypes for a SelectableElement. * * Checks this SelectableElement and the SelectableElement's children. For * each SelectableElement where the selectedType is UNSELECTED, add the * RelationshipType to a list. * * @return List */ public List getUnSelectedElementTypes() { List unselectedElements = new Vector(); getMatchingElementTypes(unselectedElements, SelectedType.UNSELECTED); return unselectedElements; } /** * Returns a list of LEAVE RelationshipTypes for a SelectableElement. * * Checks this SelectableElement and the SelectableElement's children. For * each SelectableElement where the selectedType is LEAVE, add the * RelationshipType to a list. * * @return List */ public List getLeaveElementTypes() { List leaveElements = new Vector(); getMatchingElementTypes(leaveElements, SelectedType.LEAVE); return leaveElements; } /** * Returns a string representation of this selectable element. This is * useful if a selectable element must be persisted between invocations of * eclipse. * * @return String id of this selectableElement */ public String getId() { return id; } /** * Retrieves all of the hints from the list of selections. The selections * are strings that have been produced by the getId() method. Note: this * should be invoked from the selectable element root. * * @param stringRepresentations * Strings produced by the {@link SelectableElement#getId()} * method. * @param hints * (out) A set used to store all of the hints. */ public void getHints(List stringRepresentations, Set hints) { // If we have hit a string representation that is fully selected // then we simply retrieve all of the hints for this subtree. For // this selectable element to have been in the "SELECTED" state, it // and all of its children were selected. if (stringRepresentations.contains(getId())) { getAllHints(hints); } else { for (Iterator i = children.iterator(); i.hasNext();) { ((SelectableElement) i.next()).getHints(stringRepresentations, hints); } } } /** * Retrieve all hints for the subtree rooted at this selectable element. * * @param hints * (out) A set used to store all of the hints. */ public void getAllHints(Set hints) { if (hint instanceof List) hints.addAll((List) hint); else if (hint != null) hints.add(hint); for (Iterator i = children.iterator(); i.hasNext();) { ((SelectableElement) i.next()).getAllHints(hints); } } /** * Recursively add this SelectableElement's and this SelectableElement's * children's hints to a List which is not null. * * It will not add duplicates into the List, and if the hint is null, it * will not be added to the List. * * @param list * not null, add hints to this List * @param selectableElement * recursively add hints from this SelectableElement and its * children */ public static void addHintsToList(List list, SelectableElement selectableElement) { assert null != list; assert null != selectableElement; for (int i = 0; i < selectableElement.getNumberOfChildren(); i++) { addHintsToList(list, selectableElement.getChild(i)); } Object hint = selectableElement.getHint(); if (hint != null) { //I disagree that hint should be sometimes a List, and sometimes an //element, but I will support it if (hint instanceof List) { Iterator it = ((List) hint).iterator(); while (it.hasNext()) { Object nestedHint = it.next(); if (!list.contains(nestedHint)) { list.add(nestedHint); } } } else if (!list.contains(hint)) list.add(hint); } } /** * Returns if all children have the same selected type * * @return true if all children are checked, false otherwise * @param parent * we'll be checking the children of this parent * @param selectType * the SelectedType that all children of the parent are checked * for */ public static boolean doAllChildrenHaveSelectedType( SelectableElement parent, SelectedType selectType) { for (int i = 0; i < parent.getNumberOfChildren(); i++) { if (parent.getChild(i).getSelectedType() != selectType) return false; } return true; } /** * Return all children that have the SelectedType. * * @param parent * parent selectable element. * @param selectType * the selected type to match * @param list * of SelectableElements */ public static void getAllChildrenOfType(SelectableElement parent, SelectedType selectType, List list) { assert null != list; for (int i = 0; i < parent.getNumberOfChildren(); i++) { if (parent.getChild(i).getSelectedType() == selectType) { list.add(parent.getChild(i)); } getAllChildrenOfType(parent.getChild(i), selectType, list); } } /** * Return element IDs, including children, that are SelectedType.SELECTED. * * @return List of element IDs, including children, that are * SelectedType.SELECTED. */ public List getSelectedElementIds() { List list = new ArrayList(); getMatchingElementIds(list, SelectedType.SELECTED); return list; } /** * Return matching element IDs that match the typeToMatch. * * @param matchingIds * List of String ids we are filling * @param typeToMatch * going to match this type */ private void getMatchingElementIds(List matchingIds, SelectedType typeToMatch) { for (int i = 0; i < getNumberOfChildren(); i++) { getChild(i).getMatchingElementIds(matchingIds, typeToMatch); } if (getSelectedType() == typeToMatch) { matchingIds.add(getId()); } } /** * Same idea as the clone method. Share the same images, hints, etc. of the * original. Just have different SelectableElements. * * @return a copy of this selectableElement */ public SelectableElement makeCopy() { SelectableElement selectableElement = immediateCopy(this); copyChildren(this, selectableElement); return selectableElement; } /** * Used by the copy method. Not suprisingly, this returns a copy of the src * * @param src * children will be copied from here * @return SelectableElement which is a copy of the src */ private SelectableElement immediateCopy(SelectableElement src) { SelectableElement selectableElement = new SelectableElement( src.getId(), src.getName(), src.getIconImageDescriptor(), src .getHint()); selectableElement.setSelectedType(src.getSelectedType()); return selectableElement; } /** * Used by the copy method. Not surprisingly, this copies the children of * src into dest. * * @param src * children will be copied from here * @param dest * children are copied into here */ private void copyChildren(SelectableElement src, SelectableElement dest) { for (int i = 0; i < src.getNumberOfChildren(); i++) { dest.addChild(immediateCopy(src.getChild(i))); copyChildren(src.getChild(i), dest.getChild(i)); } } /** * Collect all the hints of the children into a list. * * @param list * that I am collecting the hints into. */ private void collectChildrenHints(List list) { Object aHint = getHint(); if (aHint instanceof List) { Iterator it = ((List) aHint).iterator(); while (it.hasNext()) { Object obj = it.next(); if (!list.contains(obj)) list.add(obj); } } else if (aHint != null) { if (!list.contains(aHint)) list.add(aHint); } for (int i = 0; i < getChildren().length; i++) { getChild(i).collectChildrenHints(list); } } /** * Collect the types that match a list of String ids. Unlike getHints, which * doesn't collect children hints. * * @param list * List to add into * @param ids * List of String ids we are trying to match */ public void getHintsThatMatchTheseIds(List list, List ids) { for (int i = 0; i < getNumberOfChildren(); i++) { if (ids.contains(getChild(i).getId())) { getChild(i).collectChildrenHints(list); } else { getChild(i).getHintsThatMatchTheseIds(list, ids); } } } /** * Return the first element that matches the given id from the List of * SelectableElement objects * * @param selectableElements * List of SelectableElement objects to match * @param id * String id to match * @return the first element that matches the given id from the List of * SelectableElement objects or null if nothing matched */ public static SelectableElement findById(List selectableElements, String id) { assert null != selectableElements; Iterator it = selectableElements.iterator(); while (it.hasNext()) { Object obj = it.next(); assert (obj instanceof SelectableElement); SelectableElement selectableElement = (SelectableElement) obj; if (id.equals(selectableElement.getId())) { return selectableElement; } } return null; } /** * Return the first element that matches the given id from this * SelectableElement and its children * * @param theId * String id to match * @return the first element that matches the given id from this * SelectableElement and its children or null if nothing matched */ public SelectableElement findById(String theId) { assert null != theId; if (theId.equals(this.getId())) { return this; } for (int i = 0; i < getNumberOfChildren(); i++) { SelectableElement element = getChild(i).findById(theId); if (element != null) return element; } return null; } }