org.eclipse.jface.viewers.ContentViewer.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.jface.viewers.ContentViewer.java

Source

/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *     Steven Spungin <steven@spungin.tv> - Bug 401439
 *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 475844, 475689
 *******************************************************************************/
package org.eclipse.jface.viewers;

import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.internal.InternalPolicy;
import org.eclipse.jface.util.Policy;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Control;

/**
 * A content viewer is a model-based adapter on a widget which accesses its
 * model by means of a content provider and a label provider.
 * <p>
 * A viewer's model consists of elements, represented by objects. A viewer
 * defines and implements generic infrastructure for handling model input,
 * updates, and selections in terms of elements. Input is obtained by querying
 * an <code>IContentProvider</code> which returns elements. The elements
 * themselves are not displayed directly. They are mapped to labels, containing
 * text and/or an image, using the viewer's <code>ILabelProvider</code>.
 * </p>
 * <p>
 * Implementing a concrete content viewer typically involves the following
 * steps:
 * </p>
 * <ul>
 * <li>create SWT controls for viewer (in constructor) (optional)</li>
 * <li>initialize SWT controls from input (inputChanged)</li>
 * <li>define viewer-specific update methods</li>
 * <li>support selections (<code>setSelection</code>, <code>getSelection</code>)
 * </ul>
 */
public abstract class ContentViewer extends Viewer {

    /**
     * This viewer's content provider, or <code>null</code> if none.
     */
    private IContentProvider contentProvider = null;

    /**
     * This viewer's input, or <code>null</code> if none.
     * The viewer's input provides the "model" for the viewer's content.
     */
    private Object input = null;

    /**
     * This viewer's label provider. Initially <code>null</code>, but
     * lazily initialized (to a <code>SimpleLabelProvider</code>).
     */
    private IBaseLabelProvider labelProvider = null;

    /**
     * This viewer's label provider listener.
     * Note: Having a viewer register a label provider listener with
     * a label provider avoids having to define public methods
     * for internal events.
     */
    private final ILabelProviderListener labelProviderListener = new ILabelProviderListener() {
        private boolean logWhenDisposed = true; // initially true, set to false

        @Override
        public void labelProviderChanged(LabelProviderChangedEvent event) {
            Control control = getControl();
            if (control == null || control.isDisposed()) {
                if (logWhenDisposed) {
                    String message = "Ignored labelProviderChanged notification because control is diposed." + //$NON-NLS-1$
                    " This indicates a potential memory leak."; //$NON-NLS-1$
                    if (!InternalPolicy.DEBUG_LOG_LABEL_PROVIDER_NOTIFICATIONS_WHEN_DISPOSED) {
                        // stop logging after the first
                        logWhenDisposed = false;
                        message += " This is only logged once per viewer instance," + //$NON-NLS-1$
                        " but similar calls will still be ignored."; //$NON-NLS-1$
                    }
                    Policy.getLog().log(new Status(IStatus.WARNING, Policy.JFACE, message, new RuntimeException()));
                }
                return;
            }
            ContentViewer.this.handleLabelProviderChanged(event);
        }
    };

    /**
     * Creates a content viewer with no input, no content provider, and a
     * default label provider.
     */
    protected ContentViewer() {
    }

    /**
     * Returns the content provider used by this viewer,
     * or <code>null</code> if this view does not yet have a content
     * provider.
     * <p>
     * The <code>ContentViewer</code> implementation of this method returns the content
     * provider recorded is an internal state variable.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.getContentProvider</code> must be invoked.
     * </p>
     *
     * @return the content provider, or <code>null</code> if none
     */
    public IContentProvider getContentProvider() {
        return contentProvider;
    }

    /**
     * The <code>ContentViewer</code> implementation of this <code>IInputProvider</code>
     * method returns the current input of this viewer, or <code>null</code>
     * if none. The viewer's input provides the "model" for the viewer's
     * content.
     */
    @Override
    public Object getInput() {
        return input;
    }

    /**
     * Returns the label provider used by this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method returns the label
     * provider recorded in an internal state variable; if none has been
     * set (with <code>setLabelProvider</code>) a default label provider
     * will be created, remembered, and returned.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.getLabelProvider</code> must be invoked.
     * </p>
     *
     * @return a label provider
     */
    public IBaseLabelProvider getLabelProvider() {
        if (labelProvider == null) {
            labelProvider = new LabelProvider();
        }
        return labelProvider;
    }

