Previous Page
Next Page

11.1. Dialogs

Whenever information is requested from or presented to the user in a modeless fashion, it allows the user to freely interact with all the resources in the workbench. Windows, pages, editors, and views are all examples of modeless UI constructs that do not restrict the order in which the user interacts with them. Dialogs are typically modal, restricting the user to either entering the information requested or canceling the operation. The only time a modal UI construct should be used is when programming restrictions require a gathering or dissemination of information before any other processing can continue and, even then, for as short a time as possible.

In one case, the program could present two different versions of a file using a dialog. Unfortunately, this approach prevents the user from switching back and forth between the comparison and some other UI construct such as another editor or view. A better approach would be to present that same information in a comparison editor.

Creating a new project represents a different situation. In that case, the operation must gather all the necessary information sequentially before the operation can be performed. The user has requested the operation and typically does not need to interact with another aspect of the program until all the information is gathered and the operation is complete. In this case, a dialog or wizard is warranted.

11.1.1. SWT dialogs versus JFace dialogs

There are two distinct dialog hierarchies in Eclipse that should not be confused. SWT dialogs (org.eclipse.swt.Dialog) are Java representations of built-in platform dialogs such as a file dialog or font dialog; as such, they are not portable or extendable. JFace dialogs (org.eclipse.jface.dialogs. Dialog) are platform-independent dialogs on which wizards are built. SWT dialogs are only briefly discussed, while JFace dialogs are covered in detail.

11.1.2. Common SWT dialogs

Eclipse includes several SWT dialog classes that provide platform-independent interfaces to underlying platform-specific dialogs:

ColorDialog Prompts the user to select a color from a predefined set of available colors.

DirectoryDialog Prompts the user to navigate the filesystem and select a directory. Valid styles include SWT.OPEN for selecting an existing directory and SWT.SAVE for specifying a new directory.

FileDialog Prompts the user to navigate the filesystem and select or enter a filename. Valid styles include SWT.OPEN for selecting an existing file and SWT.SAVE for specifying a new file.

FontDialog Prompts the user to select a font from all available fonts.

MessageBox Displays a message to the user. Valid icon styles are shown in Table 11-1.

Table 11-1. Icon Styles

Constant

Icon

SWT.ICON_ERROR

SWT.ICON_INFORMATION

SWT.ICON_QUESTION

SWT.ICON_WARNING

SWT.ICON_WORKING


Valid button styles include:

SWT.OK
SWT.OK | SWT.CANCEL
SWT.YES | SWT.NO
SWT.YES | SWT.NO | SWT.CANCEL
SWT.RETRY | SWT.CANCEL
SWT.ABORT | SWT.RETRY | SWT.IGNORE

PrintDialog Prompts the user to select a printer and various print-related parameters prior to starting a print job.

With any of these SWT dialogs, one of the following modal styles can be specified:

SWT.MODELESS Modeless dialog behavior.

SWT.PRIMARY_MODAL Modal behavior with respect to the parent shell.

SWT.APPLICATION_MODAL Modal behavior with respect to the application.

SWT.SYSTEM_MODAL Modal behavior with respect to the entire system.

11.1.3. Common JFace dialogs

There are many JFace dialogs that can be either instantiated directly or reused via subclassing.

Abstract dialogs

AbstractElementListSelectionDialog An abstract dialog to select elements from a list of elements.

IconAndMessageDialog The abstract superclass of dialogs that have an icon and a message as the first two widgets.

SelectionDialog An abstract dialog for displaying and returning a selection.

SelectionStatusDialog An abstract base class for dialogs with a status bar and OK/Cancel buttons. The status message must be passed over as a StatusInfo object and can be an error, warning, or okay. The OK button is enabled or disabled depending on the status.

StatusDialog An abstract base class for dialogs with a status bar and OK/Cancel buttons.

TitleAreaDialog An abstract dialog having a title area for displaying a title and an image as well as a common area for displaying a description, a message, or an error message.

File dialogs

SaveAsDialog A standard "Save As" dialog that solicits a path from the user. The getresult() method returns the path. Note that the folder at the specified path might not exist and might need to be created.

Information dialogs

ErrorDialog A dialog to display one or more errors to the user, as contained in an IStatus 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 (see Section 11.1.9, Details dialog, on page 420 for a similar dialog that meet RFRS criteria).

MessageDialog A dialog for showing messages to the user.

MessageDialogWithToggle A MessageDialog that also allows the user to adjust a toggle setting. If a preference store is provided and the user selects the toggle, then the user's answer (yes/ok or no) will persist in the store (see Section 12.3.2, Accessing preferences, on page 469). If no store is provided, then this information can be queried after the dialog closes.

