| /******************************************************************************* |
| * Copyright (c) 2011, 2015 Wind River Systems 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: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.debug.internal.ui.viewers.model.provisional; |
| |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.TreeMap; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.debug.internal.core.IInternalDebugCoreConstants; |
| |
| /** |
| * Virtual item, which is analogous to the SWT's tree item. This class is used |
| * by the {@link VirtualTreeModelViewer}. |
| * |
| * @see VirtualTreeModelViewer |
| * @since 3.8 |
| */ |
| public class VirtualItem { |
| |
| // Data keys for display attributes of an item. |
| public static String LABEL_KEY = "LABEL_KEY"; //$NON-NLS-1$ |
| public static String IMAGE_KEY = "IMAGE_KEY"; //$NON-NLS-1$ |
| public static String FONT_KEY = "FONT_KEY"; //$NON-NLS-1$ |
| public static String FOREGROUND_KEY = "FOREGROUND_KEY"; //$NON-NLS-1$ |
| public static String BACKGROUND_KEY = "BACKGROUND_KEY"; //$NON-NLS-1$ |
| |
| public static String ELEMENT_DATA_KEY = "element"; //$NON-NLS-1$ |
| |
| /** |
| * Index object of a tree item. It allows the indexes to be modified |
| * as items are inserted and removed. |
| */ |
| public static class Index implements Comparable<Object> { |
| private Integer fIndexValue; |
| |
| public Index(int index) { |
| fIndexValue = Integer.valueOf(index); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| return obj instanceof Index && ((Index)obj).fIndexValue.equals(fIndexValue); |
| } |
| |
| @Override |
| public int hashCode() { |
| return fIndexValue.hashCode(); |
| } |
| |
| public void increment() { |
| fIndexValue = Integer.valueOf(fIndexValue.intValue() + 1); |
| } |
| |
| public void decrement() { |
| fIndexValue = Integer.valueOf(fIndexValue.intValue() - 1); |
| } |
| |
| public int intValue() { |
| return fIndexValue.intValue(); |
| } |
| |
| @Override |
| public int compareTo(Object obj) { |
| return obj instanceof Index ? fIndexValue.compareTo(((Index)obj).fIndexValue) : 0; |
| } |
| |
| @Override |
| public String toString() { |
| return fIndexValue.toString(); |
| } |
| } |
| |
| /** |
| * Parent items of this item. |
| */ |
| final private VirtualItem fParent; |
| |
| /** |
| * The index of this item. |
| */ |
| final private Index fIndex; |
| |
| /** |
| * Map of child items. The key to the map is the item's index, which |
| * must be the same object instance as the index in the item. The tree map |
| * keeps the items sorted while allowing indexes (keys) to be modified as |
| * child items are inserted and removed. |
| */ |
| private Map<Index, VirtualItem> fItems = new TreeMap<Index, VirtualItem>(); |
| |
| /** |
| * Flag indicating whether this item has child items. |
| */ |
| private boolean fHasItems = false; |
| |
| /** |
| * Indicates that this item has been expanded. It should only |
| * be set to <code>true</code> if fHasItems is <code>true</code>. |
| */ |
| private boolean fExpanded = false; |
| |
| /** |
| * The count of child items. <code>-1</code> indicates that the count |
| * is not known. |
| */ |
| private int fItemCount = -1; |
| |
| /** |
| * The data held by this item. It includes the element as well as the item |
| * display attributes. |
| */ |
| private Map<String, Object> fData = new HashMap<String, Object>(1); |
| |
| /** |
| * Flag indicating that the item needs to have it's label updated. |
| */ |
| private boolean fNeedsLabelUpdate = true; |
| |
| /** |
| * Flag indicating that the item's count needs to be updated. |
| */ |
| private boolean fNeedsCountUpdate = true; |
| |
| /** |
| * Flag indicating that the item's element needs to be updated. |
| */ |
| private boolean fNeedsDataUpdate = true; |
| |
| /** |
| * Indicates that this item has been disposed. |
| */ |
| private boolean fDisposed = false; |
| |
| |
| /** |
| * Virtual item constructor. |
| * @param parent parent virtual item |
| * @param index index of the item in the parent |
| */ |
| public VirtualItem(VirtualItem parent, Index index) { |
| fParent = parent; |
| fIndex = index; |
| } |
| |
| /** |
| * Clears the child item at the given index. |
| * @param index index of item to clear. |
| */ |
| public void clear(Index index) { |
| VirtualItem item = fItems.remove(index); |
| if (item != null) { |
| item.dispose(); |
| } |
| } |
| |
| /** |
| * Clears all child items. |
| * |
| * @since 3.9 |
| */ |
| public void clearAll() { |
| fData.clear(); |
| for (VirtualItem item : fItems.values()) { |
| item.dispose(); |
| } |
| fItems.clear(); |
| } |
| /** |
| * Returns the parent item. |
| * @return parent item. |
| */ |
| public VirtualItem getParent() { |
| return fParent; |
| } |
| |
| /** |
| * @return Returns the index of this item. |
| */ |
| public Index getIndex() { |
| return fIndex; |
| } |
| |
| /** |
| * Finds the given item in the child items of this element. |
| * @param element Data object of the item to be found. |
| * @return Item if found, <code>null</code> if not. |
| */ |
| public VirtualItem findItem(Object element) { |
| for (VirtualItem item : fItems.values()) { |
| Object nextData = item.getData(); |
| if ( (element != null && element.equals(nextData)) || (element == null && nextData == null) ) { |
| return item; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return Returns whether the data element of this item is stale. |
| */ |
| public boolean needsDataUpdate() { |
| return fNeedsDataUpdate; |
| } |
| |
| /** |
| * Marks the item as having a stale data item. |
| */ |
| public void setNeedsDataUpdate() { |
| fNeedsDataUpdate = true; |
| } |
| |
| /** |
| * Clears the stale status of the item's data element. |
| */ |
| public void clearNeedsDataUpdate() { |
| fNeedsDataUpdate = false; |
| } |
| |
| /** |
| * @return Returns whether the item has stale item count. |
| */ |
| public boolean needsCountUpdate() { |
| return fNeedsCountUpdate; |
| } |
| |
| /** |
| * Marks the item as having a stale child count. |
| */ |
| public void setNeedsCountUpdate() { |
| fNeedsCountUpdate = true; |
| fItemCount = -1; |
| } |
| |
| /** |
| * Clears the stale status of the item's child count. |
| */ |
| public void clearNeedsCountUpdate() { |
| fNeedsCountUpdate = false; |
| } |
| |
| /** |
| * @return Returns whether the item has stale label. |
| */ |
| public boolean needsLabelUpdate() { |
| return fNeedsLabelUpdate; |
| } |
| |
| /** |
| * Marks the item as having a stale label data. |
| */ |
| public void setNeedsLabelUpdate() { |
| fNeedsLabelUpdate = true; |
| } |
| |
| /** |
| * Clears the stale status of the item's label. |
| */ |
| public void clearNeedsLabelUpdate() { |
| fNeedsLabelUpdate = false; |
| } |
| |
| /** |
| * @return Returns whether the item has been disposed. |
| */ |
| public boolean isDisposed() { |
| return fDisposed; |
| } |
| |
| /** |
| * Disposes the item. |
| */ |
| public void dispose() { |
| clearAll(); |
| |
| fDisposed = true; |
| findTree().fireItemDisposed(this); |
| } |
| |
| /** |
| * @param key Key to retrieve data for. |
| * @return Returns item data corresponding to given key. |
| */ |
| public Object getData (String key) { |
| return fData.get(key); |
| } |
| |
| /** |
| * Sets given data element for given key. |
| * @param key Key for data. |
| * @param data Data value. |
| */ |
| public void setData(String key, Object data) { |
| fData.put(key, data); |
| } |
| |
| /** |
| * Sets the item's data element. |
| * @param data Item's new element. |
| */ |
| public void setData(Object data) { |
| fData.put(ELEMENT_DATA_KEY, data); |
| } |
| |
| /** |
| * @return Returns item's data element. |
| */ |
| public Object getData () { |
| return fData.get(ELEMENT_DATA_KEY); |
| } |
| |
| /** |
| * Marks the given item as expanded or collapsed. |
| * @param expanded If true, item will be marked as expanded. |
| */ |
| public void setExpanded(boolean expanded) { |
| if (fExpanded == expanded) { |
| return; |
| } |
| fExpanded = expanded; |
| |
| if (fExpanded && getItemCount() == -1) { |
| setNeedsCountUpdate(); |
| } |
| |
| |
| Assert.isTrue(!fExpanded || hasItems()); |
| |
| // If collapsed, make sure that all the children are collapsed as well. |
| if (!fExpanded) { |
| for (VirtualItem item : fItems.values()) { |
| item.setExpanded(expanded); |
| } |
| } |
| } |
| |
| /** |
| * @return Returns item's expanded state. |
| */ |
| public boolean getExpanded() { |
| return fExpanded; |
| } |
| |
| /** |
| * Sets the flag indicating whether item has child items. |
| * @param hasChildren Set to true if child has items. |
| */ |
| public void setHasItems(boolean hasChildren) { |
| fHasItems = hasChildren; |
| if (!fHasItems) { |
| if (getItemCount() != 0) { |
| setItemCount(0); |
| } |
| } else if (getItemCount() == 0) { |
| setItemCount(-1); |
| } |
| } |
| |
| /** |
| * @return Returns true if item has child items. |
| */ |
| public boolean hasItems() { |
| return fHasItems; |
| } |
| |
| /** |
| * Sets the item's child count. |
| * @param count Child count. |
| */ |
| public void setItemCount(int count) { |
| fItemCount = count; |
| for (Iterator<Entry<Index, VirtualItem>> itr = fItems.entrySet().iterator(); itr.hasNext();) { |
| Entry<Index, VirtualItem> entry = itr.next(); |
| int index = entry.getKey().intValue(); |
| if (index >= count) { |
| VirtualItem item = entry.getValue(); |
| item.dispose(); |
| itr.remove(); |
| } |
| } |
| if (fItemCount == 0) { |
| if (hasItems()) { |
| setHasItems(false); |
| } |
| if (getExpanded()) { |
| setExpanded(false); |
| } |
| } else { |
| if (!hasItems()) { |
| setHasItems(true); |
| } |
| } |
| } |
| |
| /** |
| * @return Returns item's child count. |
| */ |
| public int getItemCount() { |
| return fItemCount; |
| } |
| |
| /** |
| * Returns the child item at given index. Child item is created if needed. |
| * |
| * @param index Index of the child item. |
| * @return Child item. |
| */ |
| public VirtualItem getItem(Index index) { |
| ensureItems(); |
| |
| VirtualItem item = fItems.get(index); |
| if (item == null) { |
| item = new VirtualItem(this, index); |
| fItems.put(index, item); |
| } |
| return item; |
| } |
| |
| /** |
| * @return Returns true if any of the child items need a data update. |
| */ |
| public boolean childrenNeedDataUpdate() { |
| if (getItemCount() == 0) { |
| return false; |
| } |
| if (fItems == null || fItems.size() != fItemCount) { |
| return true; |
| } |
| for (VirtualItem child : fItems.values()) { |
| if (child.needsDataUpdate()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns an array of current child items. The returned array contains |
| * only the items that have been created. It may not contain as many items as the |
| * item count. |
| * |
| * @return Child items array. |
| */ |
| public VirtualItem[] getItems() { |
| return fItems.values().toArray(new VirtualItem[fItems.size()]); |
| } |
| |
| /** |
| * Adds a child item at the given index position. |
| * @param position The index position to inser the new item at. |
| * @return Returns the added item. |
| */ |
| public VirtualItem addItem(int position) { |
| if (!fHasItems) { |
| fHasItems = true; |
| } |
| if (fItemCount < 0) { |
| fItemCount = 0; |
| } |
| |
| // Increment all items with an index higher than the given position. |
| fItemCount++; |
| ensureItems(); |
| for (Index childIndex : fItems.keySet()) { |
| if (childIndex.intValue() >= position) { |
| childIndex.increment(); |
| } |
| } |
| |
| // Note: the same index object used to create the item has to |
| // be used as the key into the map. |
| Index childIndex = new Index(position); |
| VirtualItem newChild = new VirtualItem(this, childIndex); |
| fItems.put(childIndex, newChild); |
| return newChild; |
| } |
| |
| /** |
| * Removes the item at the given index. |
| * @param position Index of the item to remove. |
| */ |
| public void remove(Index position) { |
| fItemCount--; |
| if (fItemCount < 0) { |
| fHasItems = false; |
| } |
| |
| ensureItems(); |
| |
| VirtualItem removedItem = null; |
| for (Iterator<Entry<Index, VirtualItem>> itr = fItems.entrySet().iterator(); itr.hasNext();) { |
| Entry<Index, VirtualItem> entry = itr.next(); |
| Index childIndex = entry.getKey(); |
| if (childIndex.intValue() > position.intValue()) { |
| childIndex.decrement(); |
| } else if (childIndex.intValue() == position.intValue()) { |
| removedItem = entry.getValue(); |
| removedItem.dispose(); |
| itr.remove(); |
| } |
| } |
| } |
| |
| private void ensureItems() { |
| if (fItems == null) { |
| fItems = new HashMap<Index, VirtualItem>(Math.max(1, Math.min(fItemCount, 16))); |
| } |
| } |
| |
| private VirtualTree findTree() { |
| VirtualItem item = this; |
| while (!(item instanceof VirtualTree)) { |
| item = item.fParent; |
| } |
| return (VirtualTree)item; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuffer buffer = new StringBuffer(); |
| toStringItem(buffer, IInternalDebugCoreConstants.EMPTY_STRING); |
| return buffer.toString(); |
| } |
| |
| void toStringItem(StringBuffer buffer, String indent) { |
| buffer.append(indent); |
| buffer.append(toStringElement()); |
| buffer.append("\n"); //$NON-NLS-1$ |
| indent = indent + " "; //$NON-NLS-1$ |
| for (int i = 0; i < fItemCount; i++) { |
| VirtualItem item = fItems.get(new Index(i)); |
| if (item != null) { |
| item.toStringItem(buffer, indent); |
| } else { |
| buffer.append("<no item>\n"); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private String toStringElement() { |
| String[] label = (String[])fData.get(LABEL_KEY); |
| if (label != null && label.length != 0) { |
| return label[0]; |
| } |
| Object data = fData.get(ELEMENT_DATA_KEY); |
| if (data != null) { |
| return data.toString(); |
| } |
| return "<no data>"; //$NON-NLS-1$ |
| } |
| } |