org.eclipse.ui.internal.dialogs.cpd.TreeManager.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ui.internal.dialogs.cpd.TreeManager.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 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
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654
 *******************************************************************************/
package org.eclipse.ui.internal.dialogs.cpd;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.IBaseLabelProvider;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ICheckStateProvider;
import org.eclipse.jface.viewers.ICheckable;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.graphics.Image;

/**
 * Manages a tree which provides "standard checkbox tree behavior". I.e. it
 * follows these rules:
 * <ol>
 *    <li>If a check box is checked, all its children must be checked.</li>
 *    <li>If a check box is unchecked, all its children must be unchecked.</li>
 *    <li>If a check box is grayed, each of its children may be either checked or
 *       unchecked, however, there must be one of each.</li>
 *    <li>If a user checks a check box, its children or parents must change state
 *       accordingly.</li>
 * </ol>
 * <p>
 * <b>Note:</b> be sure to call dispose()
 * </p>
 * @since 3.5
 *
 */
public class TreeManager {
    public static final int CHECKSTATE_UNCHECKED = 0;
    public static final int CHECKSTATE_GRAY = 1;
    public static final int CHECKSTATE_CHECKED = 2;

    private static ICheckStateProvider checkStateProvider = null;
    private static IBaseLabelProvider labelProvider = null;
    private static ICheckStateListener viewerCheckListener = null;
    private static ITreeContentProvider treeContentProvider = null;

    private List<CheckListener> listeners = new ArrayList<>();
    private LocalResourceManager resourceManager = new LocalResourceManager(JFaceResources.getResources());

    /**
     * Instances of this interface will handle changes in the model
     * representation of checkstates.
     */
    public interface CheckListener {
        /**
         * Invoked when a {@link TreeManager.TreeItem}'s check state has
         * changed.
         *
         * @param changedItem
         *            The item whose check state has changed
         */
        public void checkChanged(TreeItem changedItem);
    }

    /**
     * Implementation of {@link TreeManager.CheckListener} for use in a {@link CheckboxTreeViewer}.
     */
    public static class ModelListenerForCheckboxTree implements CheckListener {
        private CheckboxTreeViewer treeViewer;

        public ModelListenerForCheckboxTree(TreeManager manager, CheckboxTreeViewer treeViewer) {
            this.treeViewer = treeViewer;
            manager.addListener(this);
        }

        @Override
        public void checkChanged(TreeItem changedItem) {
            treeViewer.update(changedItem, null);
        }
    }

    /**
     * Implementation of {@link TreeManager}CheckListener for use in a
     * {@link CheckboxTableViewer}.
     */
    public static class ModelListenerForCheckboxTable implements CheckListener {
        private CheckboxTableViewer tableViewer;

        public ModelListenerForCheckboxTable(TreeManager manager, CheckboxTableViewer tableViewer) {
            this.tableViewer = tableViewer;
            manager.addListener(this);
        }

        @Override
        public void checkChanged(TreeItem changedItem) {
            tableViewer.update(changedItem, null);
        }
    }

    public static class ViewerCheckStateListener implements ICheckStateListener {
        @Override
        public void checkStateChanged(CheckStateChangedEvent event) {
            Object checked = event.getElement();
            if (checked instanceof TreeItem) {
                ((TreeItem) checked).setChangedByUser(true);
                ((TreeItem) checked).setCheckState(event.getChecked());
            }
        }
    }

    /**
     * An {@link ICheckStateProvider} which properly provides checkbox state on
     * {@link TreeManager.TreeItem}s.
     */
    public static class CheckStateProvider implements ICheckStateProvider {
        @Override
        public boolean isChecked(Object element) {
            return ((TreeItem) element).checkState != CHECKSTATE_UNCHECKED;
        }

        @Override
        public boolean isGrayed(Object element) {
            return ((TreeItem) element).checkState == CHECKSTATE_GRAY;
        }
    }

    /**
     * A {@link IBaseLabelProvider} for {@link TreeManager.TreeItem}s.
     */
    public static class TreeItemLabelProvider extends LabelProvider {
        @Override
        public String getText(Object element) {
            if (element instanceof TreeItem) {
                return ((TreeItem) element).getLabel();
            }
            return super.getText(element);
        }