Resource dialogs

ContainerSelectionDialog A standard selection dialog that solicits a container resource from the user. The geTResult() method returns the selected container resource.

NewFolderDialog A dialog used to create a new folder. Optionally, the folder can be linked to a filesystem folder.

ProjectLocationMoveDialog A dialog used to select the location of a project for moving.

ProjectLocationSelectionDialog A dialog used to select the name and location of a project for copying.

ResourceListSelectionDialog Shows a list of resources to the user with a text entry field for a string pattern used to filter the list of resources.

ResourceSelectionDialog A standard resource selection dialog that solicits a list of resources from the user. The getresult() method returns the selected resources.

TypeFilteringDialog A selection dialog that allows the user to select a file editor.

Selection dialogs

CheckedTreeSelectionDialog A dialog to select elements out of a tree structure.

ContainerCheckedTreeViewer An enhanced CheckedTreeSelectionDialog dialog with special checked/gray state on the container (non-leaf) nodes.

ElementListSelectionDialog A dialog to select elements out of a list of elements.

ElementTreeSelectionDialog A dialog to select elements out of a tree structure.

ListDialog A dialog that prompts for one element from a list of elements. Uses IStructuredContentProvider to provide the elements and ILabelProvider to provide their labels.

ListSelectionDialog A standard dialog that solicits a list of selections from the user. This class is configured with an arbitrary data model represented by content and label provider objects. The getresult() method returns the selected elements.

TwoPaneElementSelector A list selection dialog with two panes. Duplicated entries will be folded together and are displayed in the lower pane (qualifier).

Miscellaneous dialogs

InputDialog A simple input dialog for soliciting an input string from the user.

MarkerResolutionSelectionDialog A dialog to allow the user to select from a list of marker resolutions.

ProgressMonitorDialog A modal dialog that displays progress during a long-running operation (see Section 9.4, Progress Monitor, on page 383).

TaskPropertiesDialog Shows the properties of a new or existing task, or a problem.

WizardDialog A dialog displaying a wizard and implementing the IWizardContainer interface (see Section 11.2.3, IWizardContainer, on page 434).

11.1.4. Creating a JFace dialog

The default implementation of the Dialog class creates a dialog containing a content area for dialog-specific controls and a button bar below containing OK and Cancel buttons (see Figure 11-1).

Figure 11-1. Default dialog structure.


Typically, new dialogs are created by subclassing org.eclipse.jface.dialogs.Dialog and overriding a handful of methods to customize the dialog for a particular purpose.

buttonPressed(int) Called when a button created by the createButton method is clicked by the user. The default implementation calls okPressed() if the OK button is pressed and cancelPressed() if the Cancel button is pressed.

cancelPressed() Called when the user presses the Cancel button. The default implementation sets the return code to Window.CANCEL and closes the dialog.

close() Closes the dialog, disposes of its shell, and removes the dialog from its window manager (if it has one).

createButton(Composite, int, String, boolean) Creates and returns a new button in the button bar with the given identifier and label. This method is typically called from the createButtonsForButtonBar method.

createButtonBar(Composite) Lays out a button bar and calls the createButtonsForButtonBar method to populate it. Subclasses can override createButtonBar or createButtonsForButtonBar as necessary.

createButtonsForButtonBar(Composite) Creates buttons in the button bar. The default implementation creates OK and Cancel buttons in the lower right corner. Subclasses can override this method to replace the default buttons, or extend this method to augment them using the createButton method.

createContents(Composite) Creates and returns this dialog's contents. The default implementation calls createDialogArea and createButtonBar to create the dialog area and button bar, respectively. Subclasses should override these methods rather than createContents.

createDialogArea(Composite) Creates and returns the content area for the dialog above the button bar. Subclasses typically call the superclass method and then add controls to the returned composite.

okPressed() Called when the user presses the OK button. The default implementation sets the return code to Window.OK and closes the dialog.

open() Opens this dialog, creating it first if it has not yet been created. This method waits until the user closes the dialog, and then returns the dialog's return code. A dialog's return codes are dialog-specific, although two standard return codes are predefined: Window.OK and Window.CANCEL.

setShellStyle(int) Sets the shell style bits for creating the dialog. This method has no effect after the shell is created. Valid style bits include:

SWT.MODELESS
SWT.PRIMARY_MODAL
SWT.APPLICATION_MODAL
SWT.SYSTEM_MODAL
SWT.SHELL_TRIM
SWT.DIALOG_TRIM
SWT.BORDER
SWT.CLOSE
SWT.MAX
SWT.MIN
SWT.RESIZE
SWT.TITLE

