org.openmicroscopy.shoola.env.ui.ActivityComponent.java Source code

Java tutorial

Introduction

Here is the source code for org.openmicroscopy.shoola.env.ui.ActivityComponent.java

Source

/*
 * org.openmicroscopy.shoola.env.ui.ActivityComponent
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006-2013 University of Dundee. All rights reserved.
 *
 *
 *    This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */
package org.openmicroscopy.shoola.env.ui;

//Java imports
import info.clearthought.layout.TableLayout;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JToolBar;

import omero.model.OriginalFile;

import org.apache.commons.lang.StringUtils;
import org.jdesktop.swingx.JXBusyLabel;
import org.openmicroscopy.shoola.env.Environment;
import org.openmicroscopy.shoola.env.LookupNames;
import org.openmicroscopy.shoola.env.config.Registry;
import org.openmicroscopy.shoola.env.data.ProcessException;
import org.openmicroscopy.shoola.env.data.model.ApplicationData;
import org.openmicroscopy.shoola.env.data.model.DownloadActivityParam;
import org.openmicroscopy.shoola.env.data.model.DownloadAndLaunchActivityParam;
import org.openmicroscopy.shoola.env.data.util.SecurityContext;
import org.openmicroscopy.shoola.env.event.EventBus;
import org.openmicroscopy.shoola.util.filter.file.CSVFilter;
import org.openmicroscopy.shoola.util.ui.UIUtilities;
import org.openmicroscopy.shoola.util.ui.filechooser.FileChooser;
import pojos.FileAnnotationData;

/**
 * Top class that each action should extend.
 *
 * @author Jean-Marie Burel     
 *         <a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author Donald MacDonald &nbsp;&nbsp;&nbsp;&nbsp;
 *         <a href="mailto:donald@lifesci.dundee.ac.uk">donald@lifesci.dundee.ac.uk</a>
 * @version 3.0
 * @since 3.0-Beta4
 */
public abstract class ActivityComponent extends JPanel implements ActionListener {

    /** Bound property indicating to remove the entry from the display. */
    static final String REMOVE_ACTIVITY_PROPERTY = "removeActivity";

    /** Bound property indicating to unregister the activity. */
    static final String UNREGISTER_ACTIVITY_PROPERTY = "unregisterActivity";

    /** The default dimension of the status. */
    private static final Dimension SIZE = new Dimension(22, 22);

    /** ID to remove the entry from the display. */
    private static final int REMOVE = 0;

    /** ID to cancel the activity. */
    private static final int CANCEL = 1;

    /** ID to display the standard error. */
    private static final int EXCEPTION = 2;

    /** ID to display all the output. */
    private static final int ALL_RESULT = 3;

    /** ID to display the error. */
    private static final int ERROR = 4;

    /** ID to display the info. */
    private static final int INFO = 5;

    /** The key to look for to display the output message. */
    private static final String MESSAGE = "Message";

    /** The key to look for to display the error message if any. */
    static final String STD_ERR = "stderr";

    /** The key to look for to display the output message if any. */
    static final String STD_OUT = "stdout";

    /** Indicate the status of the activity. */
    private JXBusyLabel status;

    /** Button to remove the activity from the display. */
    private JButton removeButton;

    /** Button to cancel the activity. */
    private JButton cancelButton;

    /** The label hosting the icon. */
    protected JLabel iconLabel;

    /** The component displaying the status. */
    private JComponent statusPane;

    /** The index of the {@link #cancelButton} or {@link #removeButton}. */
    private int buttonIndex;

    /** The tool bar displaying controls. *. */
    private JToolBar toolBar;

    /** Button to shows the exception. */
    private JButton exceptionButton;

    /** Menu displaying the option to view the standard error. */
    private ActivityResultPopupMenu errorMenu;

    /** Menu displaying the option to view the standard output. */
    private ActivityResultPopupMenu infoMenu;

    /** The exception thrown while running the script. */
    private Throwable exception;

    /** The label displaying the type of activity. */
    protected JLabel type;

    /** The label displaying message if any. */
    protected JLabel messageLabel;

    /** Convenience reference for subclasses. */
    protected final Registry registry;

    /** Convenience reference for subclasses. */
    protected final SecurityContext ctx;

    /** Convenience reference for subclasses. */
    protected final UserNotifier viewer;

    /** The result of the activity. */
    protected Object result;

    /** Loader associated to the activity. */
    protected UserNotifierLoader loader;

