| /******************************************************************************* |
| * Copyright (c) 2008 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.statushandlers; |
| |
| import java.net.URL; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.eclipse.jface.action.ContributionItem; |
| import org.eclipse.jface.action.IAction; |
| import org.eclipse.jface.action.IContributionItem; |
| import org.eclipse.jface.action.ToolBarManager; |
| import org.eclipse.jface.dialogs.Dialog; |
| import org.eclipse.jface.dialogs.DialogTray; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.dialogs.ErrorSupportProvider; |
| import org.eclipse.jface.dialogs.IDialogConstants; |
| import org.eclipse.jface.dialogs.MessageDialogWithToggle; |
| import org.eclipse.jface.dialogs.TrayDialog; |
| import org.eclipse.jface.layout.GridLayoutFactory; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.resource.LocalResourceManager; |
| import org.eclipse.jface.resource.ResourceManager; |
| import org.eclipse.jface.util.Policy; |
| import org.eclipse.jface.viewers.IContentProvider; |
| import org.eclipse.jface.viewers.ILabelProviderListener; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredContentProvider; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.ITableLabelProvider; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TableViewer; |
| import org.eclipse.jface.viewers.Viewer; |
| import org.eclipse.jface.viewers.ViewerComparator; |
| import org.eclipse.jface.window.Window; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.dnd.Clipboard; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.events.MouseEvent; |
| import org.eclipse.swt.events.MouseListener; |
| import org.eclipse.swt.events.SelectionAdapter; |
| import org.eclipse.swt.events.SelectionEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.Cursor; |
| import org.eclipse.swt.graphics.GC; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.ImageData; |
| import org.eclipse.swt.graphics.PaletteData; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.swt.graphics.Rectangle; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.layout.GridLayout; |
| import org.eclipse.swt.widgets.Button; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.swt.widgets.ToolBar; |
| import org.eclipse.swt.widgets.ToolItem; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.internal.IWorkbenchGraphicConstants; |
| import org.eclipse.ui.internal.WorkbenchImages; |
| import org.eclipse.ui.internal.WorkbenchMessages; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.internal.progress.ProgressManager; |
| import org.eclipse.ui.internal.progress.ProgressManagerUtil; |
| import org.eclipse.ui.internal.progress.ProgressMessages; |
| import org.eclipse.ui.internal.statushandlers.DefaultDetailsArea; |
| import org.eclipse.ui.internal.statushandlers.StackTraceSupportArea; |
| import org.eclipse.ui.progress.IProgressConstants; |
| |
| import com.ibm.icu.text.DateFormat; |
| |
| /** |
| * <p> |
| * The {@link WorkbenchStatusDialogManager} is a utility class for displaying |
| * one or more messages (errors, warnings or infos) to the user. The dialog |
| * supplied has a Details button that opens/closes the details area. The default |
| * {@link AbstractStatusAreaProvider} displays a tree of {@link StatusAdapter}s |
| * related to the selected item on the messages list. The dialog also hasa |
| * Support button that opens/closes the support area which contains the provided |
| * {@link AbstractStatusAreaProvider}. The Support button is disabled and not |
| * visible unless |
| * {@link WorkbenchStatusDialogManager#enableDefaultSupportArea(boolean)} is |
| * invoked. |
| * </p> |
| * |
| * <p> |
| * The default details area can be replaced using |
| * {@link WorkbenchStatusDialogManager#setDetailsAreaProvider(AbstractStatusAreaProvider)} |
| * </p> |
| * |
| * <p> |
| * The default support area can be replaced using |
| * {@link WorkbenchStatusDialogManager#setSupportAreaProvider(AbstractStatusAreaProvider)} |
| * or {@link Policy#setErrorSupportProvider(ErrorSupportProvider)}. |
| * </p> |
| * |
| * <p> |
| * The manager can switch from a non-modal dialog to a modal dialog. See |
| * {@link #addStatusAdapter(StatusAdapter, boolean)} |
| * </p> |
| * |
| * <p> |
| * IMPORTANT: This class is <em>not</em> intended to be subclassed by clients. |
| * </p> |
| * |
| * @see Policy#setErrorSupportProvider(ErrorSupportProvider) |
| * @see ErrorSupportProvider |
| * @see AbstractStatusAreaProvider |
| * @since 3.4 |
| */ |
| public class WorkbenchStatusDialogManager { |
| |
| /** |
| * This class is responsible for managing details area. |
| * |
| * @since 3.4 |
| */ |
| private final class DetailsAreaManager { |
| private AbstractStatusAreaProvider provider = null; |
| private Control control = null; |
| |
| /** |
| * Closes the details area |
| */ |
| public void close() { |
| if (control != null && !control.isDisposed()) { |
| control.dispose(); |
| } |
| } |
| |
| /** |
| * This method is responsible for creating details area on the specified |
| * Composite and displaying specified StatusAdapter |
| * |
| * @param parent |
| * A composite on which should be the details area created. |
| * @param statusAdapter |
| * StatusAdapter for which should be the details area |
| * created. |
| */ |
| public void createDetailsArea(Composite parent, |
| StatusAdapter statusAdapter) { |
| Composite container = new Composite(parent, SWT.NONE); |
| container.setLayout(GridLayoutFactory.fillDefaults().create()); |
| container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); |
| getProvider().createSupportArea(container, statusAdapter); |
| control = container; |
| } |
| |
| /** |
| * Returns current detail area provider. |
| * |
| * @return current detail area provider. |
| */ |
| private AbstractStatusAreaProvider getProvider() { |
| if (provider == null) { |
| provider = new DefaultDetailsArea( |
| WorkbenchStatusDialogManager.this); |
| } |
| return provider; |
| } |
| |
| /** |
| * This method allows to check if the details area is open (physically |
| * constructed). |
| * |
| * @return true if the area is open, false otherwise |
| */ |
| public boolean isOpen() { |
| if (control == null || control.isDisposed()) { |
| return false; |
| } |
| return true; |
| } |
| |
| /** |
| * This method sets the details area provider. If null is set, the |
| * default area provider (status tree) will be used. |
| * |
| * @param provider |
| * A provider that will create contents for details area. |
| */ |
| public void setDetailsAreaProvider(AbstractStatusAreaProvider provider) { |
| this.provider = provider; |
| } |
| } |
| |
| /** |
| * Parent window actually does not use its Shell to build dialog on. The |
| * window passes the shell to the InternalDialog, and it can do switching |
| * modality and recreate the window silently. |
| * |
| * @since 3.4 |
| */ |
| private class InternalDialog extends TrayDialog { |
| |
| private WorkbenchStatusDialogManager statusDialog; |
| |
| /** |
| * Instantiates the internal dialog on the given shell. Created dialog |
| * uses statusDialog methods to create its contents. |
| * |
| * @param parentShell - |
| * a parent shell for the dialog |
| * @param statusDialog - |
| * a dialog from which methods should be used to create |
| * contents |
| * @param modal - |
| * true if created dialog should be modal, false otherwise. |
| */ |
| public InternalDialog(Shell parentShell, |
| WorkbenchStatusDialogManager statusDialog, boolean modal) { |
| super(parentShell); |
| |
| this.statusDialog = statusDialog; |
| setShellStyle(SWT.RESIZE | SWT.MAX | SWT.MIN | getShellStyle()); |
| setBlockOnOpen(false); |
| |
| if (!modal) { |
| setShellStyle(~SWT.APPLICATION_MODAL & getShellStyle()); |
| } |
| } |
| |
| protected void buttonPressed(int id) { |
| if (id == GOTO_ACTION_ID) { |
| IAction gotoAction = getGotoAction(); |
| if (gotoAction != null) { |
| if (isPromptToClose()) { |
| okPressed(); // close the dialog |
| gotoAction.run(); // run the goto action |
| } |
| } |
| } |
| if (id == IDialogConstants.DETAILS_ID) { |
| // was the details button pressed? |
| detailsOpened = toggleDetailsArea(); |
| } else { |
| super.buttonPressed(id); |
| } |
| } |
| |
| /* |
| * (non-Javadoc) Method declared in Window. |
| */ |
| final protected void configureShell(Shell shell) { |
| super.configureShell(shell); |
| if (title != null) { |
| shell.setText(title); |
| } |
| } |
| |
| public int convertHeightInCharsToPixels(int chars) { |
| return super.convertHeightInCharsToPixels(chars); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#convertHorizontalDLUsToPixels(int) |
| */ |
| public int convertHorizontalDLUsToPixels(int dlus) { |
| return super.convertHorizontalDLUsToPixels(dlus); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#convertVerticalDLUsToPixels(int) |
| */ |
| public int convertVerticalDLUsToPixels(int dlus) { |
| return super.convertVerticalDLUsToPixels(dlus); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#convertWidthInCharsToPixels(int) |
| */ |
| public int convertWidthInCharsToPixels(int chars) { |
| return super.convertWidthInCharsToPixels(chars); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#createButton(org.eclipse.swt.widgets.Composite, |
| * int, java.lang.String, boolean) |
| */ |
| public Button createButton(Composite parent, int id, String label, |
| boolean defaultButton) { |
| return super.createButton(parent, id, label, defaultButton); |
| } |
| |
| /** |
| * Status dialog button should be aligned SWT.END. |
| */ |
| protected void setButtonLayoutData(Button button) { |
| GridData data = new GridData(SWT.END, SWT.FILL, false, false); |
| int widthHint = convertHorizontalDLUsToPixels(IDialogConstants.BUTTON_WIDTH); |
| Point minSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); |
| data.widthHint = Math.max(widthHint, minSize.x); |
| button.setLayoutData(data); |
| } |
| |
| protected Control createButtonBar(Composite parent) { |
| return statusDialog.createButtonBar(parent); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets.Composite) |
| */ |
| protected Control createDialogArea(Composite parent) { |
| return statusDialog.createDialogArea(parent); |
| } |
| |
| public Button getButton(int id) { |
| return super.getButton(id); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#initializeBounds() |
| */ |
| protected void initializeBounds() { |
| super.initializeBounds(); |
| this.statusDialog.initializeBounds(); |
| } |
| |
| /** |
| * This function checks if the dialog is modal. |
| * |
| * @return true if the dialog is modal, false otherwise |
| * |
| */ |
| public boolean isModal() { |
| return ((getShellStyle() & SWT.APPLICATION_MODAL) == SWT.APPLICATION_MODAL); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.Dialog#isResizable() |
| */ |
| protected boolean isResizable() { |
| return true; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.window.Window#open() |
| */ |
| public int open() { |
| if (shouldDisplay(statusAdapter, displayMask)) { |
| int result = super.open(); |
| if (modalitySwitch) { |
| if (detailsOpened) { |
| showDetailsArea(); |
| } |
| if (trayOpened) { |
| openTray(supportTray); |
| } |
| } |
| return result; |
| } |
| setReturnCode(OK); |
| return OK; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jface.dialogs.TrayDialog#closeTray() |
| */ |
| public void closeTray() throws IllegalStateException { |
| super.closeTray(); |
| trayOpened = false; |
| if (launchTrayButton != null && !launchTrayButton.isDisposed()) { |
| launchTrayButton.setEnabled(supportTray.providesSupport() && !trayOpened); |
| } |
| } |
| |
| } |
| |
| /** |
| * This class is responsible for disposing dialog elements when the dialog |
| * is closed. |
| * |
| * @since 3.4 |
| * |
| */ |
| private final class StatusDialogDisposeListener implements DisposeListener { |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.swt.events.DisposeListener#widgetDisposed(org.eclipse.swt.events.DisposeEvent) |
| */ |
| public void widgetDisposed(org.eclipse.swt.events.DisposeEvent e) { |
| dialog = null; |
| statusListViewer = null; |
| statusAdapter = null; |
| errors.clear(); |
| modals.clear(); |
| } |
| } |
| |
| /** |
| * This class is responsible for displaying the support area on the right |
| * side of the status dialog. |
| * |
| * @since 3.4 |
| * |
| */ |
| private class SupportTray extends DialogTray implements |
| ISelectionChangedListener { |
| |
| private IContributionItem closeAction; |
| private Image normal; |
| private Image hover; |
| |
| private Composite supportArea; |
| private Composite supportAreaContent; |
| |
| private StatusAdapter lastSelectedStatus; |
| private boolean defaultSupportAreaEnabled; |
| |
| /** |
| * Creates any actions needed by the tray. |
| */ |
| private void createActions() { |
| createImages(); |
| closeAction = new ContributionItem() { |
| public void fill(ToolBar parent, int index) { |
| final ToolItem item = new ToolItem(parent, SWT.PUSH); |
| item.setImage(normal); |
| item.setHotImage(hover); |
| item.setToolTipText(JFaceResources.getString("close")); //$NON-NLS-1$ |
| item.addListener(SWT.Selection, new Listener() { |
| public void handleEvent(Event event) { |
| trayOpened = false; |
| |
| // close the tray |
| closeTray(); |
| |
| // set focus back to shell |
| getShell().setFocus(); |
| } |
| }); |
| } |
| }; |
| |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.dialogs.DialogTray#createContents(org.eclipse.swt.widgets.Composite) |
| */ |
| protected Control createContents(Composite parent) { |
| Composite container = new Composite(parent, SWT.NONE); |
| |
| // nothing to display. Should never happen, cause button is disabled |
| // when nothing to display. |
| |
| if (!providesSupport()) { |
| return container; |
| } |
| |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = layout.marginHeight = 0; |
| layout.verticalSpacing = 0; |
| container.setLayout(layout); |
| GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| container.setLayoutData(layoutData); |
| |
| container.addListener(SWT.Dispose, new Listener() { |
| public void handleEvent(Event event) { |
| // dispose event |
| } |
| }); |
| |
| ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); |
| toolBarManager.createControl(container); |
| GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_END); |
| gd.grabExcessHorizontalSpace = true; |
| toolBarManager.getControl().setLayoutData(gd); |
| Label separator = new Label(container, SWT.SEPARATOR |
| | SWT.HORIZONTAL); |
| gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL); |
| gd.heightHint = 1; |
| separator.setLayoutData(gd); |
| |
| createActions(); |
| toolBarManager.add(closeAction); |
| |
| toolBarManager.update(true); |
| |
| supportArea = new Composite(container, SWT.NONE); |
| layout = new GridLayout(); |
| layout.marginWidth = layout.marginHeight = 0; |
| layout.verticalSpacing = 0; |
| supportArea.setLayout(layout); |
| gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL |
| | GridData.VERTICAL_ALIGN_FILL); |
| gd.horizontalSpan = 1; |
| gd.grabExcessHorizontalSpace = true; |
| gd.grabExcessVerticalSpace = true; |
| supportArea.setLayoutData(gd); |
| |
| // if only one status adapter is displayed, |
| // selection listener does not work, so it is necessary to |
| // set the last selected status manually |
| if (getStatusAdapters().size() == 1) { |
| lastSelectedStatus = statusAdapter; |
| } |
| |
| if (lastSelectedStatus != null) |
| createSupportArea(supportArea, lastSelectedStatus); |
| |
| Point shellSize = getShell().getSize(); |
| Point desiredSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| |
| if(desiredSize.y > shellSize.y){ |
| getShell().setSize(shellSize.x, Math.min(desiredSize.y,500)); |
| } |
| |
| return container; |
| } |
| |
| /** |
| * Creates any custom needed by the tray, such as the close button. |
| */ |
| private void createImages() { |
| Display display = Display.getCurrent(); |
| int[] shape = new int[] { 3, 3, 5, 3, 7, 5, 8, 5, 10, 3, 12, 3, 12, |
| 5, 10, 7, 10, 8, 12, 10, 12, 12, 10, 12, 8, 10, 7, 10, 5, |
| 12, 3, 12, 3, 10, 5, 8, 5, 7, 3, 5 }; |
| |
| /* |
| * Use magenta as transparency color since it is used infrequently. |
| */ |
| Color border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); |
| Color background = display |
| .getSystemColor(SWT.COLOR_LIST_BACKGROUND); |
| Color backgroundHot = new Color(display, new RGB(252, 160, 160)); |
| Color transparent = display.getSystemColor(SWT.COLOR_MAGENTA); |
| |
| PaletteData palette = new PaletteData(new RGB[] { |
| transparent.getRGB(), border.getRGB(), background.getRGB(), |
| backgroundHot.getRGB() }); |
| ImageData data = new ImageData(16, 16, 8, palette); |
| data.transparentPixel = 0; |
| |
| normal = new Image(display, data); |
| normal.setBackground(transparent); |
| GC gc = new GC(normal); |
| gc.setBackground(background); |
| gc.fillPolygon(shape); |
| gc.setForeground(border); |
| gc.drawPolygon(shape); |
| gc.dispose(); |
| |
| hover = new Image(display, data); |
| hover.setBackground(transparent); |
| gc = new GC(hover); |
| gc.setBackground(backgroundHot); |
| gc.fillPolygon(shape); |
| gc.setForeground(border); |
| gc.drawPolygon(shape); |
| gc.dispose(); |
| |
| backgroundHot.dispose(); |
| } |
| |
| /** |
| * Create the area for extra error support information. |
| * |
| * @param parent |
| * A composite on which should be the support area created. |
| * @param statusAdapter |
| * StatusAdapter for which should be the support area |
| * created. |
| */ |
| private void createSupportArea(Composite parent, |
| StatusAdapter statusAdapter) { |
| |
| ErrorSupportProvider provider = Policy.getErrorSupportProvider(); |
| |
| if (userSupportProvider != null) { |
| provider = userSupportProvider; |
| } |
| |
| if (defaultSupportAreaEnabled && provider == null) { |
| provider = new StackTraceSupportArea(); |
| } |
| |
| // default support area was disabled |
| if (provider == null) |
| return; |
| |
| if (supportAreaContent != null) |
| supportAreaContent.dispose(); |
| |
| supportAreaContent = new Composite(parent, SWT.FILL); |
| |
| GridData supportData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| supportAreaContent.setLayoutData(supportData); |
| if (supportAreaContent.getLayout() == null) { |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = 0; |
| layout.marginHeight = 0; |
| supportAreaContent.setLayout(layout); // Give it a default |
| // layout |
| } |
| |
| if (provider instanceof AbstractStatusAreaProvider) { |
| ((AbstractStatusAreaProvider) provider).createSupportArea( |
| supportAreaContent, statusAdapter); |
| } else { |
| provider.createSupportArea(supportAreaContent, statusAdapter |
| .getStatus()); |
| } |
| } |
| |
| /** |
| * This method manages the enablement of the default support area. |
| * |
| * @param enable |
| * true enables, false disables. |
| */ |
| public void enableDefaultSupportArea(boolean enable) { |
| this.defaultSupportAreaEnabled = enable; |
| } |
| |
| private StatusAdapter getStatusAdapterFromEvent( |
| SelectionChangedEvent event) { |
| |
| ISelection selection = event.getSelection(); |
| |
| if (selection instanceof StructuredSelection) { |
| StructuredSelection structuredSelection = (StructuredSelection) selection; |
| Object element = structuredSelection.getFirstElement(); |
| if (element instanceof StatusAdapter) { |
| return (StatusAdapter) element; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Checks if the support dialog has any support areas. |
| * |
| * @return true if support dialog has any support areas to display, |
| * false otherwise |
| * |
| */ |
| private boolean providesSupport() { |
| if (Policy.getErrorSupportProvider() != null) { |
| return true; |
| } |
| if (userSupportProvider != null) { |
| return true; |
| } |
| return defaultSupportAreaEnabled; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) |
| */ |
| public void selectionChanged(SelectionChangedEvent event) { |
| lastSelectedStatus = getStatusAdapterFromEvent(event); |
| if (supportArea != null && !supportArea.isDisposed()) { |
| if (lastSelectedStatus != null) { |
| createSupportArea(supportArea, lastSelectedStatus); |
| supportArea.layout(true); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Preference used to indicate whether the user should be prompted to |
| * confirm the execution of the job's goto action |
| */ |
| private static final String PREF_SKIP_GOTO_ACTION_PROMPT = "pref_skip_goto_action_prompt"; //$NON-NLS-1$ |
| |
| /** |
| * The id of the goto action button |
| */ |
| private static final int GOTO_ACTION_ID = IDialogConstants.CLIENT_ID + 1; |
| |
| /** |
| * Returns whether the given StatusAdapter object should be displayed. |
| * |
| * @param statusAdapter |
| * a status object |
| * @param mask |
| * a mask as per <code>IStatus.matches</code> |
| * @return <code>true</code> if the given status should be displayed, and |
| * <code>false</code> otherwise |
| * @see org.eclipse.core.runtime.IStatus#matches(int) |
| */ |
| private static boolean shouldDisplay(StatusAdapter statusAdapter, int mask) { |
| IStatus status = statusAdapter.getStatus(); |
| IStatus[] children = status.getChildren(); |
| if (children == null || children.length == 0) { |
| return status.matches(mask); |
| } |
| for (int i = 0; i < children.length; i++) { |
| if (children[i].matches(mask)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Stores statuses. |
| */ |
| private Collection errors = Collections.synchronizedSet(new HashSet()); |
| |
| /** |
| * Stores information, which statuses should be displayed in modal window. |
| */ |
| private HashMap modals = new HashMap(); |
| |
| /** |
| * This field stores the real dialog that appears to the user. |
| */ |
| private InternalDialog dialog; |
| |
| /** |
| * This composite holds all components of the dialog. |
| */ |
| private Composite dialogArea; |
| |
| /** |
| * This composite is initially scrolled to the 0 x 0 size. When more than |
| * one status arrives, listArea is resized and a list is created on it to |
| * present statuses to the user. |
| */ |
| private Composite listArea; |
| |
| /** |
| * On this composite are presented additional elements for displaying single |
| * status. Currently it is the second label that displays the second most |
| * important message to the user. |
| */ |
| private Composite singleStatusDisplayArea; |
| |
| /** |
| * This label is used to display the second most important message to the |
| * user. It is placed on singleStatusDisplayArea. |
| */ |
| private Label singleStatusLabel; |
| |
| /** |
| * A list from which the user selects statuses. The list is placed on |
| * listArea. |
| */ |
| private TableViewer statusListViewer; |
| |
| /** |
| * A list label provider |
| */ |
| private ITableLabelProvider statusListLabelProvider = new ITableLabelProvider() { |
| ResourceManager manager = new LocalResourceManager(JFaceResources.getResources()); |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener) |
| */ |
| public void addListener(ILabelProviderListener listener) { |
| // Do nothing |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() |
| */ |
| public void dispose() { |
| manager.dispose(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, |
| * int) |
| */ |
| public Image getColumnImage(Object element, int columnIndex) { |
| Image result = null; |
| if (element != null) { |
| StatusAdapter statusAdapter = ((StatusAdapter) element); |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| if (job != null) { |
| result = getIcon(job); |
| } |
| } |
| // if somehow disposed image was received (should not happen) |
| if (result != null && result.isDisposed()) { |
| result = null; |
| } |
| return result; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, |
| * int) |
| */ |
| public String getColumnText(Object element, int columnIndex) { |
| StatusAdapter statusAdapter = (StatusAdapter) element; |
| String text = WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred; |
| if (getStatusAdapters().size() == 1) { |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| if (job != null) { |
| text = getPrimaryMessage(statusAdapter); |
| } else { |
| text = getSecondaryMessage(statusAdapter); |
| } |
| } else { |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| if (job != null) { |
| text = job.getName(); |
| } else { |
| text = getPrimaryMessage(statusAdapter); |
| } |
| } |
| Long timestamp = (Long) statusAdapter |
| .getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY); |
| |
| if (timestamp != null && getStatusAdapters().size() > 1) { |
| String date = DateFormat.getDateTimeInstance(DateFormat.LONG, |
| DateFormat.LONG) |
| .format(new Date(timestamp.longValue())); |
| return NLS.bind(ProgressMessages.JobInfo_Error, (new Object[] { |
| text, date })); |
| } |
| return text; |
| } |
| |
| /* |
| * Get the icon for the job. |
| */ |
| private Image getIcon(Job job) { |
| if (job != null) { |
| Object property = job |
| .getProperty(IProgressConstants.ICON_PROPERTY); |
| |
| // Create an image from the job's icon property or family |
| if (property instanceof ImageDescriptor) { |
| return manager.createImage((ImageDescriptor) property); |
| } else if (property instanceof URL) { |
| return manager.createImage(ImageDescriptor |
| .createFromURL((URL) property)); |
| } else { |
| // Let the progress manager handle the resource management |
| return ProgressManager.getInstance().getIconFor(job); |
| } |
| } |
| return null; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, |
| * java.lang.String) |
| */ |
| public boolean isLabelProperty(Object element, String property) { |
| return false; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener) |
| */ |
| public void removeListener(ILabelProviderListener listener) { |
| // Do nothing |
| } |
| }; |
| |
| /** |
| * This variable holds current details area provider. |
| */ |
| private DetailsAreaManager detailsManager = new DetailsAreaManager(); |
| |
| private DisposeListener disposeListener = new StatusDialogDisposeListener(); |
| |
| /** |
| * The title of the dialog. |
| */ |
| private String title; |
| |
| /** |
| * The current clipboard. To be disposed when closing the dialog. |
| */ |
| private Clipboard clipboard; |
| |
| /** |
| * Filter mask for determining which status items to display. Default allows |
| * for displaying all statuses. |
| */ |
| private int displayMask = 0xFFFF; |
| |
| /** |
| * Currently selected status adapter. |
| */ |
| private StatusAdapter statusAdapter; |
| |
| /** |
| * In this support tray status support providers are displayed. |
| */ |
| private SupportTray supportTray = new WorkbenchStatusDialogManager.SupportTray(); |
| |
| /** |
| * Toolbar on the left botom corner. Allows for opening support tray. |
| */ |
| private ToolBar toolBar; |
| |
| /** |
| * This item is used to launch support tray |
| */ |
| private ToolItem launchTrayButton; |
| |
| /** |
| * This flag indicates if the dialog is switching modality. For now it is |
| * possible only to change from non-modal to modal. |
| */ |
| private boolean modalitySwitch = false; |
| |
| /** |
| * This fields holds the information about dialog position and size when |
| * switching the modality. |
| */ |
| private Rectangle shellBounds; |
| |
| /** |
| * This flag indicates if the details area was opened before switching the |
| * modality or not. |
| */ |
| private boolean detailsOpened = false; |
| |
| /** |
| * This flag indicates if the support area was opened before switching the |
| * modality or not. |
| */ |
| private boolean trayOpened = false; |
| |
| /** |
| * This field contains the support provider set by the user. |
| */ |
| private AbstractStatusAreaProvider userSupportProvider; |
| |
| /** |
| * Main dialog image holder. |
| */ |
| private Label titleImageLabel; |
| |
| /** |
| * Message in the header. |
| */ |
| private Label mainMessageLabel; |
| |
| /** |
| * Header area. |
| */ |
| private Composite titleArea; |
| |
| /** |
| * 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 WorkbenchStatusDialogManager(int displayMask, String dialogTitle) { |
| |
| Assert.isNotNull(Display.getCurrent(), |
| "WorkbenchStatusDialogManager must be instantiated in UI thread"); //$NON-NLS-1$ |
| |
| this.displayMask = displayMask; |
| this.title = dialogTitle == null ? JFaceResources |
| .getString("Problem_Occurred") : //$NON-NLS-1$ |
| dialogTitle; |
| } |
| |
| /** |
| * Creates workbench status dialog. |
| * |
| * @param parentShell |
| * the parent shell for the dialog. It may be null. |
| * @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. |
| * @deprecated As of 3.4 the <code>parentShell<code> is ignored |
| * @see #WorkbenchStatusDialogManager(int, String) |
| */ |
| public WorkbenchStatusDialogManager(Shell parentShell, int displayMask, |
| String dialogTitle) { |
| |
| this(displayMask, dialogTitle); |
| } |
| |
| /** |
| * Creates workbench status dialog. |
| * |
| * @param dialogTitle |
| * the title of the dialog. If null, than default will be used. |
| */ |
| public WorkbenchStatusDialogManager(String dialogTitle) { |
| this(IStatus.INFO | IStatus.WARNING | IStatus.ERROR, dialogTitle); |
| } |
| |
| /** |
| * Creates workbench status dialog. |
| * |
| * @param parentShell |
| * the parent shell for the dialog. It may be null. |
| * @param dialogTitle |
| * the title of the dialog. If null, than default will be used. |
| * @deprecated As of 3.4 the <code>parentShell<code> is ignored |
| * @see #WorkbenchStatusDialogManager(String) |
| */ |
| public WorkbenchStatusDialogManager(Shell parentShell, String dialogTitle) { |
| this(dialogTitle); |
| } |
| |
| /** |
| * <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; |
| } |
| |
| Assert.isNotNull(Display.getCurrent(), |
| "WorkbenchStatusDialogManager#addStatusAdapter must be called from UI thread"); //$NON-NLS-1$ |
| |
| if (!PlatformUI.isWorkbenchRunning()) { |
| // we are shutting down, so just log |
| WorkbenchPlugin.log(statusAdapter.getStatus()); |
| return; |
| } |
| |
| // Add the error in the UI thread to ensure thread safety in the |
| // dialog |
| if (dialog == null || dialog.getShell().isDisposed()) { |
| |
| errors.add(statusAdapter); |
| modals.put(statusAdapter, new Boolean(modal)); |
| // Delay prompting if the status adapter property is set |
| if (shouldPrompt(statusAdapter)) { |
| if (dialog == null) { |
| dialog = new InternalDialog(getParentShell(), |
| WorkbenchStatusDialogManager.this, shouldBeModal()); |
| setSelectedStatusAdapter(statusAdapter); |
| dialog.open(); |
| dialog.getShell().addDisposeListener(disposeListener); |
| } |
| refresh(); |
| refreshDialogSize(); |
| } |
| |
| } else { |
| if (statusAdapter |
| .getProperty(IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY) != null) { |
| statusAdapter.setProperty( |
| IProgressConstants.NO_IMMEDIATE_ERROR_PROMPT_PROPERTY, |
| Boolean.FALSE); |
| } |
| openStatusDialog(modal, statusAdapter); |
| } |
| } |
| |
| /** |
| * This method closes the dialog. |
| */ |
| private boolean close() { |
| if (detailsOpened) { |
| toggleDetailsArea(); |
| } |
| if (trayOpened) { |
| closeTray(); |
| } |
| shellBounds = getShell().getBounds(); |
| if (clipboard != null) { |
| clipboard.dispose(); |
| } |
| statusListViewer = null; |
| boolean result = this.dialog.close(); |
| if (!modalitySwitch) { |
| ProgressManagerUtil.animateDown(shellBounds); |
| } |
| return result; |
| } |
| |
| /** |
| * Closes the dialog tray (it is support area at the right side of the |
| * dialog) |
| * |
| * @throws IllegalStateException |
| */ |
| private void closeTray() throws IllegalStateException { |
| this.dialog.closeTray(); |
| } |
| |
| /** |
| * This method creates button bar that is available on the bottom of the |
| * dialog. |
| */ |
| private Control createButtonBar(Composite parent) { |
| Composite composite = new Composite(parent, SWT.NONE); |
| GridLayout layout = new GridLayout(); |
| layout.marginWidth = dialog |
| .convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); |
| layout.marginHeight = dialog |
| .convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); |
| composite.setLayout(layout); |
| composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| toolBar = createSupportToolbar(composite); |
| |
| // Add the buttons to the button bar. |
| createButtonsForButtonBar(composite); |
| |
| composite.layout(); |
| return composite; |
| } |
| |
| /** |
| * This method creates buttons that are placed on button bar. |
| */ |
| private void createButtonsForButtonBar(Composite parent) { |
| IAction gotoAction = getGotoAction(); |
| String text = null; |
| if (gotoAction != null) { |
| text = gotoAction.getText(); |
| } |
| Button button = dialog.createButton(parent, GOTO_ACTION_ID, |
| text == null ? "" : text, //$NON-NLS-1$ |
| false); |
| if (text == null) |
| hideButton(button, true); |
| |
| dialog.createButton(parent, IDialogConstants.OK_ID, |
| IDialogConstants.OK_LABEL, true); |
| |
| dialog.createButton(parent, IDialogConstants.DETAILS_ID, |
| IDialogConstants.SHOW_DETAILS_LABEL, false); |
| } |
| |
| /** |
| * This method creates dialog area. |
| */ |
| private Control createDialogArea(Composite parent) { |
| createTitleArea(parent); |
| createListArea(parent); |
| dialogArea = parent; |
| Dialog.applyDialogFont(dialogArea); |
| return parent; |
| } |
| |
| /** |
| * Create an area which allows the user to view the status if only one is |
| * created or to select one of reported statuses when there are many. |
| * |
| * @param parent |
| * the parent composite on which all components should be placed. |
| */ |
| private void createListArea(Composite parent) { |
| listArea = new Composite(parent, SWT.NONE); |
| GridData layoutData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| layoutData.heightHint = 0; |
| layoutData.widthHint = 0; |
| listArea.setLayoutData(layoutData); |
| GridLayout layout = new GridLayout(); |
| listArea.setLayout(layout); |
| if(getStatusAdapters().size() > 1){ |
| fillListArea(listArea); |
| } |
| } |
| |
| /** |
| * This method creates additional display area for {@link StatusAdapter} |
| * when only one is available. |
| * |
| * It creates one label on a composite currently for secondary message. |
| * |
| * @param parent |
| * A parent composite on which all components should be placed. |
| * @return composite the composite on which are all components for |
| * displaying status when only one is available. |
| */ |
| private Composite createSingleStatusDisplayArea(Composite parent) { |
| // secondary message is displayed on separate composite with no margins |
| Composite singleStatusParent = new Composite(parent, SWT.NONE); |
| GridLayout gridLayout = new GridLayout(); |
| gridLayout.marginWidth = 0; |
| singleStatusParent.setLayout(gridLayout); |
| GridData gd = new GridData(SWT.FILL, SWT.FILL, true, false); |
| singleStatusParent.setLayoutData(gd); |
| |
| // label that wraps |
| singleStatusLabel = new Label(singleStatusParent, SWT.WRAP); |
| GridData labelLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| labelLayoutData.widthHint = dialog.convertWidthInCharsToPixels(50); |
| singleStatusLabel.setLayoutData(labelLayoutData); |
| // main message set up early, to address bug 222391 |
| singleStatusLabel.setText(statusListLabelProvider.getColumnText( |
| statusAdapter, 0)); |
| |
| singleStatusLabel.addMouseListener(new MouseListener() { |
| public void mouseDoubleClick(MouseEvent e) { |
| } |
| |
| public void mouseDown(MouseEvent e) { |
| showDetailsArea(); |
| } |
| |
| public void mouseUp(MouseEvent e) { |
| } |
| }); |
| return singleStatusParent; |
| } |
| |
| /** |
| * Creates a new control that provides access to support providers. |
| * <p> |
| * The <code>WorkbenchStatusDialog</code> implementation of this method |
| * creates the control, registers it for selection events including |
| * selection, Note that the parent's layout is assumed to be a |
| * <code>GridLayout</code> and the number of columns in this layout is |
| * incremented. Subclasses may override. |
| * </p> |
| * |
| * @param parent |
| * A parent composite on which all components should be placed. |
| * @return the report control |
| */ |
| private ToolBar createSupportToolbar(Composite parent) { |
| ToolBar toolBar = new ToolBar(parent, SWT.FLAT | SWT.NO_FOCUS); |
| ((GridLayout) parent.getLayout()).numColumns++; |
| GridData layoutData = new GridData(SWT.BEGINNING, SWT.FILL, true, false); |
| toolBar.setLayoutData(layoutData); |
| final Cursor cursor = new Cursor(parent.getDisplay(), SWT.CURSOR_HAND); |
| toolBar.setCursor(cursor); |
| toolBar.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| cursor.dispose(); |
| } |
| }); |
| toolBar.setEnabled(false); |
| return toolBar; |
| } |
| |
| /** |
| * Creates a button with a report image. This is only used if there is an |
| * image available. |
| */ |
| private ToolItem createShowSupportToolItem(ToolBar parent) { |
| ImageDescriptor descriptor = WorkbenchImages |
| .getImageDescriptor(IWorkbenchGraphicConstants.IMG_DTOOL_SHOW_SUPPORT); |
| final Image image = new Image(parent.getDisplay(), descriptor |
| .getImageData()); |
| |
| ToolItem toolItem = new ToolItem(parent, SWT.NONE); |
| toolItem.setImage(image); |
| toolItem |
| .setToolTipText(WorkbenchMessages.WorkbenchStatusDialog_Support); |
| toolItem.addSelectionListener(new SelectionAdapter() { |
| public void widgetSelected(SelectionEvent e) { |
| openTray(supportTray); |
| } |
| }); |
| toolItem.addDisposeListener(new DisposeListener() { |
| public void widgetDisposed(DisposeEvent e) { |
| image.dispose(); |
| } |
| }); |
| return toolItem; |
| } |
| |
| /** |
| * Creates title area. |
| * |
| * @param parent |
| * A composite on which the title area should be created. |
| */ |
| private void createTitleArea(Composite parent) { |
| titleArea = new Composite(parent, SWT.NONE); |
| titleArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); |
| |
| GridLayout layout = new GridLayout(); |
| layout.numColumns = 2; |
| layout.horizontalSpacing = 10; |
| layout.marginLeft = 10; |
| layout.marginTop = 10; |
| layout.marginBottom = 0; |
| titleArea.setLayout(layout); |
| |
| titleImageLabel = new Label(titleArea, SWT.NONE); |
| titleImageLabel.setImage(getErrorImage()); |
| GridData layoutData = new GridData(); |
| layoutData.verticalSpan = 2; |
| layoutData.verticalAlignment = SWT.TOP; |
| titleImageLabel.setLayoutData(layoutData); |
| |
| GridData messageData = new GridData(SWT.FILL, SWT.FILL, true, true); |
| messageData.widthHint = dialog.convertWidthInCharsToPixels(50); |
| mainMessageLabel = new Label(titleArea, SWT.WRAP); |
| mainMessageLabel.setLayoutData(messageData); |
| // main message set up early, to address bug 222391 |
| mainMessageLabel.setText(getMainMessage(statusAdapter)); |
| if (getStatusAdapters().size() == 1) { |
| singleStatusDisplayArea = createSingleStatusDisplayArea(titleArea); |
| } |
| } |
| |
| /** |
| * Enables the default support area that shows stack trace of the exception |
| * contained in the selected status. |
| * |
| * @param enable |
| * true enables, false disables default support |
| */ |
| public void enableDefaultSupportArea(boolean enable) { |
| supportTray.enableDefaultSupportArea(enable); |
| } |
| |
| /** |
| * This method creates display area for {@link StatusAdapter}s when more is |
| * available. |
| * |
| * @param parent |
| * A parent composite on which all components should be placed. |
| */ |
| private void fillListArea(Composite parent) { |
| // it is necessary to make list parent composite taller |
| GridData listAreaGD = (GridData) parent.getLayoutData(); |
| listAreaGD.grabExcessHorizontalSpace = true; |
| listAreaGD.grabExcessVerticalSpace = true; |
| listAreaGD.heightHint = SWT.DEFAULT; |
| |
| // create list viewer |
| statusListViewer = new TableViewer(parent, SWT.SINGLE | SWT.H_SCROLL |
| | SWT.V_SCROLL | SWT.BORDER); |
| statusListViewer.setComparator(getViewerComparator()); |
| Control control = statusListViewer.getControl(); |
| GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); |
| data.heightHint = dialog.convertHeightInCharsToPixels(5); |
| statusListViewer.addSelectionChangedListener(supportTray); |
| control.setLayoutData(data); |
| initContentProvider(); |
| initLabelProvider(); |
| statusListViewer |
| .addSelectionChangedListener(new ISelectionChangedListener() { |
| public void selectionChanged(SelectionChangedEvent event) { |
| handleSelectionChange(); |
| } |
| }); |
| Dialog.applyDialogFont(parent); |
| } |
| |
| /** |
| * Return the <code>Image</code> to be used when displaying an error. |
| * |
| * @return image the error image |
| */ |
| private Image getErrorImage() { |
| return getSWTImage(SWT.ICON_ERROR); |
| } |
| |
| /** |
| * Returns {@link IAction} associated with selected StatusAdapter. |
| * |
| * @return {@link IAction} that is set as {@link StatusAdapter} property |
| * with Job.class key. |
| */ |
| private IAction getGotoAction() { |
| Object property = null; |
| |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| if (job != null) { |
| property = job.getProperty(IProgressConstants.ACTION_PROPERTY); |
| } |
| |
| if (property instanceof IAction) { |
| return (IAction) property; |
| } |
| return null; |
| } |
| |
| /** |
| * Gets {@link Image} associated with current {@link StatusAdapter} |
| * severity. |
| * |
| * @return {@link Image} associated with current {@link StatusAdapter} |
| * severity. |
| */ |
| private Image getImage() { |
| if (statusAdapter != null) { |
| IStatus status = statusAdapter.getStatus(); |
| if (status != null) { |
| if (status.getSeverity() == IStatus.WARNING) { |
| return getWarningImage(); |
| } |
| if (status.getSeverity() == IStatus.INFO) { |
| return getInfoImage(); |
| } |
| } |
| } |
| // If it was not a warning or an error then return the error image |
| return getErrorImage(); |
| } |
| |
| /** |
| * Return the <code>Image</code> to be used when displaying information. |
| * |
| * @return image the information image |
| */ |
| private Image getInfoImage() { |
| return getSWTImage(SWT.ICON_INFORMATION); |
| } |
| |
| /** |
| * This method computes the dialog main message. |
| * |
| * If there is only one reported status adapter, main message should be: |
| * <ul> |
| * <li>information about job that reported an error.</li> |
| * <li>primary message, if the statusAdapter was not reported by job</li> |
| * </ul> |
| * |
| * If there is more reported statusAdapters, main message should be: |
| * <ul> |
| * <li>primary message for job reported statusAdapters</li> |
| * <li>secondary message for statuses not reported by jobs</li> |
| * </ul> |
| * |
| * If nothing can be found, some general information should be displayed. |
| * |
| * @param statusAdapter |
| * A status adapter which is used as the base for computation. |
| * @return main message of the dialog. |
| * |
| * @see WorkbenchStatusDialogManager#getPrimaryMessage(StatusAdapter) |
| * @see WorkbenchStatusDialogManager#getSecondaryMessage(StatusAdapter) |
| * @see WorkbenchStatusDialogManager#setStatusListLabelProvider(ITableLabelProvider) |
| */ |
| private String getMainMessage(StatusAdapter statusAdapter) { |
| if (errors.size() == 1) { |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| // job |
| if (job != null) { |
| return NLS |
| .bind( |
| WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurredInJob, |
| job.getName()); |
| } |
| // we are not handling job |
| return getPrimaryMessage(statusAdapter); |
| } |
| // we have a list. primary message or job name or on the list name (both |
| // with timestamp if available). |
| // we display secondary message or status |
| if (errors.size() > 1) { |
| Job job = (Job) (statusAdapter.getAdapter(Job.class)); |
| // job |
| if (job != null) { |
| return getPrimaryMessage(statusAdapter); |
| } |
| |
| // plain status |
| return getSecondaryMessage(statusAdapter); |
| } |
| return WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred; |
| } |
| |
| /** |
| * Return the parent shell. |
| * |
| * @return the parent shell of the dialog. |
| */ |
| private Shell getParentShell() { |
| return ProgressManagerUtil.getDefaultParent(); |
| } |
| |
| /** |
| * Retrieves primary message from passed statusAdapter. Primary message |
| * should be (from the most important): |
| * <ul> |
| * <li>statusAdapter title</li> |
| * <li>IStatus message</li> |
| * <li>pointing to child statuses if IStatus has them.</li> |
| * <li>exception message</li> |
| * <li>exception class</li> |
| * <li>general message informing about error (no details at all)</li> |
| * </ul> |
| * |
| * @param statusAdapter |
| * an status adapter to retrieve primary message from |
| * @return String containing primary message |
| * |
| * @see WorkbenchStatusDialogManager#getMainMessage(StatusAdapter) |
| * @see WorkbenchStatusDialogManager#getSecondaryMessage(StatusAdapter) |
| */ |
| private String getPrimaryMessage(StatusAdapter statusAdapter) { |
| // if there was nonempty title set, display the title |
| Object property = statusAdapter |
| .getProperty(IStatusAdapterConstants.TITLE_PROPERTY); |
| if (property instanceof String) { |
| String header = (String) property; |
| if (header.trim().length() > 0) { |
| return header; |
| } |
| } |
| // if there was message set in the status |
| IStatus status = statusAdapter.getStatus(); |
| if (status.getMessage() != null |
| && status.getMessage().trim().length() > 0) { |
| return status.getMessage(); |
| } |
| |
| // if status has children |
| if (status.getChildren().length > 0) { |
| return WorkbenchMessages.WorkbenchStatusDialog_StatusWithChildren; |
| } |
| |
| // check the exception |
| Throwable t = status.getException(); |
| if (t != null) { |
| if (t.getMessage() != null && t.getMessage().trim().length() > 0) { |
| return t.getMessage(); |
| } |
| return t.getClass().getName(); |
| } |
| return WorkbenchMessages.WorkbenchStatusDialog_ProblemOccurred; |
| } |
| |
| /** |
| * Retrieves secondary message from the passed statusAdapter. Secondary |
| * message is one level lower than primary. Secondary message should be |
| * (from the most important): |
| * <ul> |
| * <li>IStatus message</li> |
| * <li>pointing to child statuses if IStatus has them.</li> |
| * <li>exception message</li> |
| * <li>exception class</li> |
| * </ul> |
| * Secondary message should not be the same as primary one. If no secondary |
| * message can be extracted, details should be pointed. |
| * |
| * @param statusAdapter |
| * an status adapter to retrieve secondary message from |
| * @return String containing secondary message |
| * |
| * @see WorkbenchStatusDialogManager#getMainMessage(StatusAdapter) |
| * @see WorkbenchStatusDialogManager#getPrimaryMessage(StatusAdapter) |
| */ |
| private String getSecondaryMessage(StatusAdapter statusAdapter) { |
| String primary = getPrimaryMessage(statusAdapter); |
| // we can skip the title, it is always displayed as primary message |
| |
| // if there was message set in the status |
| IStatus status = statusAdapter.getStatus(); |
| if (status.getMessage() != null |
| && status.getMessage().trim().length() > 0 |
| && !primary.equals(status.getMessage())) { // we have not |
| // displayed it yet |
| return status.getMessage(); |
| } |
| // if status has children |
| if (status.getChildren().length > 0 |
| && !primary.equals(status.getMessage())) { |
| return WorkbenchMessages.WorkbenchStatusDialog_StatusWithChildren; |
| } |
| |
| // check the exception |
| Throwable t = status.getException(); |
| if (t != null) { |
| if (t.getMessage() != null && t.getMessage().trim().length() > 0 |
| && !primary.equals(status.getMessage())) { |
| return t.getMessage(); |
| } |
| return t.getClass().getName(); |
| } |
| return WorkbenchMessages.WorkbenchStatusDialog_SeeDetails; |
| } |
| |
| /** |
| * Returns the shell of the dialog. |
| */ |
| private Shell getShell() { |
| return this.dialog.getShell(); |
| } |
| |
| /** |
| * Get the single selection. Return null if the selection is not just one |
| * element. |
| * |
| * @return StatusAdapter or <code>null</code>. |
| */ |
| private StatusAdapter getSingleSelection() { |
| ISelection rawSelection = statusListViewer.getSelection(); |
| if (rawSelection != null |
| && rawSelection instanceof IStructuredSelection) { |
| IStructuredSelection selection = (IStructuredSelection) rawSelection; |
| if (selection.size() == 1) { |
| return (StatusAdapter) selection.getFirstElement(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Gets a collection of status adapters that were passed to the dialog. |
| * |
| * @return collection of {@link StatusAdapter} objects |
| */ |
| public Collection getStatusAdapters() { |
| return Collections.unmodifiableCollection(errors); |
| } |
| |
| /** |
| * Get an <code>Image</code> from the provide SWT image constant. |
| * |
| * @param imageID |
| * the SWT image constant |
| * @return image the image |
| */ |
| private Image getSWTImage(final int imageID) { |
| return getShell().getDisplay().getSystemImage(imageID); |
| } |
| |
| /** |
| * Return a viewer sorter for looking at the jobs. |
| * |
| * @return ViewerSorter |
| */ |
| private ViewerComparator getViewerComparator() { |
| return new ViewerComparator() { |
| private int compare(StatusAdapter s1, StatusAdapter s2) { |
| Long timestamp1 = ((Long) s1 |
| .getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY)); |
| Long timestamp2 = ((Long) s2 |
| .getProperty(IStatusAdapterConstants.TIMESTAMP_PROPERTY)); |
| if (timestamp1 == null || timestamp2 == null |
| || (timestamp1.equals(timestamp2))) { |
| String text1 = statusListLabelProvider.getColumnText(s1, 0); |
| String text2 = statusListLabelProvider.getColumnText(s2, 0); |
| return text1.compareTo(text2); |
| } |
| |
| if (timestamp1.longValue() < timestamp2.longValue()) { |
| return -1; |
| } |
| if (timestamp1.longValue() > timestamp2.longValue()) { |
| return 1; |
| } |
| // should be never called |
| return 0; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.ViewerComparator#compare(org.eclipse.jface.viewers.Viewer, |
| * java.lang.Object, java.lang.Object) |
| */ |
| public int compare(Viewer testViewer, Object o1, Object o2) { |
| if (o1 instanceof StatusAdapter && o2 instanceof StatusAdapter) { |
| return compare((StatusAdapter) o1, (StatusAdapter) o2); |
| } |
| // should not happen |
| if (o1.hashCode() < o2.hashCode()) { |
| return -1; |
| } |
| if (o2.hashCode() > o2.hashCode()) { |
| return 1; |
| } |
| return 0; |
| } |
| }; |
| } |
| |
| /** |
| * Return the <code>Image</code> to be used when displaying a warning. |
| * |
| * @return image the warning image |
| */ |
| private Image getWarningImage() { |
| return getSWTImage(SWT.ICON_WARNING); |
| } |
| |
| /** |
| * The selection in the multiple job list has changed. Update widget |
| * enablements, repopulate the list and show details. |
| */ |
| private void handleSelectionChange() { |
| StatusAdapter newSelection = getSingleSelection(); |
| if (newSelection != null) { |
| setSelectedStatusAdapter(newSelection); |
| showDetailsArea(); |
| refresh(); |
| } |
| } |
| |
| /** |
| * Sets the content provider for the viewer. |
| */ |
| private void initContentProvider() { |
| IContentProvider provider = new IStructuredContentProvider() { |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IContentProvider#dispose() |
| */ |
| public void dispose() { |
| // Nothing of interest here |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java.lang.Object) |
| */ |
| public Object[] getElements(Object inputElement) { |
| return getStatusAdapters().toArray(); |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface.viewers.Viewer, |
| * java.lang.Object, java.lang.Object) |
| */ |
| public void inputChanged(Viewer viewer, Object oldInput, |
| Object newInput) { |
| if (newInput != null) { |
| refreshStatusListArea(); |
| } |
| } |
| }; |
| statusListViewer.setContentProvider(provider); |
| statusListViewer.setInput(this); |
| statusListViewer.setSelection(new StructuredSelection(statusAdapter)); |
| } |
| |
| /** |
| * This method should initialize the dialog bounds. |
| */ |
| private void initializeBounds() { |
| refreshDialogSize(); |
| if (!modalitySwitch) { |
| Rectangle shellPosition = getShell().getBounds(); |
| ProgressManagerUtil.animateUp(shellPosition); |
| } else { |
| getShell().setBounds(shellBounds); |
| } |
| } |
| |
| /** |
| * Sets initial label provider. |
| */ |
| private void initLabelProvider() { |
| statusListViewer.setLabelProvider(statusListLabelProvider); |
| } |
| |
| /* |
| * Prompt to inform the user that the dialog will close and the errors |
| * cleared. |
| */ |
| private boolean isPromptToClose() { |
| IPreferenceStore store = WorkbenchPlugin.getDefault() |
| .getPreferenceStore(); |
| if (!store.contains(PREF_SKIP_GOTO_ACTION_PROMPT) |
| || !store.getString(PREF_SKIP_GOTO_ACTION_PROMPT).equals( |
| MessageDialogWithToggle.ALWAYS)) { |
| MessageDialogWithToggle dialog = MessageDialogWithToggle |
| .openOkCancelConfirm( |
| getShell(), |
| ProgressMessages.JobErrorDialog_CloseDialogTitle, |
| ProgressMessages.JobErrorDialog_CloseDialogMessage, |
| ProgressMessages.JobErrorDialog_DoNotShowAgainMessage, |
| false, store, PREF_SKIP_GOTO_ACTION_PROMPT); |
| return dialog.getReturnCode() == Window.OK; |
| } |
| return true; |
| } |
| |
| /** |
| * 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) { |
| errors.add(statusAdapter); |
| modals.put(statusAdapter, new Boolean(modal)); |
| boolean shouldBeModal = shouldBeModal(); |
| if (shouldBeModal ^ dialog.isModal()) { |
| dialog.getShell().removeDisposeListener(disposeListener); |
| modalitySwitch = true; |
| close(); |
| setSelectedStatusAdapter(statusAdapter); |
| dialog = new InternalDialog(getParentShell(), this, modal); |
| dialog.open(); |
| dialog.getShell().addDisposeListener(disposeListener); |
| modalitySwitch = false; |
| } |
| refresh(); |
| } |
| |
| /** |
| * Opens the dialog tray (support area at the right side of the dialog) |
| */ |
| private void openTray(DialogTray tray) throws IllegalStateException, |
| UnsupportedOperationException { |
| launchTrayButton.setEnabled(false); |
| this.dialog.openTray(tray); |
| trayOpened = true; |
| } |
| |
| /** |
| * Method which should be invoked when new errors become available for |
| * display. |
| */ |
| private void refresh() { |
| if (dialog == null) { |
| return; |
| } |
| if (dialogArea == null || dialogArea.isDisposed()) { |
| return; |
| } |
| updateTitleArea(); |
| updateListArea(); |
| updateEnablements(); |
| // adjust width if necessary |
| Point currentSize = getShell().getSize(); |
| Point desiredSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| if(currentSize.x < desiredSize.x){ |
| getShell().setSize(desiredSize.x, currentSize.y); |
| } else { |
| getShell().layout(); |
| } |
| } |
| |
| private void refreshDialogSize() { |
| Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| getShell().setSize(newSize); |
| } |
| |
| /** |
| * Refreshes the single status area. Is called only when there is one and |
| * only one error. |
| */ |
| private void refreshSingleStatusArea() { |
| String description = statusListLabelProvider.getColumnText( |
| statusAdapter, 0); |
| if (description.equals(singleStatusLabel.getText())) |
| singleStatusLabel.setText(" "); //$NON-NLS-1$ |
| singleStatusLabel.setText(description); |
| singleStatusDisplayArea.layout(); |
| getShell().setText(title); |
| } |
| |
| /** |
| * Refresh the contents of the viewer. |
| */ |
| private void refreshStatusListArea() { |
| if (statusListViewer != null |
| && !statusListViewer.getControl().isDisposed()) { |
| statusListViewer.refresh(); |
| if (statusListViewer.getTable().getItemCount() > 1) { |
| getShell() |
| .setText( |
| WorkbenchMessages.WorkbenchStatusDialog_MultipleProblemsHaveOccured); |
| } else { |
| getShell().setText(this.title); |
| } |
| } |
| } |
| |
| /** |
| * Sets the details area provider. If null is set, the default area provider |
| * will be used. |
| * |
| * @param provider |
| * A details area provider to be set. |
| */ |
| public void setDetailsAreaProvider(AbstractStatusAreaProvider provider) { |
| this.detailsManager.setDetailsAreaProvider(provider); |
| } |
| |
| /** |
| * Sets current status adapter. |
| * |
| * @param statusAdapter |
| * The statusAdapter to set. |
| */ |
| private void setSelectedStatusAdapter(StatusAdapter statusAdapter) { |
| if (this.statusAdapter != statusAdapter) { |
| this.statusAdapter = 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. |
| * |
| * @param labelProvider |
| * a label provider to be used when displaying status adapters. |
| * It must not be null. |
| */ |
| public void setStatusListLabelProvider(ITableLabelProvider labelProvider) { |
| if (statusListLabelProvider != null) { |
| throw new NullPointerException("Label provider cannot be null"); //$NON-NLS-1$ |
| } |
| statusListLabelProvider = labelProvider; |
| } |
| |
| /** |
| * Sets the support provider. |
| * |
| * The policy for choosing support provider is: |
| * <ol> |
| * <li>use the support provider set by this method, if set</li> |
| * <li>use the support provider set in JFace Policy, if set</li> |
| * <li>use the default support area, if enabled</li> |
| * </ol> |
| * |
| * @param provider |
| * Support provider to be set. |
| */ |
| public void setSupportAreaProvider(AbstractStatusAreaProvider provider) { |
| userSupportProvider = provider; |
| } |
| |
| /** |
| * 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 |
| */ |
| private boolean shouldBeModal() { |
| 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. |
| */ |
| private 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; |
| } |
| |
| /** |
| * Show the details portion of the dialog if it is not already visible. This |
| * method will only work when it is invoked after the control of the dialog |
| * has been set. In other words, after the <code>createContents</code> |
| * method has been invoked and has returned the control for the content area |
| * of the dialog. Invoking the method before the content area has been set |
| * or after the dialog has been disposed will have no effect. |
| */ |
| private void showDetailsArea() { |
| if (dialogArea != null && !dialogArea.isDisposed()) { |
| if (detailsManager.isOpen()) { |
| detailsManager.close(); |
| detailsManager.createDetailsArea(dialogArea, statusAdapter); |
| } else { |
| toggleDetailsArea(); |
| detailsOpened = true; |
| } |
| dialogArea.layout(); |
| } |
| } |
| |
| /** |
| * Toggles the unfolding of the details area. This is triggered by the user |
| * pressing the details button. |
| * |
| */ |
| private boolean toggleDetailsArea() { |
| boolean opened = false; |
| Point windowSize = getShell().getSize(); |
| if (detailsManager.isOpen()) { |
| detailsManager.close(); |
| dialog.getButton(IDialogConstants.DETAILS_ID).setText( |
| IDialogConstants.SHOW_DETAILS_LABEL); |
| opened = false; |
| } else { |
| detailsManager.createDetailsArea(dialogArea, statusAdapter); |
| dialog.getButton(IDialogConstants.DETAILS_ID).setText( |
| IDialogConstants.HIDE_DETAILS_LABEL); |
| opened = true; |
| } |
| if(getStatusAdapters().size() == 1){ |
| GridData gd = (GridData) listArea.getLayoutData(); |
| if(opened){ |
| gd.heightHint = 0; |
| gd.grabExcessVerticalSpace = false; |
| } else { |
| gd.grabExcessVerticalSpace = true; |
| } |
| listArea.setLayoutData(gd); |
| } |
| Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); |
| int diffY = newSize.y - windowSize.y; |
| // increase the dialog height if details were opened and such increase is necessary |
| // decrease the dialog height if details were closed and empty space appeared |
| if ((opened && diffY > 0) || (!opened && diffY < 0)) { |
| getShell().setSize(new Point(windowSize.x, windowSize.y + (diffY))); |
| } |
| dialogArea.layout(); |
| return opened; |
| } |
| |
| /** |
| * Update the button enablements |
| */ |
| private void updateEnablements() { |
| Button details = dialog.getButton(IDialogConstants.DETAILS_ID); |
| if (details != null) { |
| details.setEnabled(true); |
| } |
| Button gotoButton = dialog.getButton(GOTO_ACTION_ID); |
| if (gotoButton != null) { |
| IAction gotoAction = getGotoAction(); |
| boolean hasValidGotoAction = (gotoAction != null) && (gotoAction.getText() != null); |
| if (hasValidGotoAction) { |
| hideButton(gotoButton,false); |
| gotoButton.setText(gotoAction.getText()); |
| |
| ((GridData) gotoButton.getLayoutData()).widthHint = gotoButton |
| .computeSize(SWT.DEFAULT, SWT.DEFAULT).x; |
| gotoButton.getParent().layout(); |
| } |
| else |
| hideButton(gotoButton,true); |
| } |
| // and tray enablement button |
| if (supportTray.providesSupport()) { |
| if(launchTrayButton == null || launchTrayButton.isDisposed()){ |
| launchTrayButton = createShowSupportToolItem(toolBar); |
| } |
| launchTrayButton.setEnabled(!trayOpened); |
| } else { |
| if(launchTrayButton != null && !launchTrayButton.isDisposed()){ |
| launchTrayButton.dispose(); |
| launchTrayButton = null; |
| } |
| } |
| toolBar.getParent().layout(); |
| toolBar.setEnabled(toolBar.getItemCount() > 0); |
| } |
| |
| /** |
| * Hide the button if hide is <code>true</code>. |
| * |
| * @param button |
| * @param hide |
| */ |
| private void hideButton(Button button, boolean hide) { |
| ((GridData) button.getLayoutData()).exclude = hide; |
| button.setVisible(!hide); |
| button.setEnabled(!hide); |
| } |
| |
| /** |
| * This methods switches StatusAdapters presentation depending if there is |
| * one status or more. |
| */ |
| private void updateListArea() { |
| // take care about list area if there is more than one status |
| if (errors.size() > 1) { |
| if (singleStatusDisplayArea != null) { |
| singleStatusDisplayArea.dispose(); |
| } |
| if (statusListViewer == null |
| || statusListViewer.getControl().isDisposed()) { |
| fillListArea(listArea); |
| getShell().setSize( |
| getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT)); |
| } |
| refreshStatusListArea(); |
| } |
| } |
| |
| /** |
| * Updated title area. Adjust title, title message and title image according |
| * to selected {@link StatusAdapter}. |
| */ |
| private void updateTitleArea() { |
| Image image = getImage(); |
| titleImageLabel.setImage(image); |
| if (statusAdapter != null) { |
| mainMessageLabel.setText(getMainMessage(statusAdapter)); |
| } |
| if (getStatusAdapters().size() > 1 && singleStatusDisplayArea != null) { |
| singleStatusDisplayArea.dispose(); |
| } else { |
| refreshSingleStatusArea(); |
| } |
| titleArea.layout(); |
| } |
| } |