setReturnCode(int) Sets the dialog's return code that is returned by the open() method.

Dialog also provides some related utility methods:

applyDialogFont(Control) Applies the dialog font to the specified control and recursively to all child controls that currently have the default font.

getImage(String) Returns the standard dialog image with the given key (one of the Dialog.DLG_IMG_* constants). These images are managed by the dialog framework and must not be disposed by another party.

shortenText(String, Control) Shortens the specified text so that its width in pixels does not exceed the width of the given control, by inserting an ellipsis ("...") as necessary.

11.1.5. Dialog units

If you are positioning controls in the dialog area based on absolute positioning (null layout) rather than using a layout manager, such as GridLayout or FormLayout, then problems may arise when a different font is used. If the dialog is sized for a font with one pixel size and the user has his or her system set for a font in a different pixel size, then the controls will be either too big or too small for the font used. To alleviate this problem, you should position and size the controls based on the font's average character size or based on dialog units (see Figure 11-2).

Figure 11-2. Dialog units superimposed over the letter "T."


Dialog units are based on the current font and are independent of the display device; thus, they can be used to position controls within a dialog, independent of the font being used. They are defined as one-quarter of the average width of a character and one-eighth of the average height of a character.

dialog unit X = average character width / 4
dialog unit Y = average character height / 8

Therefore, use the following to convert from dialog units to pixels.

pixelX = (dialog unit X * average character width) / 4
pixelY = (dialog unit Y * average character height) / 8

The Eclipse dialog framework provides several convenient methods for converting dialog units or character sizes into pixel sizes.

convertHeightInCharsToPixels(int) Returns the number of pixels corresponding to the height of the given number of characters.

convertHorizontalDLUsToPixels(int) Returns the number of pixels corresponding to the given number of horizontal dialog units.

convertVerticalDLUsToPixels(int) Returns the number of pixels corresponding to the given number of vertical dialog units.

convertWidthInCharsToPixels(int) Returns the number of pixels corresponding to the width of the given number of characters.

11.1.6. Initial dialog location and size

The default behavior for dialogs as implemented by the dialog framework is to initially position a dialog on top of its parent window specified in the dialog's constructor. To provide a different initial location or size for a dialog, you would override the following methods as necessary.

getInitialLocation(Point) Returns the initial location to use for the dialog. The default implementation centers the dialog horizontally (half the difference to the left and half to the right) and vertically (one-third above and two-thirds below) relative to the parent shell or display bounds if there is no parent shell. The parameter is the initial size of the dialog, as returned by getInitialSize() method.

getInitialSize() Returns the initial size to use for the dialog. The default implementation returns the preferred size of the dialog based on the dialog's layout and controls using the computeSize method.

11.1.7. Resizable dialogs

By default, subclasses of Dialog are not resizable, but there are examples of resizable dialogs within the Eclipse framework such as:

org.eclipse.jdt.internal.ui.compare.ResizableDialog.

Unfortunately, this dialog is within an internal package, thus, should not be reused outside of its defining plug-in (see Section 20.2, Accessing Internal Code, on page 711). The first step to making your wizard resizable is to include the SWT.RESIZE and SWT.MAX styles when the dialog is created to allow the user to resize the dialog and display the maximize window button, as follows:

public ResizableDialog(Shell parentShell) {
   super(parentShell);
}

public ResizableDialog(IShellProvider parentShell) {
   super(parentShell);
   setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX);
}

Next, to preserve the size and location of the dialog across invocations, subclasses of this new class must supply a location in which to store values. For more about IDialogSettings see Section 11.2.7, Dialog settings, on page 441.

protected abstract IDialogSettings getDialogSettings();

Methods for loading the bounds from the dialog settings and saving the bounds into the dialog settings are neeed, as follows:

private static final String TAG_X = "x";
private static final String TAG_Y = "y";
private static final String TAG_WIDTH = "width";
private static final String TAG_HEIGHT = "height";

private Rectangle loadBounds() {
   IDialogSettings settings = getDialogSettings();
   try {
      return new Rectangle(
         settings.getInt(TAG_X),
         settings.getInt(TAG_Y),
         settings.getInt(TAG_WIDTH),
         settings.getInt(TAG_HEIGHT));
    }
    catch (NumberFormatException e) {
       return null;
    }
}
private void saveBounds(Rectangle bounds) {
   IDialogSettings settings = getDialogSettings();
   settings.put(TAG_X, bounds.x);
   settings.put(TAG_Y, bounds.y);
   settings.put(TAG_WIDTH, bounds.width);
   settings.put(TAG_HEIGHT, bounds.height);
}

