blob: 0f6bdb70db97d9238fe196a683c67c9b637676c2 [file] [log] [blame]
/*******************************************************************************
* 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);
}
}