org.eclipse.ui.internal.statushandlers.WorkbenchStatusDialogManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.ui.internal.statushandlers.WorkbenchStatusDialogManagerImpl.java

Source

/*******************************************************************************
 * Copyright (c) 2009, 2010 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.ui.internal.statushandlers;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.internal.WorkbenchPlugin;
import org.eclipse.ui.progress.IProgressConstants;
import org.eclipse.ui.statushandlers.IStatusAdapterConstants;
import org.eclipse.ui.statushandlers.StatusAdapter;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.ui.statushandlers.StatusManager.INotificationTypes;

/**
 * <p>
 * This class is the actual dialog manager. Status dialog is a very bad thing to
 * manage, because it can switch its modality. As you know this is not possible,
 * so dialog is closed and then reopened. This approach makes impossible to keep
 * dialog data inside dialog class, because the dialog will be disposed.
 * </p>
 * <p>
 * To overcome this issue, a {@link Map} dialogState is introduced, which holds
 * the actual state (and configuration) of the dialog. This map is passed to all
 * dialog subcomponents.
 * </p>
 * <p>
 * Dialog state variables are defined in {@link IStatusDialogConstants}.
 * </p>
 * 
 * @since 3.6
 * @noextend This class is not intended to be subclassed by clients.
 * @noinstantiate This class is not intended to be instantiated by clients.
 */
public class WorkbenchStatusDialogManagerImpl {

    static final QualifiedName HINT = new QualifiedName(IStatusAdapterConstants.PROPERTY_PREFIX, "hint"); //$NON-NLS-1$

    private final class StatusDialogDisposeListener implements DisposeListener {