You need to override the getInitialLocation() and getInitialSize() methods so that, when the dialog is first opened, its prior location and size are restored.

protected Rectangle cachedBounds;

protected Point getInitialSize() {

   // Track the current dialog bounds.
    getShell().addControlListener(new ControlListener() {
       public void controlMoved(ControlEvent arg0) {
          cachedBounds = getShell().getBounds();
       }
       public void controlResized(ControlEvent arg0) {
         cachedBounds = getShell().getBounds();
       }
   });

   // Answer the size from the previous incarnation.
   Rectangle b1 = getShell().getDisplay().getBounds();
   Rectangle b2 = loadBounds();
   if (b2 != null)
      return new Point(
         b1.width < b2.width ? b1.width : b2.width,
         b1.height < b1.height ? b2.height : b2.height);

   return super.getInitialSize();
}

protected Point getInitialLocation(Point initialSize) {

   // Answer the location from the previous incarnation.
   Rectangle displayBounds =
      getShell().getDisplay().getBounds();
   Rectangle bounds = loadBounds();
   if (bounds != null) {
      int x = bounds.x;
      int y = bounds.y;
      int maxX = displayBounds.x + displayBounds.width
            - initialSize.x;
      int maxY = displayBounds.y + displayBounds.height
            - initialSize.y;
      if (x > maxX)
         x = maxX;
      if (y > maxY)
         y = maxY;
      if (x < displayBounds.x)
         x = displayBounds.x;
      if (y < displayBounds.y)
         y = displayBounds.y;
      return new Point(x, y);
   }
   return super.getInitialLocation(initialSize);
 }

Finally, override the close method to save the dialog bounds for future incarnations:

public boolean close() {
   boolean closed = super.close();
   if (closed && cachedBounds != null)
      saveBounds(cachedBounds);
   return closed;
}

11.1.8. Favorites view filter dialog

As an example, create a specialized filter dialog for the Favorites view that presents the user the option of filtering content based on name, type, or location (see Section 7.2.7, Viewer filters, on page 281 and Section 7.3.4, Pull-down menu, on page 287). The dialog restricts itself to presenting and gathering information from the user and providing accessor methods for the filter action. Start by creating a new FavoritesFilterDialog class:

package com.qualityeclipse.favorites.dialogs;

import ...

public class FavoritesFilterDialog extends Dialog
{
   private String namePattern;
   private String locationPattern;
   private Collection selectedTypes;