    /** The object hosting the error.*/
    protected Object errorObject;

    /** The object hosting the info.*/
    protected Object infoObject;

    /** The component displaying the results.*/
    private JComponent resultPane;

    /** Button to show the general result. */
    protected List<ActivityResultRow> resultButtons;

    private PropertyChangeListener listener;

    /** The index where the output goes.*/
    private String paneIndex;

    /**
    * Opens the passed object. Downloads it first.
    * 
    * @param object The object to open.
    * @param parameters Either Analysis parameters or Application data.
    * @param source The source triggering the operation.
    */
    private void open(Object object, Object parameters, JComponent source) {
        if (!(object instanceof FileAnnotationData || object instanceof OriginalFile))
            return;
        Environment env = (Environment) registry.lookup(LookupNames.ENV);
        int index = -1;
        long id = -1;
        String name = "";
        OriginalFile of = null;
        if (object instanceof FileAnnotationData) {
            FileAnnotationData data = (FileAnnotationData) object;
            if (data.isLoaded()) {
                of = (OriginalFile) data.getContent();
                name = data.getFileName();
            } else {
                id = data.getId();
                index = DownloadActivityParam.FILE_ANNOTATION;
                name = "Annotation_" + id;
            }
        } else {
            of = (OriginalFile) object;
            id = of.getId().getValue();
            if (!of.isLoaded()) {
                index = DownloadActivityParam.ORIGINAL_FILE;
                name = "File_" + id;
            } else {
                if (of.getName() != null)
                    name = of.getName().getValue();
                else
                    name = "File_" + id;
            }
        }
        String path = env.getOmeroFilesHome();
        File f;
        if (index != -1) {
            path += File.separator + name;
            f = new File(path);
            //Delete the file if it already exists
            if (f.exists()) {
                f.delete();
                f = new File(path);
            }
            f.deleteOnExit();
        } else {
            String v = path + File.separator + name;
            File ff = new File(v);
            if (ff.exists())
                ff.delete();
            f = new File(path);
        }

        DownloadAndLaunchActivityParam activity;

        if (index != -1)
            activity = new DownloadAndLaunchActivityParam(id, index, f, null);
        else
            activity = new DownloadAndLaunchActivityParam(of, f, null);

        if (parameters instanceof ApplicationData) {
            activity.setApplicationData((ApplicationData) parameters);
        }
        activity.setSource(source);
        viewer.notifyActivity(ctx, activity);
    }