        public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) {
            cleanUp();
        }
    }

    private DisposeListener disposeListener = new StatusDialogDisposeListener();

    /**
     * This field stores the real dialog that appears to the user.
     */
    private InternalDialog dialog;

    /**
     * This variable holds the real state of the dialog.
     */
    private Map dialogState = new HashMap();

    /**
     * Returns whether the given StatusAdapter object should be displayed.
     * 
     * @param statusAdapter
     *            a status object
     * @return <code>true</code> if the given status should be displayed, and
     *         <code>false</code> otherwise
     * @see org.eclipse.core.runtime.IStatus#matches(int)
     */
    public boolean shouldAccept(StatusAdapter statusAdapter) {
        IStatus status = statusAdapter.getStatus();
        IStatus[] children = status.getChildren();
        int mask = ((Integer) dialogState.get(IStatusDialogConstants.MASK)).intValue();
        boolean handleOKStatuses = ((Boolean) dialogState.get(IStatusDialogConstants.HANDLE_OK_STATUSES))
                .booleanValue();
        if (children == null || children.length == 0) {
            return status.matches(mask) || (handleOKStatuses && status.isOK());
        }
        for (int i = 0; i < children.length; i++) {
            if (children[i].matches(mask)) {
                return true;
            }
        }
        if (handleOKStatuses && status.isOK()) {
            return true;
        }
        return false;
    }

    /**
     * Creates workbench status dialog.
     * 
     * @param displayMask
     *            the mask used to filter the handled <code>StatusAdapter</code>
     *            objects, the mask is a logical sum of status severities
     * @param dialogTitle
     *            the title of the dialog. If null, than default will be used.
     */
    public WorkbenchStatusDialogManagerImpl(int displayMask, String dialogTitle) {

        Assert.isNotNull(Display.getCurrent(), "WorkbenchStatusDialogManager must be instantiated in UI thread"); //$NON-NLS-1$

        dialogState = initDialogState(dialogState, displayMask, dialogTitle);
    }

    /**
     * This method creates the initial state of the dialog.
     * 
     * @param dialogState
     *            - the map to fill in.
     * @param displayMask
     *            - the mask suggesting which statuses should be displayed
     * @param dialogTitle
     *            - the dialog title.
     * @return populated dialogState
     */
    public Map initDialogState(Map dialogState, int displayMask, String dialogTitle) {
        dialogState.put(IStatusDialogConstants.MASK, new Integer(displayMask));
        dialogState.put(IStatusDialogConstants.TITLE,
                dialogTitle == null ? JFaceResources.getString("Problem_Occurred") : //$NON-NLS-1$
                        dialogTitle);
        dialogState.put(IStatusDialogConstants.HANDLE_OK_STATUSES, Boolean.FALSE);

        dialogState.put(IStatusDialogConstants.SHOW_SUPPORT, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.ENABLE_DEFAULT_SUPPORT_AREA, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.DETAILS_OPENED, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.TRAY_OPENED, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.HIDE_SUPPORT_BUTTON, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.STATUS_ADAPTERS, Collections.synchronizedSet(new HashSet()));
        dialogState.put(IStatusDialogConstants.STATUS_MODALS, new HashMap());
        dialogState.put(IStatusDialogConstants.LABEL_PROVIDER, new LabelProviderWrapper(dialogState));
        dialogState.put(IStatusDialogConstants.MODALITY_SWITCH, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.ANIMATION, Boolean.TRUE);
        return dialogState;
    }

    /**
     * <p>
     * Adds a new {@link StatusAdapter} to the status adapters list in the
     * dialog.
     * </p>
     * <p>
     * If the dialog is already visible, the status adapter will be shown
     * immediately. Otherwise, the dialog with the added status adapter will
     * show up, if all conditions below are false.
     * <ul>
     * <li>the status adapter has
     * {@link IProgressConstants#NO_IMMEDIATE_ERROR_PROMPT_PROPERTY} set to true</li>
     * </ul>
     * </p>
     * <p>
     * All not shown status adapters will be displayed as soon as the dialog
     * shows up.
     * </p>
     * 
     * @param modal
     *            <code>true</code> if the dialog should be modal,
     *            <code>false</code> otherwise
     * @param statusAdapter
     *            the status adapter
     */
    public void addStatusAdapter(final StatusAdapter statusAdapter, final boolean modal) {
        if (ErrorDialog.AUTOMATED_MODE == true) {
            return;
        }
        try {
            doAddStatusAdapter(statusAdapter, modal);
        } catch (Exception e) {
            // if dialog is open, dispose it (and all child controls)
            if (!isDialogClosed()) {
                dialog.getShell().dispose();
            }
            // reset the state
            cleanUp();
            // log original problem
            // TODO: check if is it possible to discover duplicates
            WorkbenchPlugin.log(statusAdapter.getStatus());
            // log the problem with status handling
            WorkbenchPlugin.log(e);
            e.printStackTrace();
        }
    }

    private boolean isDialogClosed() {
        return dialog == null || dialog.getShell() == null || dialog.getShell().isDisposed();
    }

    private void cleanUp() {
        dialog = null;
        getErrors().clear();
        getModals().clear();
        dialogState.put(IStatusDialogConstants.DETAILS_OPENED, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.TRAY_OPENED, Boolean.FALSE);
        dialogState.put(IStatusDialogConstants.MODALITY_SWITCH, Boolean.FALSE);
    }

    private void doAddStatusAdapter(final StatusAdapter statusAdapter, final boolean modal) {

        if (!PlatformUI.isWorkbenchRunning()) {
            // we are shutting down, so just log
            WorkbenchPlugin.log(statusAdapter.getStatus());
            return;
        }

        // if statusAdapter does not match the mask, ignore it
        if (!shouldAccept(statusAdapter)) {
            return;
        }

        // Add the error in the UI thread to ensure thread safety in the
        // dialog
        if (isDialogClosed()) {

            getErrors().add(statusAdapter);
            getModals().put(statusAdapter, new Boolean(modal));
            // Delay prompting if the status adapter property is set
            if (shouldPrompt(statusAdapter)) {
                // notify all interested parties that status adapters will be
                // handled
                StatusManager.getManager().fireNotification(INotificationTypes.HANDLED,
                        (StatusAdapter[]) getErrors().toArray(new StatusAdapter[] {}));

                if (dialog == null) {
                    setSelectedStatusAdapter(statusAdapter);
                    dialog = new InternalDialog(dialogState, shouldBeModal());
                    dialog.create();
                    dialog.getShell().addDisposeListener(disposeListener);
                    boolean showSupport = ((Boolean) dialogState.get(IStatusDialogConstants.SHOW_SUPPORT))
                            .booleanValue();
                    if (showSupport) {
                        dialog.openTray();
                        dialog.getShell().setLocation(dialog.getInitialLocation(dialog.getShell().getSize()));
                    }
                    dialog.open();
                }
                dialog.refresh();
                dialog.refreshDialogSize();
            }

        } else {
            StatusManager.getManager().fireNotification(INotificationTypes.HANDLED,
                    new StatusAdapter[] { statusAdapter });
            if (statusAdapter.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY) != null) {
                statusAdapter.setProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, Boolean.FALSE);
            }
            openStatusDialog(modal, statusAdapter);
        }
    }

    /**
     * Gets a collection of status adapters that were passed to the dialog.
     * 
     * @return collection of {@link StatusAdapter} objects
     */
    public Collection getStatusAdapters() {
        return Collections.unmodifiableCollection(getErrors());
    }

    /**
     * Opens status dialog with particular statusAdapter selected.
     * 
     * @param modal
     *            decides if window is modal or not.
     * @param statusAdapter
     *            status adapter to be selected.
     */
    private void openStatusDialog(final boolean modal, final StatusAdapter statusAdapter) {
        getErrors().add(statusAdapter);
        getModals().put(statusAdapter, new Boolean(modal));
        boolean shouldBeModal = shouldBeModal();
        if (shouldBeModal ^ dialog.isModal()) {
            dialog.getShell().removeDisposeListener(disposeListener);
            dialogState.put(IStatusDialogConstants.MODALITY_SWITCH, Boolean.TRUE);
            dialog.close();
            dialog = new InternalDialog(dialogState, modal);
            dialog.open();
            dialog.getShell().addDisposeListener(disposeListener);
            dialogState.put(IStatusDialogConstants.MODALITY_SWITCH, Boolean.FALSE);
        }
        dialog.refresh();
    }

    /**
     * Sets current status adapter.
     * 
     * @param statusAdapter
     *            The statusAdapter to set.
     */
    public void setSelectedStatusAdapter(StatusAdapter statusAdapter) {
        dialogState.put(IStatusDialogConstants.CURRENT_STATUS_ADAPTER, statusAdapter);
    }

    /**
     * Sets new label provider for the status list. This label provider is used
     * also to display the second message on the dialog if only one status is
     * available.
     * 
     * <p>
     * This method is no longer recommended to use as it is impossible to
     * achieve consistent behavior after changing only one label provider.
     * </p>
     * 
     * @deprecated As of 3.5 {@link #setMessageDecorator} is recommended. 
     * 
     * @param labelProvider
     *            a label provider to be used when displaying status adapters.
     */
    public void setStatusListLabelProvider(ITableLabelProvider labelProvider) {
        Assert.isLegal(labelProvider != null, "Label Provider cannot be null"); //$NON-NLS-1$
        dialogState.put(IStatusDialogConstants.CUSTOM_LABEL_PROVIDER, labelProvider);
    }

    /**
     * Decides if dialog should be modal. Dialog will be modal if any of the
     * statuses contained by StatusAdapters had been reported with
     * {@link StatusManager#BLOCK} flag.
     * 
     * @return true if any StatusHandler should be displayed in modal window
     */
    public boolean shouldBeModal() {
        Map modals = (Map) dialogState.get(IStatusDialogConstants.STATUS_MODALS);
        for (Iterator it = modals.keySet().iterator(); it.hasNext();) {
            Object o = it.next();
            Object value = modals.get(o);
            if (value instanceof Boolean) {
                Boolean b = (Boolean) value;
                if (b.booleanValue()) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Checks if the user should be prompted immediately about
     * {@link StatusAdapter}
     * 
     * @param statusAdapter
     *            to be checked.
     * @return true if the statusAdapter should be prompted, false otherwise.
     */
    public boolean shouldPrompt(final StatusAdapter statusAdapter) {
        Object noPromptProperty = statusAdapter.getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY);

        boolean prompt = true;
        if (noPromptProperty instanceof Boolean) {
            prompt = !((Boolean) noPromptProperty).booleanValue();
        }
        return prompt;
    }

    /**
     * Gets the shell of the managed dialog.
     * 
     * @return Shell or null
     * 
     */
    public Shell getShell() {
        if (this.dialog == null)
            return null;
        return this.dialog.getShell();
    }

    /**
     * <p>
     * This methods sets up the decorator, which is used to modify displayed
     * strings extracted from StatusAdapter. The decorator should be used to
     * remove technical codes from the dialog, f.e. following message
     * "<i>ERR2008 Invalid password</i>" can be translated into
     * "<i>Invalid password</i>".
     * </p>
     * <p>
     * The decorator will be applied only to messages extracted from
     * StatusAdapter (predefined messages like
     * "This status has children statuses. See 'Details' for more information."
     * are not affected.
     * </p>
     * <p>
     * This method should not be used together with
     * {@link #setStatusListLabelProvider(ITableLabelProvider)}.
     * </p>
     * 
     * @param decorator
     *            - the decorator to be set. Only
     *            {@link ILabelDecorator#decorateText(String, Object)} method
     *            will be used. This method should return <code>null</code> if
     *            and only if the first argument is null. StatusAdapter is
     *            passed as second parameter. Other methods should have default
     *            behavior as they may be used in future versions of the dialog.
     * @since 3.5
     */
    public void setMessageDecorator(ILabelDecorator decorator) {
        dialogState.put(IStatusDialogConstants.DECORATOR, decorator);
    }

    /**
     * This method sets various properties on the manager.
     * 
     * @param key
     *            a key of the property to be set.
     * @param value
     *            a value of the property to be set. The value must be of type
     *            specified by the property key. <code>null</code> should never
     *            be passed unless the property key javadoc allows for that.
     * @since 3.5
     */
    public void setProperty(Object key, Object value) {
        dialogState.put(key, value);
    }

    /**
     * This method gets various dialog properties.
     * 
     * @param key
     *            a key of the property to be get.
     * @return a value of the property. The value will be of type specified by
     *         the property key. <code>null</code> can be returned.
     * @since 3.5
     */
    public Object getProperty(Object key) {
        if (key == IStatusDialogConstants.SHELL) {
            return getShell();
        }
        if (key == IStatusDialogConstants.MANAGER_IMPL) {
            return this;
        }
        return dialogState.get(key);
    }

    /**
     * This method makes the dialog to be similar to the JFace ErrorDialog. The
     * dialog handles {@link StatusAdapter}s wrapping {@link IStatus} with
     * severity {@link IStatus#OK}, does not display the link to the error log,
     * does not display the link to the support area but always opens it.
     * 
     * @see ErrorDialog
     * @since 3.6
     */
    public void enableErrorDialogCompatibility() {
        setProperty(IStatusDialogConstants.ERRORLOG_LINK, Boolean.FALSE);
        setProperty(IStatusDialogConstants.HANDLE_OK_STATUSES, Boolean.TRUE);
        setProperty(IStatusDialogConstants.SHOW_SUPPORT, Boolean.TRUE);
        setProperty(IStatusDialogConstants.HIDE_SUPPORT_BUTTON, Boolean.TRUE);
    }

    /**
     * This method is public for testing purposes only.
     * 
     * @return Returns the dialog.
     */
    public InternalDialog getDialog() {
        return dialog;
    }

    /**
     * This method is public for testing purposes only.
     * 
     * @param dialog
     *            The dialog to set.
     */
    public void setDialog(InternalDialog dialog) {
        this.dialog = dialog;
    }

    /**
     * This method is public for testing purposes only.
     * 
     * @return dialog state.
     */
    public Map getDialogState() {
        return dialogState;
    }

    /**
     * Utility method to access StatusAdapters
     * 
     * @return Collection of StatusAdapters
     */
    private Collection getErrors() {
        return (Collection) dialogState.get(IStatusDialogConstants.STATUS_ADAPTERS);
    }

    /**
     * Utility method to access StatusAdapter modal flag.
     * 
     * @return Collection of StatusAdapter modal flag.
     */
    private Map getModals() {
        return (Map) dialogState.get(IStatusDialogConstants.STATUS_MODALS);
    }
}