   public FavoritesFilterDialog(
      Shell parentShell,
      String namePattern,
      String locationPattern,
      FavoriteItemType[] selectedTypes
   ) {
      super(parentShell);
      this.namePattern = namePattern;
      this.locationPattern = locationPattern;
      this.selectedTypes = new HashSet();
      for (int i = 0; i < selectedTypes.length; i++)
         this.selectedTypes.add(selectedTypes[i]);
   }

Next, override the createDialogArea() method to create the various fields that appear in the upper area of dialog.

private Text namePatternField;
private Text locationPatternField;

protected Control createDialogArea(Composite parent) {
   Composite container = (Composite) super.createDialogArea(parent);
   final GridLayout gridLayout = new GridLayout();
   gridLayout.numColumns = 2;
   container.setLayout(gridLayout);

   final Label filterLabel = new Label(container, SWT.NONE);
   filterLabel.setLayoutData(new GridData(GridData.BEGINNING,
         GridData.CENTER, false, false, 2, 1));
   filterLabel.setText("Enter a filter (* = any number of "
      + "characters, ? = any single character)"
      + "\nor an empty string for no filtering:");

   final Label nameLabel = new Label(container, SWT.NONE);
   nameLabel.setLayoutData(new GridData(GridData.END,
         GridData.CENTER, false, false));
   nameLabel.setText("Name:");

   namePatternField = new Text(container, SWT.BORDER);
   namePatternField.setLayoutData(new GridData(GridData.FILL,
         GridData.CENTER, true, false));

   final Label locationLabel = new Label(container, SWT.NONE);
   final GridData gridData = new GridData(GridData.END,
         GridData.CENTER, false, false);
   gridData.horizontalIndent = 20;
   locationLabel.setLayoutData(gridData);
   locationLabel.setText("Location:");

   locationPatternField = new Text(container, SWT.BORDER);
   locationPatternField.setLayoutData(new GridData(GridData.FILL,
         GridData.CENTER, true, false));

   final Label typesLabel = new Label(container, SWT.NONE);
   typesLabel.setLayoutData(new GridData(GridData.BEGINNING,
         GridData.CENTER, false, false, 2, 1));
   typesLabel.setText("Select the types of favorites to be shown:");

   final Composite typeCheckboxComposite = new Composite(container,
         SWT.NONE);
   final GridData gridData_1 = new GridData(GridData.FILL,
         GridData.FILL, false, false, 2, 1);
   gridData_1.horizontalIndent = 20;
   typeCheckboxComposite.setLayoutData(gridData_1);
   final GridLayout typeCheckboxLayout = new GridLayout();
   typeCheckboxLayout.numColumns = 2;
   typeCheckboxComposite.setLayout(typeCheckboxLayout);

   return container;
}

Next create a new createTypeCheckboxes() method, called at the end of the createDialogArea() method, to create one checkbox for each type.

private Map typeFields;

protected Control createDialogArea(Composite parent) {
   ... existing code ...
   createTypeCheckboxes(typeCheckboxComposite);
   return container;
}

private void createTypeCheckboxes(Composite parent) {
   typeFields = new HashMap();
   FavoriteItemType[] allTypes = FavoriteItemType.getTypes();
   for (int i = 0; i < allTypes.length; i++) {
      final FavoriteItemType eachType = allTypes[i];
      if (eachType == FavoriteItemType.UNKNOWN)
         continue;
      final Button button = new Button(parent, SWT.CHECK);
      button.setText(eachType.getName());
      typeFields.put(eachType, button);
      button.addSelectionListener(new SelectionAdapter() {
         public void widgetSelected(SelectionEvent e) {
            if (button.getSelection())
               selectedTypes.add(eachType);
            else
               selectedTypes.remove(eachType);
         }
      });
   }
}

Add the initContent() method that is called at the end of the createDialogArea() method to initialize the various fields in the dialog:

protected Control createDialogArea(Composite parent) {
   ... existing code ...
   createTypeCheckboxes(typeCheckboxComposite);
   initContent();
   return container;
}

private void initContent() {
   namePatternField.setText(namePattern != null ? namePattern : "");
   namePatternField.addModifyListener(new ModifyListener() {
      public void modifyText(ModifyEvent e) {
         namePattern = namePatternField.getText();
      }
   });

   locationPatternField
         .setText(locationPattern != null ? locationPattern : "");

   locationPatternField.addModifyListener(new ModifyListener() {
      public void modifyText(ModifyEvent e) {
         locationPattern = locationPatternField.getText();
      }
   });

   FavoriteItemType[] allTypes = FavoriteItemType.getTypes();
   for (int i = 0; i < allTypes.length; i++) {
      FavoriteItemType eachType = allTypes[i];
      if (eachType == FavoriteItemType.UNKNOWN)
         continue;
      Button button = (Button) typeFields.get(eachType);
      button.setSelection(selectedTypes.contains(eachType));
   }
}

Override the configureShell() method to set the dialog title:

protected void configureShell(Shell newShell) {
   super.configureShell(newShell);
   newShell.setText("Favorites View Filter Options");
}

Finally, add accessor methods for clients to extract the settings specified by the user when the dialog was opened:

public String getNamePattern() {
   return namePattern;
}

public String getLocationPattern() {
   return locationPattern;
}

public FavoriteItemType[] getSelectedTypes() {
   return (FavoriteItemType[]) selectedTypes
         .toArray(new FavoriteItemType[selectedTypes.size()]);
}

The filter action (see FavoritesViewFilterAction in Section 7.3.4, Pull-down menu, on page 287) must be modified to fill the dialog with the current filter settings, open the dialog, and process the specified filter settings if the user closes the dialog using the OK button. If the dialog is closed using the Cancel button or any other way besides the OK button, the changes are discarded as per standard dialog operation guidelines. The type and location view filters referenced in the following code are left as an exercise for the reader.

public void run() {
   FavoritesFilterDialog dialog =
      new FavoritesFilterDialog(
         shell,
         nameFilter.getPattern(),
         typeFilter.getTypes(),
         locationFilter.getPattern());
   if (dialog.open() != InputDialog.OK)
      return;
   nameFilter.setPattern(dialog.getNamePattern());
   locationFilter.setPattern(dialog.getLocationPattern());
   typeFilter.setPattern(dialog.getSelectedTypes());
}

Getting the preceding run() method to compile involves adding a new FavoritesViewLocationFilter and FavoritesViewTypeFilter similar to the existing FavoritesViewNameFilter. When these changes are complete, the filter dialog presents the filter settings to the user when the Filter... menu item is selected (see Figure 11-3).

Figure 11-3. New Favorites View Filter Options dialog.


11.1.9. Details dialog

One of the RFRS criteria includes identifying the plug-in and plug-in creator when reporting problems to the user. In other words, whenever the application needs to report an error message or exception to the user, the plug-in's unique identifier, version, and creator must be visible in the dialog. The org.eclipse.jface.dialogs.ErrorDialog can display exception information in a details section that is shown or hidden using a Details button, but it does not display the necessary product information as required by RFRS standards. To satisfy this requirement, ExceptionDetailsDialog was created (see Figure 11-4).

Figure 11-4. Details dialog with details hidden.


When the Details button is pressed, the dialog resizes itself to show additional information (see Figure 11-5).

Figure 11-5. Details dialog with details showing.


The ExceptionDetailsDialog class implements this expanding details behavior.

package com.qualityeclipse.favorites.dialogs;
import ...
public class ExceptionDetailsDialog extends AbstractDetailsDialog {
   private final Object details;
   private final Plugin plugin;