    /**
     * Handles a dispose event on this viewer's control.
     * <p>
     * The <code>ContentViewer</code> implementation of this method disposes of this
     * viewer's label provider and content provider (if it has one).
     * Subclasses should override this method to perform any additional
     * cleanup of resources; however, overriding methods must invoke
     * <code>super.handleDispose</code>.
     * </p>
     *
     * @param event a dispose event
     */
    protected void handleDispose(DisposeEvent event) {
        if (contentProvider != null) {
            try {
                contentProvider.inputChanged(this, getInput(), null);
            } catch (Exception e) {
                // ignore exception
                String message = "Exception while calling ContentProvider.inputChanged from ContentViewer.handleDispose"; //$NON-NLS-1$
                message += " (" + contentProvider.getClass().getName() + ")"; //$NON-NLS-1$//$NON-NLS-2$
                Policy.getLog().log(new Status(IStatus.WARNING, Policy.JFACE, message, e));
            }
            contentProvider.dispose();
            contentProvider = null;
        }
        if (labelProvider != null) {
            labelProvider.removeListener(labelProviderListener);
            labelProvider.dispose();
            labelProvider = null;
        }
        input = null;
    }

    /**
     * Handles a label provider changed event.
     * <p>
     * The <code>ContentViewer</code> implementation of this method calls <code>labelProviderChanged()</code>
     * to cause a complete refresh of the viewer.
     * Subclasses may reimplement or extend.
     * </p>
     * @param event the change event
     */
    protected void handleLabelProviderChanged(LabelProviderChangedEvent event) {
        labelProviderChanged();
    }

    /**
     * Adds event listener hooks to the given control.
     * <p>
     * All subclasses must call this method when their control is
     * first established.
     * </p>
     * <p>
     * The <code>ContentViewer</code> implementation of this method hooks
     * dispose events for the given control.
     * Subclasses may override if they need to add other control hooks;
     * however, <code>super.hookControl</code> must be invoked.
     * </p>
     *
     * @param control the control
     */
    protected void hookControl(Control control) {
        control.addDisposeListener(this::handleDispose);
    }

    /**
     * Notifies that the label provider has changed.
     * <p>
     * The <code>ContentViewer</code> implementation of this method calls <code>refresh()</code>.
     * Subclasses may reimplement or extend.
     * </p>
     */
    protected void labelProviderChanged() {
        refresh();
    }

    /**
     * Sets the content provider used by this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method records the
     * content provider in an internal state variable.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.setContentProvider</code> must be invoked.
     * </p>
     *
     * @param contentProvider the content provider
     * @see #getContentProvider
     */
    public void setContentProvider(IContentProvider contentProvider) {
        Assert.isNotNull(contentProvider);
        IContentProvider oldContentProvider = this.contentProvider;
        this.contentProvider = contentProvider;
        if (oldContentProvider != null) {
            Object currentInput = getInput();
            oldContentProvider.inputChanged(this, currentInput, null);
            oldContentProvider.dispose();
            contentProvider.inputChanged(this, null, currentInput);
            refresh();
        }
    }

    /**
     * The <code>ContentViewer</code> implementation of this <code>Viewer</code>
     * method invokes <code>inputChanged</code> on the content provider and then the
     * <code>inputChanged</code> hook method. This method fails if this viewer does
     * not have a content provider. Subclassers are advised to override
     * <code>inputChanged</code> rather than this method, but may extend this method
     * if required.
     */
    @Override
    public void setInput(Object input) {
        Control control = getControl();
        if (control == null || control.isDisposed()) {
            throw new IllegalStateException("Need an underlying widget to be able to set the input." + //$NON-NLS-1$
                    "(Has the widget been disposed?)"); //$NON-NLS-1$
        }
        Assert.isTrue(getContentProvider() != null,
                "Instances of ContentViewer must have a content provider assigned before the setInput method is called."); //$NON-NLS-1$

        Object oldInput = getInput();
        contentProvider.inputChanged(this, oldInput, input);
        this.input = input;

        // call input hook
        inputChanged(this.input, oldInput);
    }

    /**
     * Sets the label provider for this viewer.
     * <p>
     * The <code>ContentViewer</code> implementation of this method ensures that the
     * given label provider is connected to this viewer and the
     * former label provider is disconnected from this viewer.
     * Overriding this method is generally not required;
     * however, if overriding in a subclass,
     * <code>super.setLabelProvider</code> must be invoked.
     * </p>
     *
     * @param labelProvider the label provider, or <code>null</code> if none
     */
    public void setLabelProvider(IBaseLabelProvider labelProvider) {
        IBaseLabelProvider oldProvider = this.labelProvider;
        // If it hasn't changed, do nothing.
        // This also ensures that the provider is not disposed
        // if set a second time.
        if (labelProvider == oldProvider) {
            return;
        }
        if (oldProvider != null) {
            oldProvider.removeListener(this.labelProviderListener);
        }
        this.labelProvider = labelProvider;
        if (labelProvider != null) {
            labelProvider.addListener(this.labelProviderListener);
        }
        refresh();

        // Dispose old provider after refresh, so that items never refer to stale images.
        if (oldProvider != null) {
            internalDisposeLabelProvider(oldProvider);
        }
    }

    /**
     * @param oldProvider
     *
     * @since 3.4
     */
    void internalDisposeLabelProvider(IBaseLabelProvider oldProvider) {
        oldProvider.dispose();
    }
}