/* * Copyright (c) 2006 Borland Software Corp * * 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: dvorak - initial API and implementation */ package org.eclipse.gmf.internal.codegen.popup.actions; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.BasicDiagnostic; import org.eclipse.emf.common.util.Diagnostic; import org.eclipse.emf.ecore.EValidator; import org.eclipse.gmf.internal.bridge.transform.ValidationHelper; import org.eclipse.gmf.internal.bridge.transform.ValidationHelper.DiagnosticMarkerMap; import org.eclipse.gmf.internal.codegen.CodeGenUIPlugin; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IconAndMessageDialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.Clipboard; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; 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.Label; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Text; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IWorkbench; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchWindow; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IGotoMarker; import org.eclipse.ui.part.FileEditorInput; /** * A dialog to display one or more errors to the user, as contained in an * <code>Diagnostic</code> object. If an error contains additional detailed * information then a Details button is automatically supplied, which shows or * hides an error details viewer when pressed by the user.<p> * * @see org.eclipse.emf.common.util.Diagnostic */ @SuppressWarnings("synthetic-access") public class DiagnosticsDialog extends IconAndMessageDialog { private static class DiagnosticLabelProvider extends LabelProvider { @Override public Image getImage(Object element) { if (!(element instanceof Diagnostic)) { return null; } Diagnostic diagnostic = (Diagnostic) element; String imageName = ISharedImages.IMG_OBJS_ERROR_TSK; switch (diagnostic.getSeverity()) { case Diagnostic.INFO: imageName = ISharedImages.IMG_OBJS_INFO_TSK; break; case Diagnostic.WARNING: imageName = ISharedImages.IMG_OBJS_WARN_TSK; break; } return PlatformUI.getWorkbench().getSharedImages().getImage(imageName); } @Override public String getText(Object element) { if(element instanceof Diagnostic) { Diagnostic diagnostic = (Diagnostic)element; if(diagnostic.getMessage() != null) { return diagnostic.getMessage(); } } return super.getText(element); } } private static class DiagnosticContentProvider implements ITreeContentProvider { Object cachedInput; boolean showTopLevel; DiagnosticContentProvider(boolean showTopLevelDiagnostic) { showTopLevel = showTopLevelDiagnostic; } public Object[] getChildren(Object parentElement) { if(parentElement instanceof Diagnostic) { Diagnostic diagnostic = (Diagnostic)parentElement; return diagnostic.getChildren().toArray(); } return new Object[0]; } public Object getParent(Object element) { return null; } public boolean hasChildren(Object element) { if(element instanceof Diagnostic) { Diagnostic diagnostic = (Diagnostic)element ; return !diagnostic.getChildren().isEmpty(); } return false; } public Object[] getElements(Object inputElement) { if (inputElement instanceof Diagnostic) { Diagnostic diagnostic = (Diagnostic) inputElement; if(diagnostic == cachedInput && showTopLevel) { cachedInput = null; return new Object[] { diagnostic }; } return diagnostic.getChildren().toArray(); } return new Object[0]; } public void dispose() { // do nothing here } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { cachedInput = newInput; } } /** * Reserve room for this many diagnosticTree items. */ private static final int ITEM_COUNT = 7; private static final int STACK_TRACE_TEXT_LINE_COUNT = 3; /** * The nesting indent. */ private static final String NESTING_INDENT = " "; //$NON-NLS-1$ /** * The Details button. */ private Button detailsButton; /** * The title of the dialog. */ private String title; /** * The SWT diagnosticTree control that displays the error details. */ private TreeViewer diagnosticTree; /** * The SWT Text control that displays the error exception stack trace. */ private Text stackTraceText; /** * Indicates whether the error details viewer is currently created. */ private boolean detailsCreated = false; /** * The main rootDiagnotic object. */ private Diagnostic rootDiagnotic; /** * The current clipboard. To be disposed when closing the dialog. */ private Clipboard clipboard; private boolean shouldIncludeTopLevelErrorInDetails; private final String[] buttonLabels; private final int[] buttonIDs; private final int defaultButtonIndex; /** * Creates an diagnostic dialog. Note that the dialog will have no visual * representation (no widgets) until it is told to open. * <p> * * @param parentShell * the shell under which to create this dialog * @param dialogTitle * the title to use for this dialog, or <code>null</code> to * indicate that the default title should be used * @param dialogMessage * the message to show in this dialog, or <code>null</code> to * indicate that the error's message should be shown as the * primary message * @param diagnostic * the diagnostic grouping all errors to be shown to the user * @param dialogButtonLabels array of labels for buttons to create in the dialog button bar * @param dialogButtonIDs array of IDs for buttons to create in the dialog button bar * @param defaultDialogButtonIndex the index of the default button */ public DiagnosticsDialog(Shell parentShell, String dialogTitle, String dialogMessage, Diagnostic diagnostic, String[] dialogButtonLabels, int[] dialogButtonIDs, int defaultDialogButtonIndex) { super(parentShell); if(diagnostic == null) { throw new IllegalArgumentException("Null diagnostic"); //$NON-NLS-1$ } this.title = (dialogTitle == null) ? JFaceResources.getString("Problem_Occurred") : dialogTitle; //$NON-NLS-1$ this.message = (dialogMessage == null) ? diagnostic.getMessage() : JFaceResources.format("Reason", new Object[] { dialogMessage, diagnostic.getMessage() }); //$NON-NLS-1$; this.rootDiagnotic = diagnostic; this.shouldIncludeTopLevelErrorInDetails = true; assert dialogButtonIDs != null && dialogButtonLabels != null; assert dialogButtonIDs.length == dialogButtonLabels.length; assert defaultDialogButtonIndex >= 0 && defaultDialogButtonIndex < dialogButtonIDs.length; buttonIDs = dialogButtonIDs; buttonLabels = dialogButtonLabels; defaultButtonIndex = defaultDialogButtonIndex; setShellStyle(getShellStyle() | SWT.RESIZE); } @Override protected void buttonPressed(int id) { if (id == IDialogConstants.DETAILS_ID) { // was the details button pressed? toggleDetailsArea(); } else if(id == IDialogConstants.OK_ID || id == IDialogConstants.CANCEL_ID) { super.buttonPressed(id); } else { setReturnCode(id); close(); } } @Override protected void configureShell(Shell shell) { super.configureShell(shell); shell.setText(title); } @Override protected void createButtonsForButtonBar(Composite parent) { for (int i = 0; i < buttonLabels.length; i++) { createButton(parent, buttonIDs[i], buttonLabels[i], defaultButtonIndex == i); } createDetailsButton(parent); } protected void createDetailsButton(Composite parent) { if (shouldShowDetailsButton()) { detailsButton = createButton(parent, IDialogConstants.DETAILS_ID, IDialogConstants.SHOW_DETAILS_LABEL, false); } } /** * This implementation of the <code>Dialog</code> framework method creates * and lays out a composite and calls <code>createMessageArea</code> and * <code>createCustomArea</code> to populate it. Subclasses should * override <code>createCustomArea</code> to add contents below the * message. */ @Override protected Control createDialogArea(Composite parent) { createMessageArea(parent); // create a composite with standard margins and spacing Composite composite = new Composite(parent, SWT.NONE); GridLayout layout = new GridLayout(); layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN); layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN); layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING); layout.horizontalSpacing = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_SPACING); layout.numColumns = 2; composite.setLayout(layout); GridData childData = new GridData(GridData.FILL_BOTH); childData.horizontalSpan = 2; composite.setLayoutData(childData); composite.setFont(parent.getFont()); return composite; } @Override protected void createDialogAndButtonArea(Composite parent) { super.createDialogAndButtonArea(parent); if (this.dialogArea instanceof Composite) { //Create a label if there are no children to force a smaller layout Composite dialogComposite = (Composite) dialogArea; if (dialogComposite.getChildren().length == 0) { new Label(dialogComposite, SWT.NULL); } } } @Override protected Image getImage() { if (rootDiagnotic != null) { if (rootDiagnotic.getSeverity() == Diagnostic.WARNING) { return getWarningImage(); } if (rootDiagnotic.getSeverity() == Diagnostic.INFO) { return getInfoImage(); } } return getErrorImage(); } /** * Create this dialog's drop-down diagnosticTree component. * * @param parent * the parent composite * @return the drop-down diagnosticTree component */ protected void createDropDownTree(Composite parent) { // create the diagnosticTree diagnosticTree = new TreeViewer(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL); data.heightHint = diagnosticTree.getTree().getItemHeight() * ITEM_COUNT; data.horizontalSpan = 2; diagnosticTree.getTree().setLayoutData(data); diagnosticTree.getTree().setFont(parent.getFont()); Menu copyMenu = createDiagnosticTreeMenu(); diagnosticTree.getTree().setMenu(copyMenu); diagnosticTree.setLabelProvider(new DiagnosticLabelProvider()); diagnosticTree.setContentProvider(new DiagnosticContentProvider(shouldIncludeTopLevelErrorInDetails)); diagnosticTree.setInput(rootDiagnotic); diagnosticTree.expandToLevel(2); diagnosticTree.setSelection(new StructuredSelection(rootDiagnotic), true); diagnosticTree.getTree().setFocus(); } /** * Opens an dialog to display the given diagnostic and proceed, cancel buttons. * <p> * * @param parentShell * the parent shell of the dialog, or <code>null</code> if none * @param title * the title to use for this dialog, or <code>null</code> to * indicate that the default title should be used * @param message * the message to show in this dialog, or <code>null</code> to * indicate that the error's message should be shown as the * primary message * @param rootDiagnotic * the diagnostic to show to the user * * @return the code of the button that was pressed that resulted in this * dialog closing. This will be <code>Dialog.OK</code> if the OK * or <code>Dialog.CANCEL</code> if this dialog's close window decoration * or the ESC key was used. */ public static int openProceedCancel(Shell parentShell, String title, String message, Diagnostic rootDiagnostic) { return openProceedCancel(parentShell, title, message, rootDiagnostic, false); } /** * Opens an dialog to display the given diagnostic and proceed, cancel * buttons. * <p> * * @param parentShell * the parent shell of the dialog, or <code>null</code> if none * @param title * the title to use for this dialog, or <code>null</code> to * indicate that the default title should be used * @param message * the message to show in this dialog, or <code>null</code> to * indicate that the error's message should be shown as the * primary message * @param rootDiagnotic * the diagnostic to show to the user * * @param disableProceed * indicates whether the <code>PROCEED</code> button should be * disabled. * @return the code of the button that was pressed that resulted in this * dialog closing. This will be <code>Dialog.OK</code> if the OK * or <code>Dialog.CANCEL</code> if this dialog's close window * decoration or the ESC key was used. */ public static int openProceedCancel(Shell parentShell, String title, String message, Diagnostic rootDiagnostic, final boolean disableProceed) { DiagnosticsDialog dialog = new DiagnosticsDialog(parentShell, title, message, rootDiagnostic, new String[] { IDialogConstants.PROCEED_LABEL, IDialogConstants.CANCEL_LABEL }, new int[] { IDialogConstants.PROCEED_ID, IDialogConstants.CANCEL_ID }, 0) { @Override protected Control createButtonBar(Composite parent) { Control buttonBar = super.createButtonBar(parent); Button proceedButton = getButton(IDialogConstants.PROCEED_ID); if(proceedButton != null) { proceedButton.setEnabled(!disableProceed); } return buttonBar; } }; return dialog.open(); } /** * Opens an dialog to display the given diagnostic and <code>OK</code> button. * <p> * * @param parentShell * the parent shell of the dialog, or <code>null</code> if none * @param title * the title to use for this dialog, or <code>null</code> to * indicate that the default title should be used * @param message * the message to show in this dialog, or <code>null</code> to * indicate that the error's message should be shown as the * primary message * @param rootDiagnotic * the diagnostic to show to the user * * @return the code of the button that was pressed that resulted in this * dialog closing. This will be <code>Dialog.OK</code> if the OK * or <code>Dialog.CANCEL</code> if this dialog's close window * decoration or the ESC key was used. */ public static int openOk(Shell parentShell, String title, String message, Diagnostic rootDiagnostic) { return new DiagnosticsDialog(parentShell, title, message, rootDiagnostic, new String[] { IDialogConstants.OK_LABEL }, new int[] { IDialogConstants.OK_ID }, 0).open(); } private Object getSelection() { if (diagnosticTree != null && !diagnosticTree.getTree().isDisposed()) { ISelection selection = diagnosticTree.getSelection(); if(!selection.isEmpty() && selection instanceof IStructuredSelection) { IStructuredSelection structuredSelection = (IStructuredSelection)selection; return structuredSelection.getFirstElement(); } } return null; } private Diagnostic getDiagnosticSelection() { Object selection = getSelection(); return selection instanceof Diagnostic ? (Diagnostic)selection : null; } /** * Toggles the unfolding of the details area. This is triggered by the user * pressing the details button. */ private void toggleDetailsArea() { Point windowSize = getShell().getSize(); Point oldSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); if (detailsCreated) { diagnosticTree.getTree().dispose(); stackTraceText.dispose(); detailsCreated = false; detailsButton.setText(IDialogConstants.SHOW_DETAILS_LABEL); } else { createDetailsArea((Composite) getContents()); detailsButton.setText(IDialogConstants.HIDE_DETAILS_LABEL); } Point newSize = getShell().computeSize(SWT.DEFAULT, SWT.DEFAULT); getShell().setSize(new Point(windowSize.x, windowSize.y + (newSize.y - oldSize.y))); } /** * Put the details of the rootDiagnotic of the error onto the stream. * * @param buildingDiagnostic * @param buffer * @param nesting */ private void populateCopyBuffer(Diagnostic buildingDiagnostic, StringBuffer buffer, int nesting) { for (int i = 0; i < nesting; i++) { buffer.append(NESTING_INDENT); } buffer.append(buildingDiagnostic.getMessage()); buffer.append(System.getProperty("line.separator", "\n")); //$NON-NLS-1$ //$NON-NLS-2$ // Look for a nested core exception Throwable t = buildingDiagnostic.getException(); if (t instanceof CoreException) { CoreException ce = (CoreException)t; populateCopyBuffer(BasicDiagnostic.toDiagnostic(ce), buffer, nesting + 1); } List<Diagnostic> children = buildingDiagnostic.getChildren(); for (Diagnostic next : children) { populateCopyBuffer(next, buffer, nesting + 1); } } public boolean close() { if (clipboard != null) { clipboard.dispose(); } return super.close(); } /** * 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. * @since 3.1 */ protected final void showDetailsArea() { if (!detailsCreated) { Control control = getContents(); if (control != null && ! control.isDisposed()) { toggleDetailsArea(); } } } /** * Return whether the Details button should be included. * This method is invoked once when the dialog is built. * By default, the Details button is only included if * the rootDiagnotic used when creating the dialog was a multi-rootDiagnotic * or if the rootDiagnotic contains an exception. * Subclasses may override. * @return whether the Details button should be included */ protected boolean shouldShowDetailsButton() { return !rootDiagnotic.getChildren().isEmpty() || rootDiagnotic.getException() != null; } private void createDetailsArea(Composite parent) { createDropDownTree(parent); createDropDownText(parent, diagnosticTree.getTree().getBackground()); this.diagnosticTree.addPostSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { Throwable throwable = getExceptionSelection(); String textValue = ""; //$NON-NLS-1$ if(throwable != null) { // print the stacktrace in the stackTraceText field try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(baos); throwable.printStackTrace(ps); ps.flush(); baos.flush(); textValue = baos.toString(); } catch (IOException e) { // no reason for IO error } } stackTraceText.setText(textValue); } }); this.detailsCreated = true; } private void createDropDownText(Composite parent, Color backgroundColor) { // create the list stackTraceText = new Text(parent, SWT.BORDER | SWT.READ_ONLY | SWT.H_SCROLL | SWT.V_SCROLL); stackTraceText.setBackground(backgroundColor); stackTraceText.setFont(parent.getFont()); GridData data = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_VERTICAL); data.heightHint = stackTraceText.getLineHeight() * STACK_TRACE_TEXT_LINE_COUNT; data.horizontalSpan = 2; stackTraceText.setLayoutData(data); stackTraceText.setToolTipText(Messages.DiagnosticsDialog_exceptStackTrace_toolTip); } private Menu createDiagnosticTreeMenu() { Menu diagnosticMenu = new Menu(diagnosticTree.getTree()); MenuItem copyItem = new MenuItem(diagnosticMenu, SWT.NONE); copyItem.setText(Messages.DiagnosticsDialog_Copy_menuItem); copyItem.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { copyToClipboard(); } public void widgetDefaultSelected(SelectionEvent e) { copyToClipboard(); } }); if(ValidationHelper.getDiagnosticMarkerMap(rootDiagnotic) != null) { MenuItem gotoItem = new MenuItem(diagnosticMenu, SWT.NONE); gotoItem.setText(Messages.DiagnosticsDialog_gotoProblem_menuItem); gotoItem.addSelectionListener(new SelectionListener() { public void widgetSelected(SelectionEvent e) { gotoProblem(); } public void widgetDefaultSelected(SelectionEvent e) { gotoProblem(); } }); } return diagnosticMenu; } /** * Copy the contents of the statuses to the clipboard. */ private void copyToClipboard() { if (clipboard != null) { clipboard.dispose(); } StringBuffer statusBuffer = new StringBuffer(); Diagnostic source = getDiagnosticSelection(); populateCopyBuffer((source != null) ? source : rootDiagnotic, statusBuffer, 0); clipboard = new Clipboard(getShell().getDisplay()); clipboard.setContents(new Object[] { statusBuffer.toString() }, new Transfer[] { TextTransfer.getInstance() }); } private void gotoProblem() { Diagnostic diagnostic = getDiagnosticSelection(); if(diagnostic == null) { return; } DiagnosticMarkerMap markerMap = ValidationHelper.getDiagnosticMarkerMap(rootDiagnotic); IMarker marker = null; if(markerMap != null) { if(!markerMap.getMap().containsKey(diagnostic) && diagnostic == rootDiagnotic && !diagnostic.getChildren().isEmpty()) { // the root is usually just a wrapper containing real diagnostics with markers // -> take the first one diagnostic = diagnostic.getChildren().get(0); } marker = markerMap.getMap().get(diagnostic); } IWorkbench workbench = PlatformUI.getWorkbench(); IWorkbenchWindow workbenchWindow = workbench.getActiveWorkbenchWindow(); IWorkbenchPage page = (workbenchWindow != null) ? workbenchWindow.getActivePage() : null; if(page == null) { return; } try { // activate the problem view to page.showView("org.eclipse.ui.views.ProblemView"); //$NON-NLS-1$ } catch (PartInitException e) { CodeGenUIPlugin.getDefault().getLog().log(e.getStatus()); } IFile file = ValidationHelper.getFileFromDiagnostic(diagnostic); if(file != null) { try { String editorId = "org.eclipse.ui.DefaultTextEditor"; //$NON-NLS-1$ IEditorPart editorPart = null; if(marker == null || marker.getAttribute(EValidator.URI_ATTRIBUTE, null) != null) { IEditorDescriptor[] editorDescriptors = PlatformUI.getWorkbench() .getEditorRegistry().getEditors(file.getName()); if(editorDescriptors.length > 0) { editorId = editorDescriptors[0].getId(); } } editorPart = page.openEditor(new FileEditorInput(file), editorId, true, IWorkbenchPage.MATCH_INPUT | IWorkbenchPage.MATCH_ID); IGotoMarker gotoMarkerSupport = null; if(editorPart != null) { gotoMarkerSupport = (IGotoMarker)editorPart.getAdapter(IGotoMarker.class); cancelPressed(); } if(gotoMarkerSupport != null && marker != null) { // delegate to goto marker of the activated editor gotoMarkerSupport.gotoMarker(marker); } } catch (PartInitException e) { CodeGenUIPlugin.getDefault().getLog().log(e.getStatus()); } } } private Throwable getExceptionSelection() { Diagnostic selection = getDiagnosticSelection(); return (selection != null) ? selection.getException() : null; } static Diagnostic toDiagnostic(IStatus status) { Object[] data = null; if(status.getException() != null) { data = new Object[] { status.getException() }; } if(status.isMultiStatus()) { IStatus[] nestedStatuses = status.getChildren(); List<Diagnostic> children = new ArrayList<Diagnostic>(nestedStatuses.length); for (int i = 0; i < nestedStatuses.length; i++) { children.add(toDiagnostic(nestedStatuses[i])); } return new BasicDiagnostic(status.getPlugin(), status.getCode(), children, status.getMessage(), data); } return new BasicDiagnostic(status.getSeverity(), status.getPlugin(), status.getCode(), status.getMessage(), data); } }