    /** 
     * Initializes the components. 
     * 
     * @param text The type of activity.
     * @param icon The icon to display when done.
     */
    private void initComponents(String text, Icon icon) {
        exceptionButton = createButton("Failure", EXCEPTION, this);
        exceptionButton.setVisible(false);
        //removeButton = createButton("Remove", REMOVE, this);
        IconManager icons = IconManager.getInstance(registry);
        removeButton = new JButton(icons.getIcon(IconManager.REMOVE));
        UIUtilities.unifiedButtonLookAndFeel(removeButton);
        removeButton.setActionCommand("" + REMOVE);
        removeButton.addActionListener(this);
        cancelButton = createButton("Cancel", CANCEL, this);
        //if (index == ADVANCED)
        resultButtons = new ArrayList<ActivityResultRow>();
        status = new JXBusyLabel(SIZE);
        type = UIUtilities.setTextFont(text);
        iconLabel = new JLabel();
        messageLabel = UIUtilities.setTextFont("", iconLabel.getFont().getStyle(), 10);
        iconLabel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 5));
        if (icon != null)
            iconLabel.setIcon(icon);
        statusPane = status;
        resultPane = new JPanel();
        listener = new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                //do something

            }
        };
    }

    /** Builds and lays out the UI. */
    private void buildGUI() {
        JPanel barPane = new JPanel();
        barPane.setOpaque(false);
        barPane.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        double[][] size = { { TableLayout.FILL }, { TableLayout.PREFERRED, TableLayout.PREFERRED } };
        barPane.setLayout(new TableLayout(size));
        barPane.add(type, "0, 0, LEFT, CENTER");
        barPane.add(messageLabel, "0, 1, CENTER, CENTER");

        //icon, message, content, toolbar
        double[][] tl = { { TableLayout.PREFERRED, TableLayout.FILL, TableLayout.PREFERRED, TableLayout.PREFERRED },
                { TableLayout.PREFERRED } };
        setLayout(new TableLayout(tl));
        add(statusPane, "0, 0");
        JPanel p = UIUtilities.buildComponentPanel(barPane);
        p.setOpaque(false);
        p.setBackground(barPane.getBackground());
        add(p, "1, 0");
        paneIndex = "2, 0";
        add(resultPane, paneIndex);
        add(createToolBar(), "3, 0");
    }

    /**
     * Returns the tool bar.
     * 
     * @return See above.
     */
    private JComponent createToolBar() {
        toolBar = new JToolBar();
        toolBar.setOpaque(false);
        toolBar.setFloatable(false);
        toolBar.setBorder(null);
        buttonIndex = 0;
        toolBar.add(exceptionButton);
        toolBar.add(Box.createHorizontalStrut(5));
        buttonIndex = 2;
        toolBar.add(cancelButton);
        JLabel l = new JLabel();
        Font f = l.getFont();
        l.setForeground(UIUtilities.LIGHT_GREY.darker());
        l.setFont(f.deriveFont(f.getStyle(), f.getSize() - 2));
        String s = UIUtilities.formatShortDateTime(null);
        String[] values = s.split(" ");
        if (values.length > 1) {
            String v = values[1];
            if (values.length > 2)
                v += " " + values[2];
            l.setText(v);
            toolBar.add(Box.createHorizontalStrut(5));
            toolBar.add(l);
            toolBar.add(Box.createHorizontalStrut(5));
        }
        return toolBar;
    }

    /** Resets the controls. */
    private void reset() {
        toolBar.remove(buttonIndex);
        toolBar.add(removeButton, buttonIndex);
        removeButton.setEnabled(true);
        exceptionButton.setVisible(false);
        status.setBusy(false);
        status.setVisible(false);
        statusPane = iconLabel;
        remove(statusPane);
        add(statusPane, "0, 0, CENTER, CENTER");
        repaint();
    }

    /**
     * Converts the passed mapped.
     * 
     * @param m The map to handle.
     * @return See above.
     */
    private Map<String, Object> convertResult(Map<String, Object> m) {
        Map<String, Object> objects = new HashMap<String, Object>();
        if (m == null)
            return objects;
        messageLabel.setText("");
        Object v = m.get(MESSAGE);
        if (v != null) {
            if (v instanceof String)
                messageLabel.setText((String) v);
        }
        m.remove(MESSAGE);
        if (m.containsKey(STD_ERR)) {
            errorObject = m.get(STD_ERR);
            m.remove(STD_ERR);
        }
        if (m.containsKey(STD_OUT)) {
            infoObject = m.get(STD_OUT);
            m.remove(STD_OUT);
        }
        return m;
    }

    /** Shows the exception. */
    private void showException() {
        if (exception == null)
            return;
        viewer.notifyError(type.getText(), messageLabel.getText(), exception);
    }

    /**
     * Returns the identifier of the plugin to run.
     * 
     * @return See above.
     */
    private int runAsPlugin() {
        Environment env = (Environment) registry.lookup(LookupNames.ENV);
        if (env == null)
            return -1;
        return env.runAsPlugin();
    }

    /**
      * Creates a new instance.
      * 
      * @param viewer The viewer this data loader is for.
      *               Mustn't be <code>null</code>.
      * @param registry Convenience reference for subclasses.
      * @param ctx The security context.
      */
    ActivityComponent(UserNotifier viewer, Registry registry, SecurityContext ctx) {
        if (viewer == null)
            throw new NullPointerException("No viewer.");
        if (registry == null)
            throw new NullPointerException("No registry.");
        this.viewer = viewer;
        this.registry = registry;
        this.ctx = ctx;
    }

    /**
     * Initializes the components.
     * 
     * @param text      The text of the activity.
      * @param icon      The icon to display then done.
     */
    void initialize(String text, Icon icon) {
        initComponents(text, icon);
        buildGUI();
    }

    /**
     * Creates a button.
     * 
     * @param text The text of the button.
     * @param actionID The action command id.
     * @param l The action listener.
     * @return See above.
     */
    JButton createButton(String text, int actionID, ActionListener l) {
        JButton b = UIUtilities.createHyperLinkButton(text);
        b.setActionCommand("" + actionID);
        b.addActionListener(l);
        return b;
    }

    /**
    * Returns the name to give to the file.
    * 
    * @param files Collection of files in the currently selected directory.
    * @param fileName The name of the original file.
    * @param original The name of the file. 
    * @param dirPath Path to the directory.
    * @param index The index of the file.
    * @param extension The extension to check or <code>null</code>.
    * @return See above.
    */
    String getFileName(File[] files, String fileName, String original, String dirPath, int index,
            String extension) {
        String path = dirPath + original;
        boolean exist = false;
        if (files != null) {
            for (int i = 0; i < files.length; i++) {
                if ((files[i].getAbsolutePath()).equals(path)) {
                    exist = true;
                    break;
                }
            }
        }
        if (!exist)
            return original;
        if (StringUtils.isEmpty(fileName))
            return original;

        if (!StringUtils.isEmpty(extension)) {
            int n = fileName.lastIndexOf(extension);
            String v = fileName.substring(0, n) + "_(" + index + ")" + extension;
            index++;
            return getFileName(files, fileName, v, dirPath, index, extension);
        } else {
            int lastDot = fileName.lastIndexOf(".");
            if (lastDot != -1) {
                extension = fileName.substring(lastDot, fileName.length());
                String v = fileName.substring(0, lastDot) + "_(" + index + ")" + extension;
                index++;
                return getFileName(files, fileName, v, dirPath, index, null);
            }
        }

        return original;
    }

    /** Invokes when the activity has been cancelled. */
    public void onActivityCancelled() {
        reset();
        firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
        notifyActivityCancelled();
        EventBus bus = registry.getEventBus();
        bus.post(new ActivityProcessEvent(this, false));
    }

    /** Invokes when the call-back has been set. */
    public void onCallBackSet() {
        cancelButton.setEnabled(true);
    }

    /** Invokes when the activity starts. */
    public void startActivity() {
        status.setBusy(true);
    }

    /**
     * Returns <code>true</code> if the result can be displayed, 
     * <code>false</code> otherwise.
     * 
     * @param object The object to handle.
     * @return See above.
     */
    boolean canPlotResult(Object object) {
        if (object instanceof FileAnnotationData) {
            FileAnnotationData fa = (FileAnnotationData) object;
            if (fa.isLoaded()) {
                return fa.getFileName().endsWith("." + CSVFilter.CSV);
            }
        }
        return false;
    }

    /** 
     * Downloads the passed object is supported.
     * 
     * @param text   The text used if the object is not loaded.
     * @param object The object to handle.
     * @param folder Indicates where to download the file or <code>null</code>.
     */
    void download(String text, Object object, File folder) {
        if (!(object instanceof FileAnnotationData || object instanceof OriginalFile))
            return;
        int index = -1;
        if (text == null)
            text = "";
        String name = "";
        String description = "";
        long dataID = -1;
        OriginalFile of = null;
        if (object instanceof FileAnnotationData) {
            FileAnnotationData data = (FileAnnotationData) object;
            if (data.isLoaded()) {
                name = data.getFileName();
                description = data.getDescription();
                of = (OriginalFile) data.getContent();
            } else {
                of = null;
                dataID = data.getId();
                index = DownloadActivityParam.FILE_ANNOTATION;
                if (text.length() == 0)
                    text = "Annotation";
                name = text + "_" + dataID;
            }
        } else {
            of = (OriginalFile) object;
            if (!of.isLoaded()) {
                dataID = of.getId().getValue();
                index = DownloadActivityParam.ORIGINAL_FILE;
                if (text.length() == 0)
                    text = "File";
                name = text + "_" + dataID;
                of = null;
            }
        }
        final OriginalFile original = of;
        final int type = index;
        final String desc = description;
        final long id = dataID;
        if (folder != null) {
            if (original == null && type == -1)
                return;
            DownloadActivityParam activity;
            IconManager icons = IconManager.getInstance(registry);
            if (original != null) {
                activity = new DownloadActivityParam(original, folder, icons.getIcon(IconManager.DOWNLOAD_22));

            } else {
                activity = new DownloadActivityParam(id, type, folder, icons.getIcon(IconManager.DOWNLOAD_22));
            }
            activity.setLegend(desc);
            activity.setUIRegister(false);
            viewer.notifyActivity(ctx, activity);
            return;
        }
        JFrame f = registry.getTaskBar().getFrame();
        FileChooser chooser = new FileChooser(f, FileChooser.SAVE, "Download", "Select where to download the file.",
                null, true, true);
        IconManager icons = IconManager.getInstance(registry);
        chooser.setTitleIcon(icons.getIcon(IconManager.DOWNLOAD_48));
        chooser.setSelectedFileFull(name);
        chooser.setApproveButtonText("Download");
        chooser.addPropertyChangeListener(new PropertyChangeListener() {

            public void propertyChange(PropertyChangeEvent evt) {
                String name = evt.getPropertyName();
                if (FileChooser.APPROVE_SELECTION_PROPERTY.equals(name)) {
                    File[] files = (File[]) evt.getNewValue();
                    File folder = files[0];
                    if (original == null && type == -1)
                        return;
                    IconManager icons = IconManager.getInstance(registry);
                    DownloadActivityParam activity;
                    if (original != null) {
                        activity = new DownloadActivityParam(original, folder,
                                icons.getIcon(IconManager.DOWNLOAD_22));

                    } else {
                        activity = new DownloadActivityParam(id, type, folder,
                                icons.getIcon(IconManager.DOWNLOAD_22));
                    }
                    activity.setLegend(desc);
                    viewer.notifyActivity(ctx, activity);
                }
            }
        });
        chooser.centerDialog();
    }

    /** 
     * Downloads the passed object is supported.
     * 
     * @param text   The text used if the object is not loaded.
     * @param object The object to handle.
     */
    void download(String text, Object object) {
        download(text, object, null);
    }

    /**
     * Views the passed object if supported.
     * 
     * @param object The object to view.
     * @param source The UI component invoking the method.
     */
    void view(Object object, JComponent source) {
        if (object instanceof FileAnnotationData || object instanceof OriginalFile) {
            open(object, null, source);
        } else if (object instanceof File) {
            viewer.openApplication(null, ((File) object).getAbsolutePath());
            if (source != null)
                source.setEnabled(true);
        } else {
            EventBus bus = registry.getEventBus();
            //Check if running as plug-in
            ViewObjectEvent evt = new ViewObjectEvent(ctx, object, source);
            evt.setPlugin(runAsPlugin());
            bus.post(evt);
        }
    }

    /**
     * Browses the node.
     * 
     * @param object The object to view.
     * @param source The UI component invoking the method.
     */
    void browse(Object object, JComponent source) {
        EventBus bus = registry.getEventBus();
        ViewObjectEvent evt = new ViewObjectEvent(ctx, object, source);
        evt.setBrowseObject(true);
        bus.post(evt);
    }

    /**
     * Returns <code>true</code> if information returned by script,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean hasInfo() {
        return infoObject != null;
    }

    /**
     * Returns <code>true</code> if information returned by script,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean hasError() {
        return errorObject != null;
    }

    /**
     * Returns <code>true</code> if the activity is still on-going,
     * <code>false</code> otherwise.
     * 
     * @return See above.
     */
    boolean isOngoingActivity() {
        return status.isBusy();
    }

    /**
     * Notifies that it was not possible to complete the activity.
     * 
     * @param text The text to set.
     * @param message The reason of the error.
     * @param ex The exception to handle.
     */
    public void notifyError(String text, String message, Throwable ex) {
        reset();

        int status = -1;
        if (ex != null) {
            Throwable cause = ex.getCause();
            if (cause instanceof ProcessException) {
                status = ((ProcessException) cause).getStatus();
                if (status == ProcessException.NO_PROCESSOR)
                    messageLabel.setText("No processor available. " + "Please try later.");
            }
        }

        if (text != null) {
            type.setText(text);
            if (message != null)
                type.setToolTipText(message);
        }
        //if (message != null) messageLabel.setText(message);
        exception = ex;
        if (exception != null && status != ProcessException.NO_PROCESSOR) {
            exceptionButton.setVisible(true);
            exceptionButton.setToolTipText(UIUtilities.formatExceptionForToolTip(ex));
        }
        firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
        notifyActivityError();
        EventBus bus = registry.getEventBus();
        bus.post(new ActivityProcessEvent(this, true));
    }

    /**
     * Shows the menu corresponding to the passed index.
     * 
     * @param src The source of the click
     * @param index The index indicating which menu to show.
     * @param x The x-coordinate of the mouse clicked.
     * @param y The y-coordinate of the mouse clicked.
     */
    private void showMenu(JComponent src, int index, int x, int y) {
        switch (index) {
        case ERROR:
            if (errorMenu == null)
                errorMenu = new ActivityResultPopupMenu(errorObject, this);
            errorMenu.show(src, x, y);
            break;
        case INFO:
            if (infoMenu == null)
                infoMenu = new ActivityResultPopupMenu(infoObject, this);
            infoMenu.show(src, x, y);
        }
    }

    /** 
     * Invokes when the activity end. 
     * 
     * @param result The result of the activity.
     */
    public void endActivity(Object result) {
        this.result = result;
        boolean busy = status.isBusy();
        reset();
        if (result instanceof Map) {
            Map<String, Object> m = convertResult((Map<String, Object>) result);
            int size = m.size();
            this.result = m;
            remove(resultPane);
            Color c = getBackground();
            if (size == 0) {
                JToolBar row = new JToolBar();
                row.setOpaque(false);
                row.setFloatable(false);
                row.setBorder(null);
                row.setBackground(c);
                JButton button;
                if (errorObject != null) {
                    button = createButton(ActivityResultRow.ERROR_TEXT, ERROR, this);
                    button.addMouseListener(new MouseAdapter() {

                        public void mouseReleased(MouseEvent e) {
                            showMenu((JComponent) e.getSource(), ERROR, e.getX(), e.getY());
                        }
                    });
                    row.add(button);
                }
                if (infoObject != null) {
                    button = createButton(ActivityResultRow.INFO_TEXT, INFO, this);
                    button.addMouseListener(new MouseAdapter() {

                        public void mouseReleased(MouseEvent e) {
                            showMenu((JComponent) e.getSource(), INFO, e.getX(), e.getY());
                        }
                    });
                    row.add(button);
                }
                add(row, paneIndex);
            } else {
                Entry<String, Object> entry;
                Iterator<Entry<String, Object>> i = m.entrySet().iterator();
                ActivityResultRow row = null;
                JPanel content = new JPanel();
                content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));

                content.setBackground(c);
                int index = 0;
                int max = 2;
                JButton moreButton = null;
                while (i.hasNext()) {
                    entry = (Entry<String, Object>) i.next();
                    this.result = entry.getValue();
                    row = new ActivityResultRow((String) entry.getKey(), entry.getValue(), this);
                    row.setBackground(c);
                    row.addPropertyChangeListener(listener);
                    resultButtons.add(row);
                    if (index < max)
                        content.add(row);
                    else {
                        if (moreButton == null) {
                            moreButton = createButton("" + (m.size() - max) + " more", ALL_RESULT, this);
                            content.add(moreButton);
                        }
                    }
                    index++;
                }

                if (m.size() == 1)
                    add(row, paneIndex);
                else
                    add(content, paneIndex);
                resultPane = content;
            }

            repaint();
        }

        firePropertyChange(UNREGISTER_ACTIVITY_PROPERTY, null, this);
        notifyActivityEnd();
        //Post an event to 
        //if (busy) {
        EventBus bus = registry.getEventBus();
        bus.post(new ActivityProcessEvent(this, busy));
        //}
    }

    /**
     * Returns the type of activity.
     * 
     * @return See above.
     */
    public JComponent getActivityType() {
        return new JLabel(type.getText());
    }

    /** Subclasses should override the method. */
    protected abstract void notifyActivityCancelled();

    /** Subclasses should override the method. */
    protected abstract void notifyActivityEnd();

    /** Subclasses should override the method. */
    protected abstract void notifyActivityError();

    /** Creates a loader. */
    protected abstract UserNotifierLoader createLoader();

    /**
     * Removes the activity from the display
     * @see ActionListener#actionPerformed(ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
        int index = Integer.parseInt(e.getActionCommand());
        switch (index) {
        case REMOVE:
            firePropertyChange(REMOVE_ACTIVITY_PROPERTY, null, this);
            break;
        case CANCEL:
            onActivityCancelled();
            if (cancelButton != null)
                cancelButton.setEnabled(false);
            if (loader != null)
                loader.cancel();
            break;
        case EXCEPTION:
            showException();
            break;
        case ALL_RESULT:
            Iterator<ActivityResultRow> i = resultButtons.iterator();
            JPanel content = new JPanel();
            content.setBackground(getBackground());
            content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
            while (i.hasNext()) {
                content.add(i.next());
            }
            remove(resultPane);
            add(content, paneIndex);
            resultPane = content;
            validate();
            repaint();
            break;
        }
    }

    /**
     * Overridden to make sure that all the components have the correct 
     * background.
     * @see JPanel#setBackground(Color)
     */
    public void setBackground(Color color) {
        super.setBackground(color);
        if (resultPane != null)
            resultPane.setBackground(color);
        if (removeButton != null)
            removeButton.setBackground(color);
        if (cancelButton != null)
            cancelButton.setBackground(color);
        if (exceptionButton != null)
            exceptionButton.setBackground(color);
    }

}