        @Override
        public Image getImage(Object element) {
            if (element instanceof TreeItem) {
                return ((TreeItem) element).getImage();
            }
            return super.getImage(element);
        }
    }

    /**
     * An {@link ITreeContentProvider} for {@link TreeManager.TreeItem}s - will completely build the
     * tree structure represented by {@link TreeManager.TreeItem}s.
     */
    public static class TreeItemContentProvider implements ITreeContentProvider {
        @Override
        public Object[] getChildren(Object parentElement) {
            return ((TreeItem) parentElement).getChildren().toArray();
        }

        @Override
        public Object getParent(Object element) {
            return ((TreeItem) element).getParent();
        }

        @Override
        public boolean hasChildren(Object element) {
            return getChildren(element).length > 0;
        }

        @Override
        public Object[] getElements(Object inputElement) {
            return getChildren(inputElement);
        }

        @Override
        public void dispose() {
        }

        @Override
        public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        }
    }

    /**
     * @return   an {@link ICheckStateProvider} which will operate on
     *       {@link TreeItem}s
     */
    public static ICheckStateProvider getCheckStateProvider() {
        if (checkStateProvider == null) {
            checkStateProvider = new CheckStateProvider();
        }
        return checkStateProvider;
    }

    /**
     * @return   an {@link IBaseLabelProvider} which will provide the labels and
     *       images of {@link TreeItem}s
     */
    public static IBaseLabelProvider getLabelProvider() {
        if (labelProvider == null) {
            labelProvider = new TreeItemLabelProvider();
        }
        return labelProvider;
    }

    /**
     * @return   an {@link ITreeContentProvider} which will provide
     *       {@link TreeItem} content in tree format.
     */
    public static ITreeContentProvider getTreeContentProvider() {
        if (treeContentProvider == null)
            treeContentProvider = new TreeItemContentProvider();
        return treeContentProvider;
    }

    /**
     * @return   an {@link ICheckStateListener} which will respond to
     *       {@link CheckStateChangedEvent}s by updating the model to reflect
     *       them
     */
    public ICheckStateListener getViewerCheckStateListener() {
        if (viewerCheckListener == null)
            viewerCheckListener = new ViewerCheckStateListener();
        return viewerCheckListener;
    }

    /**
     * A single item in a tree of managed checkbox states.
     */
    public class TreeItem {
        private String label;
        private ImageDescriptor imageDescriptor;
        private Image image;
        private TreeItem parent;
        private List<TreeItem> children;
        private int checkState;
        private boolean changedByUser;

        public TreeItem(String label) {
            this.label = label;
            this.children = new ArrayList<>();
        }

        public String getLabel() {
            return label;
        }

        public void setLabel(String label) {
            this.label = label;
        }

        public Image getImage() {
            if (image == null) {
                if (imageDescriptor == null) {
                    return null;
                }
                image = resourceManager.createImage(imageDescriptor);
            }
            return image;
        }

        public void setImageDescriptor(ImageDescriptor imageDescriptor) {
            this.imageDescriptor = imageDescriptor;
        }

        public void addChild(TreeItem newChild) {
            newChild.parent = this;
            children.add(newChild);
            synchParents(newChild);
        }

        public List<TreeItem> getChildren() {
            return children;
        }

        public int getChildrenCount() {
            return children.size();
        }

        public TreeItem getParent() {
            return parent;
        }

        /**
        * An internal call that forwards the change events but does <b>not</b>
        * cause any iterative synchronization to take place.
        *
        * @param newState
        */
        private void internalSetCheckState(int newState) {
            if (newState == checkState)
                return;

            checkState = newState;
            fireListeners(this);
        }

        /**
         * External call to explicitly set the particular state of a
         * {@link TreeManager.TreeItem}. This is usually a response to an SWT
         * check changed event generated by a Tree/Table.
         *
         * @param checked
         */
        public void setCheckState(boolean checked) {
            int newState = checked ? CHECKSTATE_CHECKED : CHECKSTATE_UNCHECKED;
            if (checkState == newState)
                return;
            // Actually set the state and fire the CheckChangeEvent
            internalSetCheckState(newState);

            // Enforce the SWT rules for checked/gray behavior
            synchChildren(this);
            synchParents(this);
        }

        /**
         * From the client's perspective the state is a boolean.
         *
         * @return <code>true</code> if the state is not UNCHECKED
         */
        public boolean getState() {
            return !(checkState == CHECKSTATE_UNCHECKED);
        }

        int getCheckState() {
            return checkState;
        }

        /**
         * If the new state is not "GRAY" then force all children to match that
         * state (recursively).
         *
         * @param changedItem
         */
        private void synchChildren(TreeItem changedItem) {
            int newState = changedItem.checkState;

            // if the new state is 'GRAY'
            if (newState != CHECKSTATE_GRAY) {
                for (TreeItem treeItem : changedItem.children) {
                    TreeItem curItem = treeItem;
                    curItem.internalSetCheckState(newState);
                    curItem.setChangedByUser(changedItem.isChangedByUser());

                    synchChildren(curItem);
                }
            }
        }

        /**
         * Set the parent's state based on the aggregate state of its children
         * using the following rules:
         * <ul>
         * <li>All children checked...parent checked</li>
         * <li>All children unchecked...parent unchecked</li>
         * <li>else...parent GRAY</li>
         * </ul>
         *
         * @param changedItem
         */
        private void synchParents(TreeItem changedItem) {
            if (changedItem.parent == null)
                return;

            int newState = changedItem.checkState;

            if (newState == CHECKSTATE_GRAY) {
                // if the new state is 'GRAY' then -ALL- the parents are gray
                while (changedItem.parent != null && changedItem.parent.checkState != CHECKSTATE_GRAY) {
                    changedItem.parent.internalSetCheckState(CHECKSTATE_GRAY);
                    if (changedItem.isChangedByUser()) {
                        changedItem.parent.setChangedByUser(true);
                    }
                    changedItem = changedItem.parent;
                }
            } else {
                // compute the parent's state - checked if all children are
                // checked, unchecked if all children are unchecked, gray if
                // some of each
                boolean checkedFound = newState == CHECKSTATE_CHECKED;
                boolean uncheckedFound = newState == CHECKSTATE_UNCHECKED;
                for (Iterator<TreeItem> i = changedItem.parent.children.iterator(); i.hasNext()
                        && (!checkedFound || !uncheckedFound);) {
                    TreeItem item = i.next();
                    switch (item.checkState) {
                    case CHECKSTATE_CHECKED: {
                        checkedFound = true;
                        break;
                    }
                    case CHECKSTATE_GRAY: {
                        checkedFound = uncheckedFound = true;
                        break;
                    }
                    case CHECKSTATE_UNCHECKED: {
                        uncheckedFound = true;
                        break;
                    }
                    }
                }

                int oldState = changedItem.parent.checkState;
                if (checkedFound && uncheckedFound) {
                    changedItem.parent.internalSetCheckState(CHECKSTATE_GRAY);
                } else if (checkedFound) {
                    changedItem.parent.internalSetCheckState(CHECKSTATE_CHECKED);
                } else {
                    changedItem.parent.internalSetCheckState(CHECKSTATE_UNCHECKED);
                }
                if (oldState != changedItem.parent.checkState) {
                    if (changedItem.isChangedByUser()) {
                        changedItem.parent.setChangedByUser(true);
                    }
                    synchParents(changedItem.parent);
                }
            }
        }

        /**
         * @param changedByUser The changedByUser to set.
         */
        public void setChangedByUser(boolean changedByUser) {
            this.changedByUser = changedByUser;
        }

        /**
         * @return Returns the changedByUser.
         */
        public boolean isChangedByUser() {
            return changedByUser;
        }

        @Override
        public String toString() {
            return label + ", check=" + getState() + ", changed=" + changedByUser; //$NON-NLS-1$ //$NON-NLS-2$
        }

    }

    /**
     * Creates a new {@link TreeManager}.
     */
    public TreeManager() {
        listeners = new ArrayList<>();
    }

    /**
     * Add a {@link CheckListener} whose
     * {@link CheckListener#checkChanged(TreeManager.TreeItem)} method will be
     * invoked when a {@link TreeItem} created in this {@link TreeManager} has a
     * check state change.
     *
     * @param listener
     */
    public void addListener(CheckListener listener) {
        listeners.add(listener);
    }

    /**
     * Provides a {@link CheckListener} which updates a viewer whenever the
     * {@link TreeManager} model changes.
     * @param viewer   The viewer whose check states will be appropriately
     *       updated on a change to the model.
     * @return   The created {@link CheckListener}.
     */
    public CheckListener getCheckListener(ICheckable viewer) {
        if (viewer instanceof CheckboxTreeViewer)
            return new ModelListenerForCheckboxTree(this, (CheckboxTreeViewer) viewer);
        if (viewer instanceof CheckboxTableViewer)
            return new ModelListenerForCheckboxTable(this, (CheckboxTableViewer) viewer);
        return null;
    }

    /**
     * Sets up this {@link TreeManager} for standard interaction with the
     * provided {@link CheckboxTreeViewer}. In particular:
     * <ul>
     *    <li>Adds a Label Provider for {@link TreeItem}s which provides both
     *       labels and images.</li>
     *    <li>Adds a {@link CheckStateProvider} for {@link TreeItem}s.</li>
     *    <li>Adds an {@link IContentProvider} to build a tree from input
     *       {@link TreeItem}s.</li>
     *    <li>Adds an {@link ICheckStateListener} to the viewer to update the
     *       appropriate {@link TreeItem}s upon a check box state change in the
     *       viewer.</li>
     *    <li>Adds a {@link CheckListener} to the {@link TreeManager} which will
     *       automatically update the viewer on a {@link TreeItem} check state
     *       change.</li>
     * </ul>
     * @param viewer   the viewer to configure with this TreeManager.
     */
    public void attachAll(CheckboxTreeViewer viewer) {
        viewer.setLabelProvider(getLabelProvider());
        viewer.setCheckStateProvider(getCheckStateProvider());
        viewer.setContentProvider(getTreeContentProvider());
        viewer.addCheckStateListener(getViewerCheckStateListener());
        getCheckListener(viewer);
    }

    /**
     * Sets up this {@link TreeManager} for standard interaction with the
     * provided {@link CheckboxTableViewer}. In particular:
     * <ul>
     *    <li>Adds a Label Provider for {@link TreeItem}s which provides both
     *       labels and images.</li>
     *    <li>Adds a {@link CheckStateProvider} for {@link TreeItem}s.</li>
     *    <li>Adds an {@link IContentProvider} to build a tree from input
     *       {@link TreeItem}s.</li>
     *    <li>Adds an {@link ICheckStateListener} to the viewer to update the
     *       appropriate {@link TreeItem}s upon a check box state change in the
     *       viewer.</li>
     *    <li>Adds a {@link CheckListener} to the {@link TreeManager} which will
     *       automatically update the viewer on a {@link TreeItem} check state
     *       change.</li>
     * </ul>
     * @param viewer   the viewer to configure with this TreeManager.
     */
    public void attachAll(CheckboxTableViewer viewer) {
        viewer.setLabelProvider(getLabelProvider());
        viewer.setCheckStateProvider(getCheckStateProvider());
        viewer.setContentProvider(getTreeContentProvider());
        viewer.addCheckStateListener(getViewerCheckStateListener());
        getCheckListener(viewer);
    }

    /**
     * Dissociates a listener.
     * @param listener   The listener to remove.
     */
    public void removeListener(CheckListener listener) {
        listeners.remove(listener);
    }

    /**
     * Fires all listeners.
     * @param item   The {@link TreeItem} that changed.
     */
    private void fireListeners(TreeItem item) {
        for (CheckListener checkListener : listeners) {
            CheckListener listener = checkListener;
            listener.checkChanged(item);
        }
    }

    public void dispose() {
        resourceManager.dispose();
        resourceManager = null;
        listeners.clear();
        listeners = null;
    }
}