   public ExceptionDetailsDialog(Shell parentShell, String title,
         Image image, String message, Object details, Plugin plugin)
   {
      this(new SameShellProvider(parentShell), title, image, message,
            details, plugin);
   }
   public ExceptionDetailsDialog(IShellProvider parentShell,
         String title, Image image, String message, Object details,
         Plugin plugin)
    {
      super(parentShell, getTitle(title, details), getImage(image,
            details), getMessage(message, details));

      this.details = details;
      this.plugin = plugin;
    }

There are several utility methods that build content based on information provided in constructor arguments. The getTitle() method returns the title based on the provided title and details object.

public static String getTitle(String title, Object details) {
   if (title != null)
      return title;
   if (details instanceof Throwable) {
      Throwable e = (Throwable) details;
      while (e instanceof InvocationTargetException)
         e = ((InvocationTargetException) e).getTargetException();
      String name = e.getClass().getName();
      return name.substring(name.lastIndexOf('.') + 1);
   }
   return "Exception";
}

The getImage() method returns the image based on the provided image and details object.

public static Image getImage(Image image, Object details) {
   if (image != null)
      return image;
   Display display = Display.getCurrent();
   if (details instanceof IStatus) {
      switch (((IStatus) details).getSeverity()) {
         case IStatus.ERROR :
            return display.getSystemImage(SWT.ICON_ERROR);
         case IStatus.WARNING :
            return display.getSystemImage(SWT.ICON_WARNING);
         case IStatus.INFO :
            return display.getSystemImage(SWT.ICON_INFORMATION);
         case IStatus.OK :
            return null;
      }
   }
   return display.getSystemImage(SWT.ICON_ERROR);
}

The getMessage() method and helper methods build up a message based on the message and details objects provided.

public static String getMessage(String message, Object details) {
   if (details instanceof Throwable) {
      Throwable e = (Throwable) details;
      while (e instanceof InvocationTargetException)
         e = ((InvocationTargetException) e).getTargetException();
      if (message == null)
         return e.toString();
      return MessageFormat.format(
         message, new Object[] { e.toString() });
    }
    if (details instanceof IStatus) {
       String statusMessage = ((IStatus) details).getMessage();
       if (message == null)
          return statusMessage;
       return MessageFormat.format(
          message, new Object[] { statusMessage });
    }
    if (message != null)
       return message;
    return "An Exception occurred.";
}

public static void appendException(PrintWriter writer, Throwable ex)
{
   if (ex instanceof CoreException) {
      appendStatus(writer, ((CoreException) ex).getStatus(), 0);
      writer.println();
   }
   appendStackTrace(writer, ex);
   if (ex instanceof InvocationTargetException)
       appendException(writer, ((InvocationTargetException) ex)
             .getTargetException());
}

public static void appendStatus(
   PrintWriter writer, IStatus status, int nesting
) {
   for (int i = 0; i < nesting; i++)
      writer.print("  ");
   writer.println(status.getMessage());
   IStatus[] children = status.getChildren();
   for (int i = 0; i < children.length; i++)
      appendStatus(writer, children[i], nesting + 1);
}

public static void appendStackTrace(
   PrintWriter writer, Throwable ex
) {
   ex.printStackTrace(writer);
}

When the Details button is clicked, the superclass determines whether the details area needs to be shown or hidden and, as necessary, calls the createDetailsArea() method to create the content for the details area.

protected Control createDetailsArea(Composite parent) {

   // Create the details area.
   Composite panel = new Composite(parent, SWT.NONE);
   panel.setLayoutData(new GridData(GridData.FILL_BOTH));
   GridLayout layout = new GridLayout();
   layout.marginHeight = 0;
   layout.marginWidth = 0;
   panel.setLayout(layout);

   // Create the details content.
   createProductInfoArea(panel);
   createDetailsViewer(panel);

   return panel;
}

protected Composite createProductInfoArea(Composite parent) {

   // If no plugin specified, then nothing to display here.
   if (plugin == null)
      return null;

   Composite composite = new Composite(parent, SWT.NULL);
   composite.setLayoutData(new GridData());
   GridLayout layout = new GridLayout();
   layout.numColumns = 2;
   layout.marginWidth = convertHorizontalDLUsToPixels(
      IDialogConstants.HORIZONTAL_MARGIN);
   composite.setLayout(layout);

   Dictionary bundleHeaders = plugin.getBundle().getHeaders();
   String pluginId = plugin.getBundle().getSymbolicName();
   String pluginVendor =
      (String) bundleHeaders.get("Bundle-Vendor");
   String pluginName = (String) bundleHeaders.get("Bundle-Name");
   String pluginVersion =
      (String) bundleHeaders.get("Bundle-Version");

   new Label(composite, SWT.NONE).setText("Provider:");
   new Label(composite, SWT.NONE).setText(pluginVendor);
   new Label(composite, SWT.NONE).setText("Plug-in Name:");
   new Label(composite, SWT.NONE).setText(pluginName);
   new Label(composite, SWT.NONE).setText("Plug-in ID:");
   new Label(composite, SWT.NONE).setText(pluginId);
   new Label(composite, SWT.NONE).setText("Version:");
   new Label(composite, SWT.NONE).setText(pluginVersion);

   return composite;
}
protected Control createDetailsViewer(Composite parent) {
   if (details == null)
      return null;

   Text text = new Text(parent, SWT.MULTI | SWT.READ_ONLY
         | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
   text.setLayoutData(new GridData(GridData.FILL_BOTH));

   // Create the content.
   StringWriter writer = new StringWriter(1000);
   if (details instanceof Throwable)
      appendException(new PrintWriter(writer), (Throwable) details);
   else if (details instanceof IStatus)
      appendStatus(new PrintWriter(writer), (IStatus) details, 0);
   text.setText(writer.toString());

   return text;
}

The ExceptionDetailsDialog class is built on top of the more generic AbstractDetailsDialog class. This abstract dialog has a details section that can be shown or hidden by the user but subclasses are responsible for providing the content of the details section.

package com.qualityeclipse.favorites.dialogs;

import ...

public abstract class AbstractDetailsDialog extends Dialog
{
   private final String title;
   private final String message;
   private final Image image;

   public AbstractDetailsDialog(Shell parentShell, String title,
          Image image, String message)
   {
      this(new SameShellProvider(parentShell),title,image,message);
   }

   public AbstractDetailsDialog(IShellProvider parentShell,
         String title, Image image, String message)
   {
      super(parentShell);

      this.title = title;
      this.image = image;
      this.message = message;

      setShellStyle(SWT.DIALOG_TRIM | SWT.RESIZE
             | SWT.APPLICATION_MODAL);
   }

The configureShell() method is responsible for setting the title:

protected void configureShell(Shell shell) {
   super.configureShell(shell);
   if (title != null)
      shell.setText(title);
}

The createDialogArea() method creates and returns the contents of the upper part of this dialog (above the button bar). This includes an image, if specified, and a message.

protected Control createDialogArea(Composite parent) {
   Composite composite = (Composite) super.createDialogArea(parent);
   composite.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

   if (image != null) {
      ((GridLayout) composite.getLayout()).numColumns = 2;
      Label label = new Label(composite, 0);
      image.setBackground(label.getBackground());
      label.setImage(image);
      label.setLayoutData(new GridData(
            GridData.HORIZONTAL_ALIGN_CENTER
                  | GridData.VERTICAL_ALIGN_BEGINNING));
   }

   Label label = new Label(composite, SWT.WRAP);
   if (message != null)
      label.setText(message);
   GridData data = new GridData(GridData.FILL_HORIZONTAL
         | GridData.VERTICAL_ALIGN_CENTER);
   data.widthHint = convertHorizontalDLUsToPixels(
      IDialogConstants.MINIMUM_MESSAGE_AREA_WIDTH);
   label.setLayoutData(data);
   label.setFont(parent.getFont());

   return composite;
}

Override the createButtonsForButtonBar() method to create OK and Details buttons.

private Button detailsButton;

protected void createButtonsForButtonBar(Composite parent) {
   createButton(parent, IDialogConstants.OK_ID,
         IDialogConstants.OK_LABEL, false);
   detailsButton = createButton(parent, IDialogConstants.DETAILS_ID,
         IDialogConstants.SHOW_DETAILS_LABEL, false);
}

The buttonPressed() method is called when either the OK or Details buttons is pressed. Override this method to alternately show or hide the details area if the Details button is pressed.

private Control detailsArea;
private Point cachedWindowSize;

protected void buttonPressed(int id) {
   if (id == IDialogConstants.DETAILS_ID)
      toggleDetailsArea();
   else
      super.buttonPressed(id);
}

protected void toggleDetailsArea() {
   Point oldWindowSize = getShell().getSize();
   Point newWindowSize = cachedWindowSize;
   cachedWindowSize = oldWindowSize;

   // Show the details area.
   if (detailsArea == null) {
      detailsArea = createDetailsArea((Composite) getContents());
      detailsButton.setText(IDialogConstants.HIDE_DETAILS_LABEL);
   }

   // Hide the details area.
   else {
      detailsArea.dispose();
      detailsArea = null;
      detailsButton.setText(IDialogConstants.SHOW_DETAILS_LABEL);
    }

    /*
     * Must be sure to call
     *    getContents().computeSize(SWT.DEFAULT, SWT.DEFAULT)
     * before calling
     *    getShell().setSize(newWindowSize)
     * since controls have been added or removed.
     */

    // Compute the new window size.
    Point oldSize = getContents().getSize();
    Point newSize = getContents().computeSize(
       SWT.DEFAULT, SWT.DEFAULT);
    if (newWindowSize == null)
       newWindowSize = new Point(oldWindowSize.x, oldWindowSize.y
          + (newSize.y - oldSize.y));
    // Crop new window size to screen.
    Point windowLoc = getShell().getLocation();
    Rectangle screenArea =
       getContents().getDisplay().getClientArea();
    if (newWindowSize.y > screenArea.height
          - (windowLoc.y - screenArea.y))
       newWindowSize.y = screenArea.height
             - (windowLoc.y - screenArea.y);

    getShell().setSize(newWindowSize);
    ((Composite) getContents()).layout();
}

Finally, subclasses must implement createDetailsArea() to provide content for the area of the dialog made visible when the Details button is clicked.

protected abstract Control createDetailsArea(Composite parent);

11.1.10. Opening a dialogfinding a parent shell

When constructing a new dialog, you need to know either the parent shell or an object that can provide a parent shell (an object that has a getShell() method or implements the IShellProvider interface). You can specify null for the parent shell, but this will prevent proper association of the dialog with its parent; if the dialog is modal as many dialogs are, then specifying the correct parent shell or shell provider will prevent the user from being able to activate the parent window before closing the dialog. So the question becomes: How do you obtain the parent shell?

IWorkbenchWindowActionDelegate (see example code in Section 6.2.6, Creating an action delegate, on page 216)If you have an action delegate, then Eclipse provides the workbench window from which a shell can be obtained. Immediately after the action delegate is instantiated, Eclipse calls the init() method with the workbench window as the argument. Cache this window and pass the window's shell as an argument when constructing your dialog:

private IWorkbenchWindow window;

public void init(IWorkbenchWindow window) {
   this.window = window;
}
public void run(IAction action) {
   Shell parentShell = window.getShell();
   MyDialog dialog = new MyDialog(parentShell, ...);
   ... etc ...
}

IObjectActionDelegate (see Section 6.3.3, IObjectActionDelegate, on page 233)If you have an action in a context menu, Eclipse provides the target part from which a shell provider can be obtained. Before the run() method is called, Eclipse calls setActivePart() with the target part. Cache this part and pass the site containing the part as an argument when constructing your dialog.

private IWorkbenchPart targetPart;

public void setActivePart(IAction action, IWorkbenchPart targetPart)
{
   this.targetPart = targetPart;
}

public void run(IAction action) {
   IWorkbenchPartSite site = targetPart.getSite();
   MyDialog dialog = new MyDialog(site, ...);
   ... etc ...
}

IViewPart or IEditorPart (see Section 7.2, View Part, on page 263 or Section 8.2, Editor Part, on page 330)If you have a view or editor, then, similar to the preceding code, you can obtain a shell provider:

IShellProvider shellProvider = viewOrEditor.getSite();

PlatformUIThe platform UI provides the workbench window from which a shell can be obtained.

Shell parentShell =
   PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();

Display (see Section 4.2.5.1, Display, on page 140)If all else fails, you can obtain the shell of the active window from Display.

Shell parentShell = Display.getDefault().getActiveShell();


Previous Page
Next Page