SWT Jface Eclipse Java

import java.io.InputStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ResourceBundle;
import java.util.Vector;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.SWTException;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.ImageLoaderEvent;
import org.eclipse.swt.graphics.ImageLoaderListener;
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.layout.RowLayout;
import org.eclipse.swt.printing.PrintDialog;
import org.eclipse.swt.printing.Printer;
import org.eclipse.swt.printing.PrinterData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Group;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class ImageAnalyzer {
  Display display;
  Shell shell;
  Canvas imageCanvas, paletteCanvas;
  Label typeLabel, sizeLabel, depthLabel, transparentPixelLabel,
      timeToLoadLabel, screenSizeLabel, backgroundPixelLabel,
      locationLabel, disposalMethodLabel, delayTimeLabel,
      repeatCountLabel, paletteLabel, dataLabel, statusLabel;
  Combo backgroundCombo, scaleXCombo, scaleYCombo, alphaCombo;
  Button incrementalCheck, transparentCheck, maskCheck, backgroundCheck;
  Button previousButton, nextButton, animateButton;
  StyledText dataText;
  Sash sash;
  Color whiteColor, blackColor, redColor, greenColor, blueColor,
      canvasBackground;
  Font fixedWidthFont;
  Cursor crossCursor;
  GC imageCanvasGC;
  int paletteWidth = 140; // recalculated and used as a width hint
  int ix = 0, iy = 0, py = 0; // used to scroll the image and palette
  float xscale = 1, yscale = 1; // used to scale the image
  int alpha = 255; // used to modify the alpha value of the image
  boolean incremental = false; // used to incrementally display an image
  boolean transparent = true; // used to display an image with transparency
  boolean showMask = false; // used to display an icon mask or transparent
                // image mask
  boolean showBackground = false; // used to display the background of an
                  // animated image
  boolean animate = false; // used to animate a multi-image file
  Thread animateThread; // draws animated images
  Thread incrementalThread; // draws incremental images
  String lastPath; // used to seed the file dialog
  String currentName; // the current image file or URL name
  String fileName; // the current image file
  ImageLoader loader; // the loader for the current image file
  ImageData[] imageDataArray; // all image data read from the current file
  int imageDataIndex; // the index of the current image data
  ImageData imageData; // the currently-displayed image data
  Image image; // the currently-displayed image
  Vector incrementalEvents; // incremental image events
  long loadTime = 0; // the time it took to load the current image
  static final int INDEX_DIGITS = 4;
  static final int ALPHA_CONSTANT = 0;
  static final int ALPHA_X = 1;
  static final int ALPHA_Y = 2;
  class TextPrompter extends Dialog {
    String message = "";
    String result = null;
    Shell dialog;
    Text text;
    public TextPrompter(Shell parent, int style) {
      super(parent, style);
    }
    public TextPrompter(Shell parent) {
      this(parent, SWT.APPLICATION_MODAL);
    }
    public String getMessage() {
      return message;
    }
    public void setMessage(String string) {
      message = string;
    }
    public String open() {
      dialog = new Shell(getParent(), getStyle());
      dialog.setText(getText());
      dialog.setLayout(new GridLayout());
      Label label = new Label(dialog, SWT.NULL);
      label.setText(message);
      label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
      text = new Text(dialog, SWT.SINGLE | SWT.BORDER);
      GridData data = new GridData(GridData.FILL_HORIZONTAL);
      data.widthHint = 300;
      text.setLayoutData(data);
      Composite buttons = new Composite(dialog, SWT.NONE);
      GridLayout grid = new GridLayout();
      grid.numColumns = 2;
      buttons.setLayout(grid);
      buttons.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END));
      Button ok = new Button(buttons, SWT.PUSH);
      ok.setText("OK");
      data = new GridData();
      data.widthHint = 75;
      ok.setLayoutData(data);
      ok.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          result = text.getText();
          dialog.dispose();
        }
      });
      Button cancel = new Button(buttons, SWT.PUSH);
      cancel.setText("Cancel");
      data = new GridData();
      data.widthHint = 75;
      cancel.setLayoutData(data);
      cancel.addSelectionListener(new SelectionAdapter() {
        public void widgetSelected(SelectionEvent e) {
          dialog.dispose();
        }
      });
      dialog.setDefaultButton(ok);
      dialog.pack();
      dialog.open();
      while (!dialog.isDisposed()) {
        if (!display.readAndDispatch())
          display.sleep();
      }
      return result;
    }
  }
  public static void main(String[] args) {
    Display display = new Display();
    ImageAnalyzer imageAnalyzer = new ImageAnalyzer();
    Shell shell = imageAnalyzer.open(display);
    while (!shell.isDisposed())
      if (!display.readAndDispatch())
        display.sleep();
    display.dispose();
  }
  public Shell open(Display dpy) {
    // Create a window and set its title.
    this.display = dpy;
    shell = new Shell(display);
    shell.setText("Image_analyzer");
    // Hook resize and dispose listeners.
    shell.addControlListener(new ControlAdapter() {
      public void controlResized(ControlEvent event) {
        resizeShell(event);
      }
    });
    shell.addShellListener(new ShellAdapter() {
      public void shellClosed(ShellEvent e) {
        animate = false; // stop any animation in progress
        if (animateThread != null) {
          // wait for the thread to die before disposing the shell.
          while (animateThread.isAlive()) {
            if (!display.readAndDispatch())
              display.sleep();
          }
        }
        e.doit = true;
      }
    });
    shell.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        // Clean up.
        if (image != null)
          image.dispose();
        whiteColor.dispose();
        blackColor.dispose();
        redColor.dispose();
        greenColor.dispose();
        blueColor.dispose();
        fixedWidthFont.dispose();
        crossCursor.dispose();
      }
    });
    // Create colors and fonts.
    whiteColor = new Color(display, 255, 255, 255);
    blackColor = new Color(display, 0, 0, 0);
    redColor = new Color(display, 255, 0, 0);
    greenColor = new Color(display, 0, 255, 0);
    blueColor = new Color(display, 0, 0, 255);
    fixedWidthFont = new Font(display, "courier", 10, 0);
    crossCursor = new Cursor(display, SWT.CURSOR_CROSS);
    // Add a menu bar and widgets.
    createMenuBar();
    createWidgets();
    shell.pack();
    // Create a GC for drawing, and hook the listener to dispose it.
    imageCanvasGC = new GC(imageCanvas);
    imageCanvas.addDisposeListener(new DisposeListener() {
      public void widgetDisposed(DisposeEvent e) {
        imageCanvasGC.dispose();
      }
    });
    // Open the window
    shell.open();
    return shell;
  }
  void createWidgets() {
    // Add the widgets to the shell in a grid layout.
    GridLayout layout = new GridLayout();
    layout.marginHeight = 0;
    layout.numColumns = 2;
    shell.setLayout(layout);
    // Separate the menu bar from the rest of the widgets.
    Label separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
    GridData gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = GridData.FILL;
    separator.setLayoutData(gridData);
    // Add a composite to contain some control widgets across the top.
    Composite controls = new Composite(shell, SWT.NULL);
    RowLayout rowLayout = new RowLayout();
    rowLayout.marginTop = 0;
    rowLayout.marginBottom = 5;
    rowLayout.spacing = 8;
    controls.setLayout(rowLayout);
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    controls.setLayoutData(gridData);
    // Combo to change the background.
    Group group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("Background");
    backgroundCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY);
    backgroundCombo.setItems(new String[] { "None",
        "White", "Black",
        "Red", "Green",
        "Blue" });
    backgroundCombo.select(backgroundCombo.indexOf("White"));
    backgroundCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        changeBackground();
      }
    });
    // Combo to change the x scale.
    String[] values = { "0.1", "0.2", "0.3", "0.4", "0.5", "0.6", "0.7",
        "0.8", "0.9", "1", "1.1", "1.2", "1.3", "1.4", "1.5", "1.6",
        "1.7", "1.8", "1.9", "2", "3", "4", "5", "6", "7", "8", "9",
        "10", };
    group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("X_scale");
    scaleXCombo = new Combo(group, SWT.DROP_DOWN);
    for (int i = 0; i < values.length; i++) {
      scaleXCombo.add(values[i]);
    }
    scaleXCombo.select(scaleXCombo.indexOf("1"));
    scaleXCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        scaleX();
      }
    });
    // Combo to change the y scale.
    group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("Y_scale");
    scaleYCombo = new Combo(group, SWT.DROP_DOWN);
    for (int i = 0; i < values.length; i++) {
      scaleYCombo.add(values[i]);
    }
    scaleYCombo.select(scaleYCombo.indexOf("1"));
    scaleYCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        scaleY();
      }
    });
    // Combo to change the alpha value.
    group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("Alpha_K");
    alphaCombo = new Combo(group, SWT.DROP_DOWN | SWT.READ_ONLY);
    for (int i = 0; i <= 255; i += 5) {
      alphaCombo.add(String.valueOf(i));
    }
    alphaCombo.select(alphaCombo.indexOf("255"));
    alphaCombo.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        alpha();
      }
    });
    // Check box to request incremental display.
    group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("Display");
    incrementalCheck = new Button(group, SWT.CHECK);
    incrementalCheck.setText("Incremental");
    incrementalCheck.setSelection(incremental);
    incrementalCheck.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        incremental = ((Button) event.widget).getSelection();
      }
    });
    // Check box to request transparent display.
    transparentCheck = new Button(group, SWT.CHECK);
    transparentCheck.setText("Transparent");
    transparentCheck.setSelection(transparent);
    transparentCheck.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        transparent = ((Button) event.widget).getSelection();
        if (image != null) {
          imageCanvas.redraw();
        }
      }
    });
    // Check box to request mask display.
    maskCheck = new Button(group, SWT.CHECK);
    maskCheck.setText("Mask");
    maskCheck.setSelection(showMask);
    maskCheck.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        showMask = ((Button) event.widget).getSelection();
        if (image != null) {
          imageCanvas.redraw();
        }
      }
    });
    // Check box to request background display.
    backgroundCheck = new Button(group, SWT.CHECK);
    backgroundCheck.setText("Background");
    backgroundCheck.setSelection(showBackground);
    backgroundCheck.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        showBackground = ((Button) event.widget).getSelection();
      }
    });
    // Group the animation buttons.
    group = new Group(controls, SWT.NULL);
    group.setLayout(new RowLayout());
    group.setText("Animation");
    // Push button to display the previous image in a multi-image file.
    previousButton = new Button(group, SWT.PUSH);
    previousButton.setText("Previous");
    previousButton.setEnabled(false);
    previousButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        previous();
      }
    });
    // Push button to display the next image in a multi-image file.
    nextButton = new Button(group, SWT.PUSH);
    nextButton.setText("Next");
    nextButton.setEnabled(false);
    nextButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        next();
      }
    });
    // Push button to toggle animation of a multi-image file.
    animateButton = new Button(group, SWT.PUSH);
    animateButton.setText("Animate");
    animateButton.setEnabled(false);
    animateButton.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        animate();
      }
    });
    // Label to show the image file type.
    typeLabel = new Label(shell, SWT.NULL);
    typeLabel.setText("Type_initial");
    typeLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Canvas to show the image.
    imageCanvas = new Canvas(shell, SWT.V_SCROLL | SWT.H_SCROLL
        | SWT.NO_REDRAW_RESIZE);
    imageCanvas.setBackground(whiteColor);
    imageCanvas.setCursor(crossCursor);
    gridData = new GridData();
    gridData.verticalSpan = 15;
    gridData.horizontalAlignment = GridData.FILL;
    gridData.verticalAlignment = GridData.FILL;
    gridData.grabExcessHorizontalSpace = true;
    gridData.grabExcessVerticalSpace = true;
    imageCanvas.setLayoutData(gridData);
    imageCanvas.addPaintListener(new PaintListener() {
      public void paintControl(PaintEvent event) {
        if (image != null)
          paintImage(event);
      }
    });
    imageCanvas.addMouseMoveListener(new MouseMoveListener() {
      public void mouseMove(MouseEvent event) {
        if (image != null) {
          showColorAt(event.x, event.y);
        }
      }
    });
    // Set up the image canvas scroll bars.
    ScrollBar horizontal = imageCanvas.getHorizontalBar();
    horizontal.setVisible(true);
    horizontal.setMinimum(0);
    horizontal.setEnabled(false);
    horizontal.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        scrollHorizontally((ScrollBar) event.widget);
      }
    });
    ScrollBar vertical = imageCanvas.getVerticalBar();
    vertical.setVisible(true);
    vertical.setMinimum(0);
    vertical.setEnabled(false);
    vertical.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        scrollVertically((ScrollBar) event.widget);
      }
    });
    // Label to show the image size.
    sizeLabel = new Label(shell, SWT.NULL);
    sizeLabel.setText("Size_initial");
    sizeLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the image depth.
    depthLabel = new Label(shell, SWT.NULL);
    depthLabel.setText("Depth_initial");
    depthLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the transparent pixel.
    transparentPixelLabel = new Label(shell, SWT.NULL);
    transparentPixelLabel.setText("Transparent_pixel_initial");
    transparentPixelLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the time to load.
    timeToLoadLabel = new Label(shell, SWT.NULL);
    timeToLoadLabel.setText("Time_to_load_initial");
    timeToLoadLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Separate the animation fields from the rest of the fields.
    separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
    separator.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the logical screen size for animation.
    screenSizeLabel = new Label(shell, SWT.NULL);
    screenSizeLabel.setText("Animation_size_initial");
    screenSizeLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the background pixel.
    backgroundPixelLabel = new Label(shell, SWT.NULL);
    backgroundPixelLabel.setText("Background_pixel_initial");
    backgroundPixelLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the image location (x, y).
    locationLabel = new Label(shell, SWT.NULL);
    locationLabel.setText("Image_location_initial");
    locationLabel
        .setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the image disposal method.
    disposalMethodLabel = new Label(shell, SWT.NULL);
    disposalMethodLabel.setText("Disposal_initial");
    disposalMethodLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the image delay time.
    delayTimeLabel = new Label(shell, SWT.NULL);
    delayTimeLabel.setText("Delay_initial");
    delayTimeLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show the background pixel.
    repeatCountLabel = new Label(shell, SWT.NULL);
    repeatCountLabel.setText("Repeats_initial");
    repeatCountLabel.setLayoutData(new GridData(
        GridData.HORIZONTAL_ALIGN_FILL));
    // Separate the animation fields from the palette.
    separator = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL);
    separator.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Label to show if the image has a direct or indexed palette.
    paletteLabel = new Label(shell, SWT.NULL);
    paletteLabel.setText("Palette_initial");
    paletteLabel
        .setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_FILL));
    // Canvas to show the image's palette.
    paletteCanvas = new Canvas(shell, SWT.BORDER | SWT.V_SCROLL
        | SWT.NO_REDRAW_RESIZE);
    paletteCanvas.setFont(fixedWidthFont);
    paletteCanvas.getVerticalBar().setVisible(true);
    gridData = new GridData();
    gridData.horizontalAlignment = GridData.FILL;
    gridData.verticalAlignment = GridData.FILL;
    GC gc = new GC(paletteLabel);
    paletteWidth = gc.stringExtent("Max_length_string").x;
    gc.dispose();
    gridData.widthHint = paletteWidth;
    gridData.heightHint = 16 * 11; // show at least 16 colors
    paletteCanvas.setLayoutData(gridData);
    paletteCanvas.addPaintListener(new PaintListener() {
      public void paintControl(PaintEvent event) {
        if (image != null)
          paintPalette(event);
      }
    });
    // Set up the palette canvas scroll bar.
    vertical = paletteCanvas.getVerticalBar();
    vertical.setVisible(true);
    vertical.setMinimum(0);
    vertical.setIncrement(10);
    vertical.setEnabled(false);
    vertical.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        scrollPalette((ScrollBar) event.widget);
      }
    });
    // Sash to see more of image or image data.
    sash = new Sash(shell, SWT.HORIZONTAL);
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = GridData.FILL;
    sash.setLayoutData(gridData);
    sash.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        if (event.detail != SWT.DRAG) {
          ((GridData) paletteCanvas.getLayoutData()).heightHint = SWT.DEFAULT;
          Rectangle paletteCanvasBounds = paletteCanvas.getBounds();
          int minY = paletteCanvasBounds.y + 20;
          Rectangle dataLabelBounds = dataLabel.getBounds();
          int maxY = statusLabel.getBounds().y
              - dataLabelBounds.height - 20;
          if (event.y > minY && event.y < maxY) {
            Rectangle oldSash = sash.getBounds();
            sash.setBounds(event.x, event.y, event.width,
                event.height);
            int diff = event.y - oldSash.y;
            Rectangle bounds = imageCanvas.getBounds();
            imageCanvas.setBounds(bounds.x, bounds.y, bounds.width,
                bounds.height + diff);
            bounds = paletteCanvasBounds;
            paletteCanvas.setBounds(bounds.x, bounds.y,
                bounds.width, bounds.height + diff);
            bounds = dataLabelBounds;
            dataLabel.setBounds(bounds.x, bounds.y + diff,
                bounds.width, bounds.height);
            bounds = dataText.getBounds();
            dataText.setBounds(bounds.x, bounds.y + diff,
                bounds.width, bounds.height - diff);
            // shell.layout(true);
          }
        }
      }
    });
    // Label to show data-specific fields.
    dataLabel = new Label(shell, SWT.NULL);
    dataLabel.setText("Pixel_data_initial");
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = GridData.FILL;
    dataLabel.setLayoutData(gridData);
    // Text to show a dump of the data.
    dataText = new StyledText(shell, SWT.BORDER | SWT.MULTI | SWT.READ_ONLY
        | SWT.V_SCROLL | SWT.H_SCROLL);
    dataText.setBackground(display
        .getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
    dataText.setFont(fixedWidthFont);
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = GridData.FILL;
    gridData.verticalAlignment = GridData.FILL;
    gridData.heightHint = 128;
    gridData.grabExcessVerticalSpace = true;
    dataText.setLayoutData(gridData);
    dataText.addMouseListener(new MouseAdapter() {
      public void mouseDown(MouseEvent event) {
        if (image != null && event.button == 1) {
          showColorForData();
        }
      }
    });
    dataText.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        if (image != null) {
          showColorForData();
        }
      }
    });
    // Label to show status and cursor location in image.
    statusLabel = new Label(shell, SWT.NULL);
    statusLabel.setText("");
    gridData = new GridData();
    gridData.horizontalSpan = 2;
    gridData.horizontalAlignment = GridData.FILL;
    statusLabel.setLayoutData(gridData);
  }
  Menu createMenuBar() {
    // Menu bar.
    Menu menuBar = new Menu(shell, SWT.BAR);
    shell.setMenuBar(menuBar);
    createFileMenu(menuBar);
    createAlphaMenu(menuBar);
    return menuBar;
  }
  void createFileMenu(Menu menuBar) {
    // File menu
    MenuItem item = new MenuItem(menuBar, SWT.CASCADE);
    item.setText("File");
    Menu fileMenu = new Menu(shell, SWT.DROP_DOWN);
    item.setMenu(fileMenu);
    // File -> Open File...
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("OpenFile");
    item.setAccelerator(SWT.MOD1 + 'O');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuOpenFile();
      }
    });
    // File -> Open URL...
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("OpenURL");
    item.setAccelerator(SWT.MOD1 + 'U');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuOpenURL();
      }
    });
    // File -> Reopen
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Reopen");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuReopen();
      }
    });
    new MenuItem(fileMenu, SWT.SEPARATOR);
    // File -> Save
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Save");
    item.setAccelerator(SWT.MOD1 + 'S');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuSave();
      }
    });
    // File -> Save As...
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Save_as");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuSaveAs();
      }
    });
    // File -> Save Mask As...
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Save_mask_as");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuSaveMaskAs();
      }
    });
    new MenuItem(fileMenu, SWT.SEPARATOR);
    // File -> Print
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Print");
    item.setAccelerator(SWT.MOD1 + 'P');
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuPrint();
      }
    });
    new MenuItem(fileMenu, SWT.SEPARATOR);
    // File -> Exit
    item = new MenuItem(fileMenu, SWT.PUSH);
    item.setText("Exit");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        shell.close();
      }
    });
  }
  void createAlphaMenu(Menu menuBar) {
    // Alpha menu
    MenuItem item = new MenuItem(menuBar, SWT.CASCADE);
    item.setText("Alpha");
    Menu alphaMenu = new Menu(shell, SWT.DROP_DOWN);
    item.setMenu(alphaMenu);
    // Alpha -> K
    item = new MenuItem(alphaMenu, SWT.PUSH);
    item.setText("K");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuComposeAlpha(ALPHA_CONSTANT);
      }
    });
    // Alpha -> (K + x) % 256
    item = new MenuItem(alphaMenu, SWT.PUSH);
    item.setText("(K + x) % 256");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuComposeAlpha(ALPHA_X);
      }
    });
    // Alpha -> (K + y) % 256
    item = new MenuItem(alphaMenu, SWT.PUSH);
    item.setText("(K + y) % 256");
    item.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        menuComposeAlpha(ALPHA_Y);
      }
    });
  }
  void menuComposeAlpha(int alpha_op) {
    if (image == null)
      return;
    animate = false; // stop any animation in progress
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      if (alpha_op == ALPHA_CONSTANT) {
        imageData.alpha = alpha;
      } else {
        imageData.alpha = -1;
        switch (alpha_op) {
        case ALPHA_X:
          for (int y = 0; y < imageData.height; y++) {
            for (int x = 0; x < imageData.width; x++) {
              imageData.setAlpha(x, y, (x + alpha) % 256);
            }
          }
          break;
        case ALPHA_Y:
          for (int y = 0; y < imageData.height; y++) {
            for (int x = 0; x < imageData.width; x++) {
              imageData.setAlpha(x, y, (y + alpha) % 256);
            }
          }
          break;
        default:
          break;
        }
      }
      displayImage(imageData);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void menuOpenFile() {
    animate = false; // stop any animation in progress
    resetScaleCombos();
    // Get the user to choose an image file.
    FileDialog fileChooser = new FileDialog(shell, SWT.OPEN);
    if (lastPath != null)
      fileChooser.setFilterPath(lastPath);
    fileChooser.setFilterExtensions(new String[] {
        "*.bmp; *.gif; *.ico; *.jpg; *.pcx; *.png; *.tif", "*.bmp",
        "*.gif", "*.ico", "*.jpg", "*.pcx", "*.png", "*.tif" });
    fileChooser.setFilterNames(new String[] {
        "All_images"
            + " (bmp, gif, ico, jpg, pcx, png, tif)",
        "BMP (*.bmp)", "GIF (*.gif)", "ICO (*.ico)", "JPEG (*.jpg)",
        "PCX (*.pcx)", "PNG (*.png)", "TIFF (*.tif)" });
    String filename = fileChooser.open();
    lastPath = fileChooser.getFilterPath();
    if (filename == null)
      return;
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      loader = new ImageLoader();
      if (incremental) {
        // Prepare to handle incremental events.
        loader.addImageLoaderListener(new ImageLoaderListener() {
          public void imageDataLoaded(ImageLoaderEvent event) {
            incrementalDataLoaded(event);
          }
        });
        incrementalThreadStart();
      }
      // Read the new image(s) from the chosen file.
      long startTime = System.currentTimeMillis();
      imageDataArray = loader.load(filename);
      loadTime = System.currentTimeMillis() - startTime;
      if (imageDataArray.length > 0) {
        // Cache the filename.
        currentName = filename;
        fileName = filename;
        // If there are multiple images in the file (typically GIF)
        // then enable the Previous, Next and Animate buttons.
        previousButton.setEnabled(imageDataArray.length > 1);
        nextButton.setEnabled(imageDataArray.length > 1);
        animateButton.setEnabled(imageDataArray.length > 1
            && loader.logicalScreenWidth > 0
            && loader.logicalScreenHeight > 0);
        // Display the first image in the file.
        imageDataIndex = 0;
        displayImage(imageDataArray[imageDataIndex]);
        resetScrollBars();
      }
    } catch (SWTException e) {
      showErrorDialog("Loading_lc", filename, e);
    } catch (SWTError e) {
      showErrorDialog("Loading_lc", filename, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void menuOpenURL() {
    animate = false; // stop any animation in progress
    resetScaleCombos();
    // Get the user to choose an image URL.
    TextPrompter textPrompter = new TextPrompter(shell,
        SWT.APPLICATION_MODAL | SWT.DIALOG_TRIM);
    textPrompter.setText("OpenURLDialog");
    textPrompter.setMessage("EnterURL");
    String urlname = textPrompter.open();
    if (urlname == null)
      return;
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      URL url = new URL(urlname);
      InputStream stream = url.openStream();
      loader = new ImageLoader();
      if (incremental) {
        // Prepare to handle incremental events.
        loader.addImageLoaderListener(new ImageLoaderListener() {
          public void imageDataLoaded(ImageLoaderEvent event) {
            incrementalDataLoaded(event);
          }
        });
        incrementalThreadStart();
      }
      // Read the new image(s) from the chosen URL.
      long startTime = System.currentTimeMillis();
      imageDataArray = loader.load(stream);
      stream.close();
      loadTime = System.currentTimeMillis() - startTime;
      if (imageDataArray.length > 0) {
        currentName = urlname;
        fileName = null;
        // If there are multiple images (typically GIF)
        // then enable the Previous, Next and Animate buttons.
        previousButton.setEnabled(imageDataArray.length > 1);
        nextButton.setEnabled(imageDataArray.length > 1);
        animateButton.setEnabled(imageDataArray.length > 1
            && loader.logicalScreenWidth > 0
            && loader.logicalScreenHeight > 0);
        // Display the first image.
        imageDataIndex = 0;
        displayImage(imageDataArray[imageDataIndex]);
        resetScrollBars();
      }
    } catch (Exception e) {
      showErrorDialog("Loading", urlname, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  /*
   * Called to start a thread that draws incremental images as they are
   * loaded.
   */
  void incrementalThreadStart() {
    incrementalEvents = new Vector();
    incrementalThread = new Thread("Incremental") {
      public void run() {
        // Draw the first ImageData increment.
        while (incrementalEvents != null) {
          // Synchronize so we don't try to remove when the vector is
          // null.
          synchronized (ImageAnalyzer.this) {
            if (incrementalEvents != null) {
              if (incrementalEvents.size() > 0) {
                ImageLoaderEvent event = (ImageLoaderEvent) incrementalEvents
                    .remove(0);
                if (image != null)
                  image.dispose();
                image = new Image(display, event.imageData);
                imageData = event.imageData;
                imageCanvasGC.drawImage(image, 0, 0,
                    imageData.width, imageData.height,
                    imageData.x, imageData.y,
                    imageData.width, imageData.height);
              } else {
                yield();
              }
            }
          }
        }
        display.wake();
      }
    };
    incrementalThread.setDaemon(true);
    incrementalThread.start();
  }
  /*
   * Called when incremental image data has been loaded, for example, for
   * interlaced GIF/PNG or progressive JPEG.
   */
  void incrementalDataLoaded(ImageLoaderEvent event) {
    // Synchronize so that we do not try to add while
    // the incremental drawing thread is removing.
    synchronized (this) {
      incrementalEvents.addElement(event);
    }
  }
  void menuSave() {
    if (image == null)
      return;
    animate = false; // stop any animation in progress
    // If the image file type is unknown, we can't 'Save',
    // so we have to use 'Save As...'.
    if (imageData.type == SWT.IMAGE_UNDEFINED || fileName == null) {
      menuSaveAs();
      return;
    }
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      // Save the current image to the current file.
      loader.data = new ImageData[] { imageData };
      loader.save(fileName, imageData.type);
    } catch (SWTException e) {
      showErrorDialog("Saving_lc", fileName, e);
    } catch (SWTError e) {
      showErrorDialog("Saving_lc", fileName, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void menuSaveAs() {
    if (image == null)
      return;
    animate = false; // stop any animation in progress
    // Get the user to choose a file name and type to save.
    FileDialog fileChooser = new FileDialog(shell, SWT.SAVE);
    fileChooser.setFilterPath(lastPath);
    if (fileName != null) {
      String name = fileName;
      int nameStart = name.lastIndexOf(java.io.File.separatorChar);
      if (nameStart > -1) {
        name = name.substring(nameStart + 1);
      }
      fileChooser.setFileName(name);
    }
    fileChooser.setFilterExtensions(new String[] { "*.bmp", "*.gif",
        "*.ico", "*.jpg", "*.png" });
    fileChooser.setFilterNames(new String[] { "BMP (*.bmp)", "GIF (*.gif)",
        "ICO (*.ico)", "JPEG (*.jpg)", "PNG (*.png)" });
    String filename = fileChooser.open();
    lastPath = fileChooser.getFilterPath();
    if (filename == null)
      return;
    // Figure out what file type the user wants saved.
    // We need to rely on the file extension because FileDialog
    // does not have API for asking what filter type was selected.
    int filetype = determineFileType(filename);
    if (filetype == SWT.IMAGE_UNDEFINED) {
      MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
      box.setMessage(createMsg("Unknown_extension",
          filename.substring(filename.lastIndexOf('.') + 1)));
      box.open();
      return;
    }
    if (new java.io.File(filename).exists()) {
      MessageBox box = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK
          | SWT.CANCEL);
      box.setMessage(createMsg("Overwrite", filename));
      if (box.open() == SWT.CANCEL)
        return;
    }
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      // Save the current image to the specified file.
      loader.data = new ImageData[] { imageData };
      loader.save(filename, filetype);
      // Update the shell title and file type label,
      // and use the new file.
      fileName = filename;
      shell.setText(createMsg("Analyzer_on", filename));
      typeLabel.setText(createMsg("Type_string",
          fileTypeString(filetype)));
    } catch (SWTException e) {
      showErrorDialog("Saving_lc", filename, e);
    } catch (SWTError e) {
      showErrorDialog("Saving_lc", filename, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void menuSaveMaskAs() {
    if (image == null || !showMask)
      return;
    if (imageData.getTransparencyType() == SWT.TRANSPARENCY_NONE)
      return;
    animate = false; // stop any animation in progress
    // Get the user to choose a file name and type to save.
    FileDialog fileChooser = new FileDialog(shell, SWT.SAVE);
    fileChooser.setFilterPath(lastPath);
    if (fileName != null)
      fileChooser.setFileName(fileName);
    fileChooser.setFilterExtensions(new String[] { "*.bmp", "*.gif",
        "*.ico", "*.jpg", "*.png" });
    fileChooser.setFilterNames(new String[] { "BMP (*.bmp)", "GIF (*.gif)",
        "ICO (*.ico)", "JPEG (*.jpg)", "PNG (*.png)" });
    String filename = fileChooser.open();
    lastPath = fileChooser.getFilterPath();
    if (filename == null)
      return;
    // Figure out what file type the user wants saved.
    // We need to rely on the file extension because FileDialog
    // does not have API for asking what filter type was selected.
    int filetype = determineFileType(filename);
    if (filetype == SWT.IMAGE_UNDEFINED) {
      MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
      box.setMessage(createMsg("Unknown_extension",
          filename.substring(filename.lastIndexOf('.') + 1)));
      box.open();
      return;
    }
    if (new java.io.File(filename).exists()) {
      MessageBox box = new MessageBox(shell, SWT.ICON_QUESTION | SWT.OK
          | SWT.CANCEL);
      box.setMessage(createMsg("Overwrite", filename));
      if (box.open() == SWT.CANCEL)
        return;
    }
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      // Save the mask of the current image to the specified file.
      ImageData maskImageData = imageData.getTransparencyMask();
      loader.data = new ImageData[] { maskImageData };
      loader.save(filename, filetype);
    } catch (SWTException e) {
      showErrorDialog("Saving_lc", filename, e);
    } catch (SWTError e) {
      showErrorDialog("Saving_lc", filename, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void menuPrint() {
    if (image == null)
      return;
    try {
      // Ask the user to specify the printer.
      PrintDialog dialog = new PrintDialog(shell, SWT.NULL);
      PrinterData printerData = dialog.open();
      if (printerData == null)
        return;
      Printer printer = new Printer(printerData);
      Point screenDPI = display.getDPI();
      Point printerDPI = printer.getDPI();
      int scaleFactor = printerDPI.x / screenDPI.x;
      Rectangle trim = printer.computeTrim(0, 0, 0, 0);
      if (printer.startJob(currentName)) {
        if (printer.startPage()) {
          GC gc = new GC(printer);
          int transparentPixel = imageData.transparentPixel;
          if (transparentPixel != -1 && !transparent) {
            imageData.transparentPixel = -1;
          }
          Image printerImage = new Image(printer, imageData);
          gc.drawImage(printerImage, 0, 0, imageData.width,
              imageData.height, -trim.x, -trim.y, scaleFactor
                  * imageData.width, scaleFactor
                  * imageData.height);
          if (transparentPixel != -1 && !transparent) {
            imageData.transparentPixel = transparentPixel;
          }
          printerImage.dispose();
          gc.dispose();
          printer.endPage();
        }
        printer.endJob();
      }
      printer.dispose();
    } catch (SWTError e) {
      MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
      box.setMessage("Printing_error" + e.getMessage());
      box.open();
    }
  }
  void menuReopen() {
    if (currentName == null)
      return;
    animate = false; // stop any animation in progress
    resetScrollBars();
    resetScaleCombos();
    Cursor waitCursor = new Cursor(display, SWT.CURSOR_WAIT);
    shell.setCursor(waitCursor);
    imageCanvas.setCursor(waitCursor);
    try {
      loader = new ImageLoader();
      long startTime = System.currentTimeMillis();
      ImageData[] newImageData;
      if (fileName == null) {
        URL url = new URL(currentName);
        InputStream stream = url.openStream();
        newImageData = loader.load(stream);
        stream.close();
      } else {
        newImageData = loader.load(fileName);
      }
      loadTime = System.currentTimeMillis() - startTime;
      imageDataIndex = 0;
      displayImage(newImageData[imageDataIndex]);
    } catch (Exception e) {
      showErrorDialog("Reloading", currentName, e);
    } finally {
      shell.setCursor(null);
      imageCanvas.setCursor(crossCursor);
      waitCursor.dispose();
    }
  }
  void changeBackground() {
    String background = backgroundCombo.getText();
    if (background.equals("White")) {
      imageCanvas.setBackground(whiteColor);
    } else if (background.equals("Black")) {
      imageCanvas.setBackground(blackColor);
    } else if (background.equals("Red")) {
      imageCanvas.setBackground(redColor);
    } else if (background.equals("Green")) {
      imageCanvas.setBackground(greenColor);
    } else if (background.equals("Blue")) {
      imageCanvas.setBackground(blueColor);
    } else {
      imageCanvas.setBackground(null);
    }
  }
  /*
   * Called when the ScaleX combo selection changes.
   */
  void scaleX() {
    try {
      xscale = Float.parseFloat(scaleXCombo.getText());
    } catch (NumberFormatException e) {
      xscale = 1;
      scaleXCombo.select(scaleXCombo.indexOf("1"));
    }
    if (image != null) {
      resizeScrollBars();
      imageCanvas.redraw();
    }
  }
  /*
   * Called when the ScaleY combo selection changes.
   */
  void scaleY() {
    try {
      yscale = Float.parseFloat(scaleYCombo.getText());
    } catch (NumberFormatException e) {
      yscale = 1;
      scaleYCombo.select(scaleYCombo.indexOf("1"));
    }
    if (image != null) {
      resizeScrollBars();
      imageCanvas.redraw();
    }
  }
  /*
   * Called when the Alpha combo selection changes.
   */
  void alpha() {
    try {
      alpha = Integer.parseInt(alphaCombo.getText());
    } catch (NumberFormatException e) {
      alphaCombo.select(alphaCombo.indexOf("255"));
      alpha = 255;
    }
  }
  /*
   * Called when the mouse moves in the image canvas. Show the color of the
   * image at the point under the mouse.
   */
  void showColorAt(int mx, int my) {
    int x = mx - imageData.x - ix;
    int y = my - imageData.y - iy;
    showColorForPixel(x, y);
  }
  /*
   * Called when a mouse down or key press is detected in the data text. Show
   * the color of the pixel at the caret position in the data text.
   */
  void showColorForData() {
    int delimiterLength = dataText.getLineDelimiter().length();
    int charactersPerLine = 6 + 3 * imageData.bytesPerLine
        + delimiterLength;
    int position = dataText.getCaretOffset();
    int y = position / charactersPerLine;
    if ((position - y * charactersPerLine) < 6
        || ((y + 1) * charactersPerLine - position) <= delimiterLength) {
      statusLabel.setText("");
      return;
    }
    int dataPosition = position - 6 * (y + 1) - delimiterLength * y;
    int byteNumber = dataPosition / 3;
    int where = dataPosition - byteNumber * 3;
    int xByte = byteNumber % imageData.bytesPerLine;
    int x = -1;
    int depth = imageData.depth;
    if (depth == 1) { // 8 pixels per byte (can only show 3 of 8)
      if (where == 0)
        x = xByte * 8;
      if (where == 1)
        x = xByte * 8 + 3;
      if (where == 2)
        x = xByte * 8 + 7;
    }
    if (depth == 2) { // 4 pixels per byte (can only show 3 of 4)
      if (where == 0)
        x = xByte * 4;
      if (where == 1)
        x = xByte * 4 + 1;
      if (where == 2)
        x = xByte * 4 + 3;
    }
    if (depth == 4) { // 2 pixels per byte
      if (where == 0)
        x = xByte * 2;
      if (where == 1)
        x = xByte * 2;
      if (where == 2)
        x = xByte * 2 + 1;
    }
    if (depth == 8) { // 1 byte per pixel
      x = xByte;
    }
    if (depth == 16) { // 2 bytes per pixel
      x = xByte / 2;
    }
    if (depth == 24) { // 3 bytes per pixel
      x = xByte / 3;
    }
    if (depth == 32) { // 4 bytes per pixel
      x = xByte / 4;
    }
    if (x != -1) {
      showColorForPixel(x, y);
    } else {
      statusLabel.setText("");
    }
  }
  /*
   * Set the status label to show color information for the specified pixel in
   * the image.
   */
  void showColorForPixel(int x, int y) {
    if (x >= 0 && x < imageData.width && y >= 0 && y < imageData.height) {
      int pixel = imageData.getPixel(x, y);
      RGB rgb = imageData.palette.getRGB(pixel);
      Object[] args = { new Integer(x), new Integer(y),
          new Integer(pixel), Integer.toHexString(pixel), rgb };
      if (pixel == imageData.transparentPixel) {
        statusLabel.setText(createMsg("Color_at_trans", args));
      } else {
        statusLabel.setText(createMsg("Color_at",
            args));
      }
    } else {
      statusLabel.setText("");
    }
  }
  /*
   * Called when the Animate button is pressed.
   */
  void animate() {
    animate = !animate;
    if (animate && image != null && imageDataArray.length > 1) {
      animateThread = new Thread("Animation") {
        public void run() {
          // Pre-animation widget setup.
          preAnimation();
          // Animate.
          try {
            animateLoop();
          } catch (final SWTException e) {
            display.syncExec(new Runnable() {
              public void run() {
                showErrorDialog(createMsg("Creating_image",
                    new Integer(imageDataIndex + 1)),
                    currentName, e);
              }
            });
          }
          // Post animation widget reset.
          postAnimation();
        }
      };
      animateThread.setDaemon(true);
      animateThread.start();
    }
  }
  /*
   * Loop through all of the images in a multi-image file and display them one
   * after another.
   */
  void animateLoop() {
    // Create an off-screen image to draw on, and a GC to draw with.
    // Both are disposed after the animation.
    Image offScreenImage = new Image(display, loader.logicalScreenWidth,
        loader.logicalScreenHeight);
    GC offScreenImageGC = new GC(offScreenImage);
    try {
      // Use syncExec to get the background color of the imageCanvas.
      display.syncExec(new Runnable() {
        public void run() {
          canvasBackground = imageCanvas.getBackground();
        }
      });
      // Fill the off-screen image with the background color of the
      // canvas.
      offScreenImageGC.setBackground(canvasBackground);
      offScreenImageGC.fillRectangle(0, 0, loader.logicalScreenWidth,
          loader.logicalScreenHeight);
      // Draw the current image onto the off-screen image.
      offScreenImageGC.drawImage(image, 0, 0, imageData.width,
          imageData.height, imageData.x, imageData.y,
          imageData.width, imageData.height);
      int repeatCount = loader.repeatCount;
      while (animate && (loader.repeatCount == 0 || repeatCount > 0)) {
        if (imageData.disposalMethod == SWT.DM_FILL_BACKGROUND) {
          // Fill with the background color before drawing.
          Color bgColor = null;
          int backgroundPixel = loader.backgroundPixel;
          if (showBackground && backgroundPixel != -1) {
            // Fill with the background color.
            RGB backgroundRGB = imageData.palette
                .getRGB(backgroundPixel);
            bgColor = new Color(null, backgroundRGB);
          }
          try {
            offScreenImageGC
                .setBackground(bgColor != null ? bgColor
                    : canvasBackground);
            offScreenImageGC.fillRectangle(imageData.x,
                imageData.y, imageData.width, imageData.height);
          } finally {
            if (bgColor != null)
              bgColor.dispose();
          }
        } else if (imageData.disposalMethod == SWT.DM_FILL_PREVIOUS) {
          // Restore the previous image before drawing.
          offScreenImageGC.drawImage(image, 0, 0, imageData.width,
              imageData.height, imageData.x, imageData.y,
              imageData.width, imageData.height);
        }
        // Get the next image data.
        imageDataIndex = (imageDataIndex + 1) % imageDataArray.length;
        imageData = imageDataArray[imageDataIndex];
        image.dispose();
        image = new Image(display, imageData);
        // Draw the new image data.
        offScreenImageGC.drawImage(image, 0, 0, imageData.width,
            imageData.height, imageData.x, imageData.y,
            imageData.width, imageData.height);
        // Draw the off-screen image to the screen.
        imageCanvasGC.drawImage(offScreenImage, 0, 0);
        // Sleep for the specified delay time before drawing again.
        try {
          Thread.sleep(visibleDelay(imageData.delayTime * 10));
        } catch (InterruptedException e) {
        }
        // If we have just drawn the last image in the set,
        // then decrement the repeat count.
        if (imageDataIndex == imageDataArray.length - 1)
          repeatCount--;
      }
    } finally {
      offScreenImage.dispose();
      offScreenImageGC.dispose();
    }
  }
  /*
   * Pre animation setup.
   */
  void preAnimation() {
    display.syncExec(new Runnable() {
      public void run() {
        // Change the label of the Animate button to 'Stop'.
        animateButton.setText("Stop");
        // Disable anything we don't want the user
        // to select during the animation.
        previousButton.setEnabled(false);
        nextButton.setEnabled(false);
        backgroundCombo.setEnabled(false);
        scaleXCombo.setEnabled(false);
        scaleYCombo.setEnabled(false);
        alphaCombo.setEnabled(false);
        incrementalCheck.setEnabled(false);
        transparentCheck.setEnabled(false);
        maskCheck.setEnabled(false);
        // leave backgroundCheck enabled
        // Reset the scale combos and scrollbars.
        resetScaleCombos();
        resetScrollBars();
      }
    });
  }
  /*
   * Post animation reset.
   */
  void postAnimation() {
    display.syncExec(new Runnable() {
      public void run() {
        // Enable anything we disabled before the animation.
        previousButton.setEnabled(true);
        nextButton.setEnabled(true);
        backgroundCombo.setEnabled(true);
        scaleXCombo.setEnabled(true);
        scaleYCombo.setEnabled(true);
        alphaCombo.setEnabled(true);
        incrementalCheck.setEnabled(true);
        transparentCheck.setEnabled(true);
        maskCheck.setEnabled(true);
        // Reset the label of the Animate button.
        animateButton.setText("Animate");
        if (animate) {
          // If animate is still true, we finished the
          // full number of repeats. Leave the image as-is.
          animate = false;
        } else {
          // Redisplay the current image and its palette.
          displayImage(imageDataArray[imageDataIndex]);
        }
      }
    });
  }
  /*
   * Called when the Previous button is pressed. Display the previous image in
   * a multi-image file.
   */
  void previous() {
    if (image != null && imageDataArray.length > 1) {
      if (imageDataIndex == 0) {
        imageDataIndex = imageDataArray.length;
      }
      imageDataIndex = imageDataIndex - 1;
      displayImage(imageDataArray[imageDataIndex]);
    }
  }
  /*
   * Called when the Next button is pressed. Display the next image in a
   * multi-image file.
   */
  void next() {
    if (image != null && imageDataArray.length > 1) {
      imageDataIndex = (imageDataIndex + 1) % imageDataArray.length;
      displayImage(imageDataArray[imageDataIndex]);
    }
  }
  void displayImage(ImageData newImageData) {
    if (incremental && incrementalThread != null) {
      // Tell the incremental thread to stop drawing.
      synchronized (this) {
        incrementalEvents = null;
      }
      // Wait until the incremental thread is done.
      while (incrementalThread.isAlive()) {
        if (!display.readAndDispatch())
          display.sleep();
      }
    }
    // Dispose of the old image, if there was one.
    if (image != null)
      image.dispose();
    try {
      // Cache the new image and imageData.
      image = new Image(display, newImageData);
      imageData = newImageData;
    } catch (SWTException e) {
      showErrorDialog("Creating_from" + " ",
          currentName, e);
      image = null;
      return;
    }
    // Update the widgets with the new image info.
    String string = createMsg("Analyzer_on", currentName);
    shell.setText(string);
    if (imageDataArray.length > 1) {
      string = createMsg("Type_index", new Object[] {
          fileTypeString(imageData.type),
          new Integer(imageDataIndex + 1),
          new Integer(imageDataArray.length) });
    } else {
      string = createMsg("Type_string",
          fileTypeString(imageData.type));
    }
    typeLabel.setText(string);
    string = createMsg("Size_value", new Object[] {
        new Integer(imageData.width), new Integer(imageData.height) });
    sizeLabel.setText(string);
    string = createMsg("Depth_value", new Integer(
        imageData.depth));
    depthLabel.setText(string);
    string = createMsg("Transparent_pixel_value",
        pixelInfo(imageData.transparentPixel));
    transparentPixelLabel.setText(string);
    string = createMsg("Time_to_load_value", new Long(
        loadTime));
    timeToLoadLabel.setText(string);
    string = createMsg("Animation_size_value",
        new Object[] { new Integer(loader.logicalScreenWidth),
            new Integer(loader.logicalScreenHeight) });
    screenSizeLabel.setText(string);
    string = createMsg("Background_pixel_value",
        pixelInfo(loader.backgroundPixel));
    backgroundPixelLabel.setText(string);
    string = createMsg("Image_location_value",
        new Object[] { new Integer(imageData.x),
            new Integer(imageData.y) });
    locationLabel.setText(string);
    string = createMsg("Disposal_value", new Object[] {
        new Integer(imageData.disposalMethod),
        disposalString(imageData.disposalMethod) });
    disposalMethodLabel.setText(string);
    int delay = imageData.delayTime * 10;
    int delayUsed = visibleDelay(delay);
    if (delay != delayUsed) {
      string = createMsg("Delay_value", new Object[] {
          new Integer(delay), new Integer(delayUsed) });
    } else {
      string = createMsg("Delay_used", new Integer(
          delay));
    }
    delayTimeLabel.setText(string);
    if (loader.repeatCount == 0) {
      string = createMsg("Repeats_forever",
          new Integer(loader.repeatCount));
    } else {
      string = createMsg("Repeats_value", new Integer(
          loader.repeatCount));
    }
    repeatCountLabel.setText(string);
    if (imageData.palette.isDirect) {
      string = "Palette_direct";
    } else {
      string = createMsg("Palette_value", new Integer(
          imageData.palette.getRGBs().length));
    }
    paletteLabel.setText(string);
    string = createMsg("Pixel_data_value",
        new Object[] { new Integer(imageData.bytesPerLine),
            new Integer(imageData.scanlinePad),
            depthInfo(imageData.depth) });
    dataLabel.setText(string);
    String data = dataHexDump(dataText.getLineDelimiter());
    dataText.setText(data);
    // bold the first column all the way down
    int index = 0;
    while ((index = data.indexOf(':', index + 1)) != -1)
      dataText.setStyleRange(new StyleRange(index - INDEX_DIGITS,
          INDEX_DIGITS, dataText.getForeground(), dataText
              .getBackground(), SWT.BOLD));
    statusLabel.setText("");
    // Redraw both canvases.
    paletteCanvas.redraw();
    imageCanvas.redraw();
  }
  void paintImage(PaintEvent event) {
    Image paintImage = image;
    int transparentPixel = imageData.transparentPixel;
    if (transparentPixel != -1 && !transparent) {
      imageData.transparentPixel = -1;
      paintImage = new Image(display, imageData);
    }
    int w = Math.round(imageData.width * xscale);
    int h = Math.round(imageData.height * yscale);
    event.gc.drawImage(paintImage, 0, 0, imageData.width, imageData.height,
        ix + imageData.x, iy + imageData.y, w, h);
    if (showMask
        && (imageData.getTransparencyType() != SWT.TRANSPARENCY_NONE)) {
      ImageData maskImageData = imageData.getTransparencyMask();
      Image maskImage = new Image(display, maskImageData);
      event.gc.drawImage(maskImage, 0, 0, imageData.width,
          imageData.height, w + 10 + ix + imageData.x, iy
              + imageData.y, w, h);
      maskImage.dispose();
    }
    if (transparentPixel != -1 && !transparent) {
      imageData.transparentPixel = transparentPixel;
      paintImage.dispose();
    }
  }
  void paintPalette(PaintEvent event) {
    GC gc = event.gc;
    gc.fillRectangle(paletteCanvas.getClientArea());
    if (imageData.palette.isDirect) {
      // For a direct palette, display the masks.
      int y = py + 10;
      int xTab = 50;
      gc.drawString("rMsk", 10, y, true);
      gc.drawString(toHex4ByteString(imageData.palette.redMask), xTab, y,
          true);
      gc.drawString("gMsk", 10, y += 12, true);
      gc.drawString(toHex4ByteString(imageData.palette.greenMask), xTab,
          y, true);
      gc.drawString("bMsk", 10, y += 12, true);
      gc.drawString(toHex4ByteString(imageData.palette.blueMask), xTab,
          y, true);
      gc.drawString("rShf", 10, y += 12, true);
      gc.drawString(Integer.toString(imageData.palette.redShift), xTab,
          y, true);
      gc.drawString("gShf", 10, y += 12, true);
      gc.drawString(Integer.toString(imageData.palette.greenShift), xTab,
          y, true);
      gc.drawString("bShf", 10, y += 12, true);
      gc.drawString(Integer.toString(imageData.palette.blueShift), xTab,
          y, true);
    } else {
      // For an indexed palette, display the palette colors and indices.
      RGB[] rgbs = imageData.palette.getRGBs();
      if (rgbs != null) {
        int xTab1 = 40, xTab2 = 100;
        for (int i = 0; i < rgbs.length; i++) {
          int y = (i + 1) * 10 + py;
          gc.drawString(String.valueOf(i), 10, y, true);
          gc.drawString(toHexByteString(rgbs[i].red)
              + toHexByteString(rgbs[i].green)
              + toHexByteString(rgbs[i].blue), xTab1, y, true);
          Color color = new Color(display, rgbs[i]);
          gc.setBackground(color);
          gc.fillRectangle(xTab2, y + 2, 10, 10);
          color.dispose();
        }
      }
    }
  }
  void resizeShell(ControlEvent event) {
    if (image == null || shell.isDisposed())
      return;
    resizeScrollBars();
  }
  // Reset the scale combos to 1.
  void resetScaleCombos() {
    xscale = 1;
    yscale = 1;
    scaleXCombo.select(scaleXCombo.indexOf("1"));
    scaleYCombo.select(scaleYCombo.indexOf("1"));
  }
  // Reset the scroll bars to 0.
  void resetScrollBars() {
    if (image == null)
      return;
    ix = 0;
    iy = 0;
    py = 0;
    resizeScrollBars();
    imageCanvas.getHorizontalBar().setSelection(0);
    imageCanvas.getVerticalBar().setSelection(0);
    paletteCanvas.getVerticalBar().setSelection(0);
  }
  void resizeScrollBars() {
    // Set the max and thumb for the image canvas scroll bars.
    ScrollBar horizontal = imageCanvas.getHorizontalBar();
    ScrollBar vertical = imageCanvas.getVerticalBar();
    Rectangle canvasBounds = imageCanvas.getClientArea();
    int width = Math.round(imageData.width * xscale);
    if (width > canvasBounds.width) {
      // The image is wider than the canvas.
      horizontal.setEnabled(true);
      horizontal.setMaximum(width);
      horizontal.setThumb(canvasBounds.width);
      horizontal.setPageIncrement(canvasBounds.width);
    } else {
      // The canvas is wider than the image.
      horizontal.setEnabled(false);
      if (ix != 0) {
        // Make sure the image is completely visible.
        ix = 0;
        imageCanvas.redraw();
      }
    }
    int height = Math.round(imageData.height * yscale);
    if (height > canvasBounds.height) {
      // The image is taller than the canvas.
      vertical.setEnabled(true);
      vertical.setMaximum(height);
      vertical.setThumb(canvasBounds.height);
      vertical.setPageIncrement(canvasBounds.height);
    } else {
      // The canvas is taller than the image.
      vertical.setEnabled(false);
      if (iy != 0) {
        // Make sure the image is completely visible.
        iy = 0;
        imageCanvas.redraw();
      }
    }
    // Set the max and thumb for the palette canvas scroll bar.
    vertical = paletteCanvas.getVerticalBar();
    if (imageData.palette.isDirect) {
      vertical.setEnabled(false);
    } else { // indexed palette
      canvasBounds = paletteCanvas.getClientArea();
      int paletteHeight = imageData.palette.getRGBs().length * 10 + 20; // 10
                                        // pixels
                                        // each
                                        // index
                                        // + 20
                                        // for
                                        // margins.
      vertical.setEnabled(true);
      vertical.setMaximum(paletteHeight);
      vertical.setThumb(canvasBounds.height);
      vertical.setPageIncrement(canvasBounds.height);
    }
  }
  /*
   * Called when the image canvas' horizontal scrollbar is selected.
   */
  void scrollHorizontally(ScrollBar scrollBar) {
    if (image == null)
      return;
    Rectangle canvasBounds = imageCanvas.getClientArea();
    int width = Math.round(imageData.width * xscale);
    int height = Math.round(imageData.height * yscale);
    if (width > canvasBounds.width) {
      // Only scroll if the image is bigger than the canvas.
      int x = -scrollBar.getSelection();
      if (x + width < canvasBounds.width) {
        // Don't scroll past the end of the image.
        x = canvasBounds.width - width;
      }
      imageCanvas.scroll(x, iy, ix, iy, width, height, false);
      ix = x;
    }
  }
  /*
   * Called when the image canvas' vertical scrollbar is selected.
   */
  void scrollVertically(ScrollBar scrollBar) {
    if (image == null)
      return;
    Rectangle canvasBounds = imageCanvas.getClientArea();
    int width = Math.round(imageData.width * xscale);
    int height = Math.round(imageData.height * yscale);
    if (height > canvasBounds.height) {
      // Only scroll if the image is bigger than the canvas.
      int y = -scrollBar.getSelection();
      if (y + height < canvasBounds.height) {
        // Don't scroll past the end of the image.
        y = canvasBounds.height - height;
      }
      imageCanvas.scroll(ix, y, ix, iy, width, height, false);
      iy = y;
    }
  }
  /*
   * Called when the palette canvas' vertical scrollbar is selected.
   */
  void scrollPalette(ScrollBar scrollBar) {
    if (image == null)
      return;
    Rectangle canvasBounds = paletteCanvas.getClientArea();
    int paletteHeight = imageData.palette.getRGBs().length * 10 + 20;
    if (paletteHeight > canvasBounds.height) {
      // Only scroll if the palette is bigger than the canvas.
      int y = -scrollBar.getSelection();
      if (y + paletteHeight < canvasBounds.height) {
        // Don't scroll past the end of the palette.
        y = canvasBounds.height - paletteHeight;
      }
      paletteCanvas.scroll(0, y, 0, py, paletteWidth, paletteHeight,
          false);
      py = y;
    }
  }
  /*
   * Return a String containing a line-by-line dump of the data in the current
   * imageData. The lineDelimiter parameter must be a string of length 1 or 2.
   */
  String dataHexDump(String lineDelimiter) {
    if (image == null)
      return "";
    char[] dump = new char[imageData.height
        * (6 + 3 * imageData.bytesPerLine + lineDelimiter.length())];
    int index = 0;
    for (int i = 0; i < imageData.data.length; i++) {
      if (i % imageData.bytesPerLine == 0) {
        int line = i / imageData.bytesPerLine;
        dump[index++] = Character.forDigit(line / 1000 % 10, 10);
        dump[index++] = Character.forDigit(line / 100 % 10, 10);
        dump[index++] = Character.forDigit(line / 10 % 10, 10);
        dump[index++] = Character.forDigit(line % 10, 10);
        dump[index++] = ':';
        dump[index++] = ' ';
      }
      byte b = imageData.data[i];
      dump[index++] = Character.forDigit((b & 0xF0) >> 4, 16);
      dump[index++] = Character.forDigit(b & 0x0F, 16);
      dump[index++] = ' ';
      if ((i + 1) % imageData.bytesPerLine == 0) {
        dump[index++] = lineDelimiter.charAt(0);
        if (lineDelimiter.length() > 1)
          dump[index++] = lineDelimiter.charAt(1);
      }
    }
    String result = "";
    try {
      result = new String(dump);
    } catch (OutOfMemoryError e) {
      /* Too much data to display in the text widget - truncate at 4M. */
      result = new String(dump, 0, 4 * 1024 * 1024)
          + "\n ...data dump truncated at 4M...";
    }
    return result;
  }
  /*
   * Open an error dialog displaying the specified information.
   */
  void showErrorDialog(String operation, String filename, Throwable e) {
    MessageBox box = new MessageBox(shell, SWT.ICON_ERROR);
    String message = createMsg("Error", new String[] {
        operation, filename });
    String errorMessage = "";
    if (e != null) {
      if (e instanceof SWTException) {
        SWTException swte = (SWTException) e;
        errorMessage = swte.getMessage();
        if (swte.throwable != null) {
          errorMessage += ":\n" + swte.throwable.toString();
        }
      } else if (e instanceof SWTError) {
        SWTError swte = (SWTError) e;
        errorMessage = swte.getMessage();
        if (swte.throwable != null) {
          errorMessage += ":\n" + swte.throwable.toString();
        }
      } else {
        errorMessage = e.toString();
      }
    }
    box.setMessage(message + errorMessage);
    box.open();
  }
  /*
   * Open a dialog asking the user for more information on the type of BMP
   * file to save.
   */
  int showBMPDialog() {
    final int[] bmpType = new int[1];
    bmpType[0] = SWT.IMAGE_BMP;
    SelectionListener radioSelected = new SelectionAdapter() {
      public void widgetSelected(SelectionEvent event) {
        Button radio = (Button) event.widget;
        if (radio.getSelection())
          bmpType[0] = ((Integer) radio.getData()).intValue();
      }
    };
    // need to externalize strings
    final Shell dialog = new Shell(shell, SWT.DIALOG_TRIM);
    dialog.setText("Save_as");
    dialog.setLayout(new GridLayout());
    Label label = new Label(dialog, SWT.NONE);
    label.setText("Save_as");
    Button radio = new Button(dialog, SWT.RADIO);
    radio.setText("Save_as_type_no_compress");
    radio.setSelection(true);
    radio.setData(new Integer(SWT.IMAGE_BMP));
    radio.addSelectionListener(radioSelected);
    radio = new Button(dialog, SWT.RADIO);
    radio.setText("Save_as_type_rle_compress");
    radio.setData(new Integer(SWT.IMAGE_BMP_RLE));
    radio.addSelectionListener(radioSelected);
    radio = new Button(dialog, SWT.RADIO);
    radio.setText("Save_as_type_os2");
    radio.setData(new Integer(SWT.IMAGE_OS2_BMP));
    radio.addSelectionListener(radioSelected);
    label = new Label(dialog, SWT.SEPARATOR | SWT.HORIZONTAL);
    label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
    Button ok = new Button(dialog, SWT.PUSH);
    ok.setText("OK");
    GridData data = new GridData();
    data.horizontalAlignment = SWT.CENTER;
    data.widthHint = 75;
    ok.setLayoutData(data);
    ok.addSelectionListener(new SelectionAdapter() {
      public void widgetSelected(SelectionEvent e) {
        dialog.close();
      }
    });
    dialog.pack();
    dialog.open();
    while (!dialog.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
    return bmpType[0];
  }
  /*
   * Return a String describing how to analyze the bytes in the hex dump.
   */
  static String depthInfo(int depth) {
    Object[] args = { new Integer(depth), "" };
    switch (depth) {
    case 1:
      args[1] = createMsg("Multi_pixels", new Object[] {
          new Integer(8), " [01234567]" });
      break;
    case 2:
      args[1] = createMsg("Multi_pixels", new Object[] {
          new Integer(4), "[00112233]" });
      break;
    case 4:
      args[1] = createMsg("Multi_pixels", new Object[] {
          new Integer(2), "[00001111]" });
      break;
    case 8:
      args[1] = "One_byte";
      break;
    case 16:
      args[1] = createMsg("Multi_bytes", new Integer(2));
      break;
    case 24:
      args[1] = createMsg("Multi_bytes", new Integer(3));
      break;
    case 32:
      args[1] = createMsg("Multi_bytes", new Integer(4));
      break;
    default:
      args[1] = "Unsupported_lc";
    }
    return createMsg("Depth_info", args);
  }
  /*
   * Return the specified number of milliseconds. If the specified number of
   * milliseconds is too small to see a visual change, then return a higher
   * number.
   */
  static int visibleDelay(int ms) {
    if (ms < 20)
      return ms + 30;
    if (ms < 30)
      return ms + 10;
    return ms;
  }
  /*
   * Return the specified byte value as a hex string, preserving leading 0's.
   */
  static String toHexByteString(int i) {
    if (i <= 0x0f)
      return "0" + Integer.toHexString(i);
    return Integer.toHexString(i & 0xff);
  }
  /*
   * Return the specified 4-byte value as a hex string, preserving leading
   * 0's. (a bit 'brute force'... should probably use a loop...)
   */
  static String toHex4ByteString(int i) {
    String hex = Integer.toHexString(i);
    if (hex.length() == 1)
      return "0000000" + hex;
    if (hex.length() == 2)
      return "000000" + hex;
    if (hex.length() == 3)
      return "00000" + hex;
    if (hex.length() == 4)
      return "0000" + hex;
    if (hex.length() == 5)
      return "000" + hex;
    if (hex.length() == 6)
      return "00" + hex;
    if (hex.length() == 7)
      return "0" + hex;
    return hex;
  }
  /*
   * Return a String describing the specified transparent or background pixel.
   */
  static String pixelInfo(int pixel) {
    if (pixel == -1)
      return pixel + " (" + "None_lc" + ")";
    else
      return pixel + " (0x" + Integer.toHexString(pixel) + ")";
  }
  /*
   * Return a String describing the specified disposal method.
   */
  static String disposalString(int disposalMethod) {
    switch (disposalMethod) {
    case SWT.DM_FILL_NONE:
      return "None_lc";
    case SWT.DM_FILL_BACKGROUND:
      return "Background_lc";
    case SWT.DM_FILL_PREVIOUS:
      return "Previous_lc";
    }
    return "Unspecified_lc";
  }
  /*
   * Return a String describing the specified image file type.
   */
  String fileTypeString(int filetype) {
    if (filetype == SWT.IMAGE_BMP)
      return "BMP";
    if (filetype == SWT.IMAGE_BMP_RLE)
      return "RLE" + imageData.depth + " BMP";
    if (filetype == SWT.IMAGE_OS2_BMP)
      return "OS/2 BMP";
    if (filetype == SWT.IMAGE_GIF)
      return "GIF";
    if (filetype == SWT.IMAGE_ICO)
      return "ICO";
    if (filetype == SWT.IMAGE_JPEG)
      return "JPEG";
    if (filetype == SWT.IMAGE_PNG)
      return "PNG";
    return "Unknown_ac";
  }
  /*
   * Return the specified file's image type, based on its extension. Note that
   * this is not a very robust way to determine image type, and it is only to
   * be used in the absence of any better method.
   */
  int determineFileType(String filename) {
    String ext = filename.substring(filename.lastIndexOf('.') + 1);
    if (ext.equalsIgnoreCase("bmp")) {
      return showBMPDialog();
    }
    if (ext.equalsIgnoreCase("gif"))
      return SWT.IMAGE_GIF;
    if (ext.equalsIgnoreCase("ico"))
      return SWT.IMAGE_ICO;
    if (ext.equalsIgnoreCase("jpg") || ext.equalsIgnoreCase("jpeg"))
      return SWT.IMAGE_JPEG;
    if (ext.equalsIgnoreCase("png"))
      return SWT.IMAGE_PNG;
    return SWT.IMAGE_UNDEFINED;
  }
  static String createMsg(String msg, Object[] args) {
    MessageFormat formatter = new MessageFormat(msg);
    return formatter.format(args);
  }
  static String createMsg(String msg, Object arg) {
    MessageFormat formatter = new MessageFormat(msg);
    return formatter.format(new Object[] { arg });
  }
}