org.eclipse.birt.report.designer.data.ui.property.AbstractPropertyDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.birt.report.designer.data.ui.property.AbstractPropertyDialog.java

Source

/*******************************************************************************
 * Copyright (c) 2004 Actuate Corporation. All rights reserved. This program and
 * the accompanying materials are made available under the terms of the Eclipse
 * Public License v1.0 which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors: Actuate Corporation - initial API and implementation
 ******************************************************************************/

package org.eclipse.birt.report.designer.data.ui.property;

import org.eclipse.birt.report.designer.ui.dialogs.BaseDialog;
import org.eclipse.birt.report.designer.ui.dialogs.properties.IPropertyPage;
import org.eclipse.birt.report.designer.ui.dialogs.properties.IPropertyPageContainer;
import org.eclipse.core.runtime.ListenerList;
import org.eclipse.jface.dialogs.DialogMessageArea;
import org.eclipse.jface.dialogs.DialogPage;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IMessageProvider;
import org.eclipse.jface.dialogs.IPageChangeProvider;
import org.eclipse.jface.dialogs.IPageChangedListener;
import org.eclipse.jface.dialogs.PageChangedEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.swt.widgets.Widget;

import com.ibm.icu.util.StringTokenizer;

/**
 * This is the base class for a dialog box with a tree on the left hand side and
 * pages for each node on the right. It takes a model object in the constructor
 * and maintains a reference to this object. The pages can retrieve this model
 * object by calling the {@link #getModel() getModel}method. It defines two
 * abstract methods {@link #performCancel() performCancel}and
 * {@link #performOk() performOk}. These methods are called when the user
 * presses the ok or cancel button. <br/>
 * <br/>
 * 
 * Pages can be added to this by either calling the
 * {@link #addNodeTo(String, PropertyNode) addNodeTo}method or by calling the
 * {@link #addPageTo(String, String, String, Image, IPropertyPage) addPageTo}
 * method.
 * 
 * @version $Revision: 1.19.6.1 $ $Date: 2011/04/02 07:27:08 $
 */

public abstract class AbstractPropertyDialog extends BaseDialog
        implements IPropertyPageContainer, IPageChangeProvider {

    private static final String SASHFORM_RIGHT = "SASHFORM.RIGHT"; //$NON-NLS-1$

    private static final String SASHFORM_LEFT = "SASHFORM.LEFT"; //$NON-NLS-1$

    private transient Object modelObject = null;

    protected transient PropertyNode rootNode = null;

    private transient StackLayout propertyPaneLayout = null;

    private transient Composite propertyPane = null;

    private transient PropertyNode currentNode = null;

    private transient boolean processSelection = true;

    private transient Label titleImage = null;

    protected TreeViewer viewer = null;

    private DialogMessageArea messageArea = null;

    private String nodeId;

    protected boolean showPage = false;

    private int[] widthHints = new int[2];

    private Control treeViewer;

    private Control pageContainer;

    private Composite container;

    private ListenerList pageChangedListeners = new ListenerList();

    /**
     * The only constructor for this dialog. It takes the parentShell and the
     * model object as parameters.
     * 
     * @param parentShell
     *            The parent shell
     * @param model
     *            The model object that the dialog pages will interact with.
     */
    public AbstractPropertyDialog(Shell parentShell, Object model) {
        super(parentShell, ""); //$NON-NLS-1$
        setModel(model);
        setShellStyle(getShellStyle() | SWT.RESIZE | SWT.MAX);
        propertyPaneLayout = new StackLayout();
        rootNode = new PropertyNode(
                "org.eclipse.birt.report.designer.ui.dialogs.properties.propertydialog.rootnode"); //$NON-NLS-1$
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.birt.report.designer.ui.IPropertyPageContainer#setModel(java
     * .lang.Object)
     */
    public void setModel(Object model) {
        modelObject = model;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.birt.report.designer.ui.IPropertyPageContainer#getModel()
     */
    public final Object getModel() {
        return modelObject;
    }

    /**
     * Adds a node to the dialog at the specified path The path needs to point
     * to an existing node or to the root element. The path elements are
     * separated by the "/" character. If the path is null then the node is just
     * added to the root. For e.g. In order to create the following tree
     * structure: <br/>
     * 
     * <pre>
     * 
     * 
     * 
     * 
     * 
     *           Prop1
     *           |_
     *             Prop2
     * 
     * 
     * 
     * 
     * 
     * </pre>
     * 
     * <br/>
     * The api needs to be called as follows: <br/>
     * <code>
     *  addNodeTo("/", Prop1Node);<br/>
     *  addNodeTo("/Prop1", Prop2Node);<br/>
     * </code> Prop1 and Prop2 being the node ids of the respective nodes.
     * 
     * @param path
     *            The path under which the new node is to be added.
     * @param node
     *            The node to be added.
     */
    public final void addNodeTo(String path, PropertyNode node) {
        node.setContainer(this);
        if (path == null || path.trim().equals("/")) //$NON-NLS-1$
        {
            rootNode.add(node);
        } else {
            PropertyNode parentNode = getNode(path);
            if (parentNode != null) {
                parentNode.add(node);
            }
        }
    }

    /**
     * This is a convenience method that can be used to add the PropertyPage to
     * the dialog. In turn it creates a PropertyNode object and calls the
     * {@link #addNodeTo(String, PropertyNode) addNodeTo}method.
     * 
     * @param path
     *            The path under which the new page is to be added.
     * @param nodeId
     *            The unique id to be assigned to the node
     * @param nodeLabel
     *            The label for the node, If this is null then the page name is
     *            used
     * @param nodeImage
     *            The image for the node if any. If this is null then the image
     *            returned by the page is used
     * @param page
     *            The page to be added to the dialog.
     */
    public final void addPageTo(String path, String nodeId, String nodeLabel, Image nodeImage, IPropertyPage page) {
        PropertyNode node = new PropertyNode(nodeId, nodeLabel, nodeImage, page);
        addNodeTo(path, node);
    }

    /**
     * Looks up the node based on the path it is givean
     * 
     * @param path
     *            The complete path to the target node.
     * @return The Node object or null if it is not found.
     */
    private final PropertyNode getNode(String path) {
        PropertyNode currentNode = null;
        if (path != null) {
            path = path.trim();
            currentNode = rootNode;
            StringTokenizer tokenizer = new StringTokenizer(path, "/"); //$NON-NLS-1$
            while (tokenizer.hasMoreTokens()) {
                currentNode = currentNode.getSubNode(tokenizer.nextToken());
                if (currentNode == null) {
                    return null;
                }
            }

        }
        return currentNode;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.jface.dialogs.Dialog#createDialogArea(org.eclipse.swt.widgets
     * .Composite)
     */
    protected Control createDialogArea(Composite parent) {
        Composite composite = new Composite(parent, SWT.NONE);
        composite.setLayoutData(new GridData(GridData.FILL_BOTH));
        GridLayout layout = new GridLayout();
        layout.numColumns = 1;
        layout.marginWidth = convertHorizontalDLUsToPixels(IDialogConstants.HORIZONTAL_MARGIN);
        layout.marginHeight = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_MARGIN);
        layout.verticalSpacing = convertVerticalDLUsToPixels(IDialogConstants.VERTICAL_SPACING);
        composite.setLayout(layout);

        container = new Composite(composite, SWT.NONE);
        layout = new GridLayout();
        layout.numColumns = 3;
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        layout.horizontalSpacing = 2;
        container.setLayout(layout);
        container.setLayoutData(new GridData(GridData.FILL_BOTH));

        treeViewer = createTreeViewer(container);
        treeViewer.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        Sash sash = createSash(container);
        pageContainer = createPropertyPane(container);
        pageContainer.setLayoutData(new GridData(GridData.FILL_BOTH));
        addDragListerner(sash, container, treeViewer, pageContainer);

        Label label = new Label(composite, SWT.HORIZONTAL | SWT.SEPARATOR);
        label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

        initTreeSelection();

        return composite;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.TrayDialog#close()
     */
    public boolean close() {

        IDialogSettings setting = getDialogBoundsSettings();
        if (setting != null) {
            setting.put(SASHFORM_LEFT, widthHints[0]);
            setting.put(SASHFORM_RIGHT, widthHints[1]);
        }

        return super.close();
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.Dialog#initializeBounds()
     */
    protected void initializeBounds() {
        try {
            IDialogSettings setting = getDialogBoundsSettings();
            if (setting != null) {
                widthHints[0] = setting.getInt(SASHFORM_LEFT);
                widthHints[1] = setting.getInt(SASHFORM_RIGHT);
            } else {
                final int total = getDefaultSize().x;
                widthHints[0] = (int) (total * 0.2);
                widthHints[1] = (int) (total * 0.8);
            }

        } catch (NumberFormatException e) {
            final int total = getDefaultSize().x;
            widthHints[0] = (int) (total * 0.2);
            widthHints[1] = (int) (total * 0.8);
        }
        GridData data = (GridData) treeViewer.getLayoutData();
        data.widthHint = widthHints[0];
        data = (GridData) pageContainer.getLayoutData();
        data.widthHint = widthHints[1];
        container.layout(true);
        super.initializeBounds();
    }

    /**
     * 
     * @param sash
     * @param parent
     * @param left
     * @param right
     */
    private void addDragListerner(final Sash sash, final Composite parent, final Control left,
            final Control right) {
        sash.addListener(SWT.Selection, new Listener() {

            public void handleEvent(Event event) {
                if (event.detail == SWT.DRAG) {
                    return;
                }
                Sash sash = (Sash) event.widget;
                int shift = event.x - sash.getBounds().x;

                GridData data = (GridData) left.getLayoutData();
                int newWidthHint = data.widthHint + shift;
                if (newWidthHint < 100) {
                    newWidthHint = 100;
                    shift = 100 - data.widthHint;
                }
                // if the dialog wasn't of a custom size we know we can shrink
                // it if necessary based on sash movement.
                widthHints[0] = data.widthHint = newWidthHint;
                data = (GridData) right.getLayoutData();
                newWidthHint = data.widthHint - shift;
                widthHints[1] = data.widthHint = newWidthHint;
                parent.layout();
            }
        });
    }

    /**
     * For the first node of tree is not default selected as it is under
     * Windows, this method set the first node to be selected for Linux.
     */
    private void initTreeSelection() {
        PropertyNode node = getNode(getDeafultNode());
        if (node != null) {
            StructuredSelection select = new StructuredSelection(node);
            viewer.setSelection(select);
        } else {
            if (viewer.getTree().getItems().length > 0) {
                TreeItem firstNode = viewer.getTree().getItems()[0];
                StructuredSelection select = new StructuredSelection(firstNode.getData());
                viewer.setSelection(select);
            }
        }
    }

    private final Control createTreeViewer(Composite parent) {
        viewer = new TreeViewer(parent);
        viewer.addSelectionChangedListener(new ISelectionChangedListener() {

            public void selectionChanged(SelectionChangedEvent event) {
                final PropertyNode selectedNode = (PropertyNode) ((IStructuredSelection) event.getSelection())
                        .getFirstElement();
                if (selectedNode == null)
                    return;
                if (processSelection) {
                    BusyIndicator.showWhile(getShell().getDisplay(), new Runnable() {

                        public void run() {
                            showSelectionPage(selectedNode);
                        }
                    });
                }
                processSelection = true;
            }
        });

        viewer.getTree().addMouseTrackListener(new MouseTrackAdapter() {

            public void mouseHover(MouseEvent event) {
                Widget widget = event.widget;
                if (widget == viewer.getTree()) {
                    Point pt = new Point(event.x, event.y);
                    TreeItem item = viewer.getTree().getItem(pt);
                    if (item == null)
                        viewer.getTree().setToolTipText("");//$NON-NLS-1$
                    else {
                        String text = null;
                        if (item.getData() instanceof PropertyNode) {
                            text = ((PropertyNode) item.getData()).getPage().getToolTip();
                        }

                        viewer.getTree().setToolTipText(text);
                    }
                }
            }
        });

        viewer.setContentProvider(new PropertyContentProvider());
        viewer.setLabelProvider(new PropertyLabelProvider());
        viewer.setInput(rootNode);
        return viewer.getTree();
    }

    public void showSelectionPage(PropertyNode selectedNode) {
        if (selectedNode == null)
            return;
        showPage = showPage(selectedNode);
        if (!showPage) {
            processSelection = false;
            viewer.setSelection(new StructuredSelection(currentNode));
        }
    }

    private final Composite createPropertyPane(Composite parent) {
        // If the title is visible
        // create the title area.
        Composite propertyContainer = new Composite(parent, SWT.NONE);
        GridLayout layout = new GridLayout();
        layout.marginWidth = 0;
        layout.marginHeight = 0;
        propertyContainer.setLayout(layout);

        Composite titleComposite = new Composite(propertyContainer, SWT.NONE);
        layout = new GridLayout();
        layout.marginHeight = 0;
        layout.marginWidth = 0;
        layout.verticalSpacing = 0;
        layout.horizontalSpacing = 0;
        titleComposite.setLayout(layout);
        GridData titleLayoutData = new GridData(GridData.FILL_HORIZONTAL);
        titleLayoutData.horizontalIndent = IDialogConstants.HORIZONTAL_MARGIN;
        titleComposite.setLayoutData(titleLayoutData);

        if (isTitleVisible()) {
            createTitleArea(titleComposite);

            Label separator = new Label(propertyContainer, SWT.HORIZONTAL | SWT.SEPARATOR);
            separator.setLayoutData(new GridData(GridData.FILL_HORIZONTAL | GridData.GRAB_HORIZONTAL));
        }

        GridData data = new GridData(GridData.FILL_BOTH);
        propertyPane = new Composite(propertyContainer, SWT.NONE);
        propertyPane.setLayout(propertyPaneLayout);
        propertyPane.setLayoutData(data);
        return propertyContainer;
    }

    /**
     * Create TitleArea
     * 
     * @param parent
     * @return
     */
    public Composite createTitleArea(Composite parent) {
        Composite messageComposite = new Composite(parent, SWT.NONE);
        GridData data = new GridData(GridData.FILL_BOTH);
        messageComposite.setLayoutData(data);

        FormLayout messageLayout = new FormLayout();
        messageComposite.setLayout(messageLayout);
        messageArea = new DialogMessageArea();
        messageArea.createContents(messageComposite);

        titleImage = new Label(messageComposite, SWT.LEFT);
        FormData imageData = new FormData();
        imageData.right = new FormAttachment(100);
        imageData.top = new FormAttachment(0);
        imageData.bottom = new FormAttachment(100);
        titleImage.setLayoutData(imageData);

        FormData messageData = new FormData();
        messageData.left = new FormAttachment(0, 0);
        messageData.right = new FormAttachment(titleImage, 100);
        messageData.bottom = new FormAttachment(titleImage, 0, SWT.BOTTOM);
        messageData.top = new FormAttachment(0, 0);
        messageArea.setTitleLayoutData(messageData);
        messageArea.setMessageLayoutData(messageData);

        return messageComposite;
    }

    private final boolean showPage(PropertyNode node) {
        if (currentNode != null) {
            // First check whether we can change the page.
            if (node == currentNode || !currentNode.getPage().canLeave()) {
                return false;
            }
        }
        // Check whether the page control has been created
        if (!node.isPageControlCreated()) {
            // if not create it
            Control control = node.createPageControl(propertyPane);
            if (node.getId() != null && node.getId().equals(this.nodeId)) {
                control.setFocus();
            }
            assert (control != null) : "Control has not been created for node " + node.getId(); //$NON-NLS-1$
        }
        currentNode = node;
        // show the page control
        propertyPaneLayout.topControl = node.getPageControl();
        // Call the activated method to indicate that this page is activated
        node.getPage().pageActivated();
        viewer.setSelection(new StructuredSelection(node));
        propertyPane.layout();
        firePageChanged(new PageChangedEvent(this, new DialogPageAdapter(currentNode)));
        return true;

    }

    /**
     *
     */
    static class DialogPageAdapter extends DialogPage {

        private PropertyNode node;

        /**
         * 
         * @param node
         */
        DialogPageAdapter(PropertyNode node) {
            this.node = node;
        }

        /*
         * (non-Javadoc)
         * 
         * @see org.eclipse.jface.dialogs.DialogPage#getControl()
         */
        public Control getControl() {
            return node.getPageControl();
        }

        public void createControl(Composite parent) {
            // do nothing here
        }
    }

    /**
     * 
     * @return
     */
    protected Composite getPropertyPane() {
        return this.propertyPane;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.Dialog#buttonPressed(int)
     */
    protected final void buttonPressed(int buttonId) {
        switch (buttonId) {
        case IDialogConstants.OK_ID: {
            okPressed();
            break;
        }
        case IDialogConstants.CANCEL_ID: {
            cancelPressed();
            break;
        }
        case IDialogConstants.HELP_ID: {
            performHelp();
            break;
        }
        default: {
            super.buttonPressed(buttonId);
        }
        }
    }

    /**
     * Called when the user pressed the Ok button. It is called only if all the
     * pages return true for their individual
     * {@link org.eclipse.birt.report.designer.data.ui.property.IPropertyPage#performOk()
     * performOk} methods. Derived classes can save the contents of the dialog
     * and perform any validation if necessary on the contents. <br/>
     * The dialog is closed if this method returns true.
     * 
     * @return boolean indicating whether the method was successful or not.
     */
    public abstract boolean performOk();

    /**
     * Called when the user presses the Cancel button. It is called only if all
     * the pages return true for their individual
     * {@link org.eclipse.birt.report.designer.data.ui.property.IPropertyPage#performCancel()
     * performCancel()} methods. <br/>
     * The dialog is closed if this method returns true.
     * 
     * @return boolean indicating whether the method was successful or not.
     */
    public abstract boolean performCancel();

    private final void performHelp() {
        // Call the perform help of the current page
        if (currentNode != null) {
            currentNode.getPageControl().notifyListeners(SWT.Help, new Event());
        }
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.Dialog#cancelPressed()
     */
    protected final void cancelPressed() {
        // First call cancel on all the pages
        if (rootNode.hasSubNodes()) {
            PropertyNode[] nodes = rootNode.getSubNodes();
            for (int n = 0; n < nodes.length; n++) {
                // Check whether the current page can be cancelled
                if (!cancelPressed(nodes[n])) {
                    return;
                }
            }
        }

        // Finally call it on the dialog
        if (performCancel()) {
            super.cancelPressed();
        }
    }

    private final boolean cancelPressed(PropertyNode node) {
        if (node.getPage().performCancel()) {
            // if this node allows performing the cancel operation
            // check the child nodes
            if (node.hasSubNodes()) {
                PropertyNode[] nodes = node.getSubNodes();
                for (int n = 0; n < nodes.length; n++) {
                    // Check whether the current page can be cancelled
                    if (!cancelPressed(nodes[n])) {
                        return false;
                    }
                }
            }
            // If we have come here then we know that all the nodes have
            // successfully allowed cancelling
            return true;
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.Dialog#okPressed()
     */
    protected void okPressed() {
        if (currentNode != null) {
            if (!okPressed(currentNode))
                return;
        }
        // First call ok on all the pages
        if (rootNode.hasSubNodes()) {
            PropertyNode[] nodes = rootNode.getSubNodes();
            for (int n = 0; n < nodes.length; n++) {
                // Check whether the current page can be closed
                if (nodes[n] != currentNode && (!okPressed(nodes[n]))) {
                    return;
                }
            }
        }
        // Finally call it on the dialog
        if (performOk()) {
            super.okPressed();
        }
    }

    private final boolean okPressed(PropertyNode node) {
        if (node.getPage().performOk()) {
            // if this node allows performing the cancel operation
            // check the child nodes
            if (node.hasSubNodes()) {
                PropertyNode[] nodes = node.getSubNodes();
                for (int n = 0; n < nodes.length; n++) {
                    // Check whether the current page can be cancelled
                    if (!okPressed(nodes[n])) {
                        return false;
                    }
                }
            }
            // If we have come here then we know that all the nodes have
            // successfully allowed cancelling
            return true;
        }
        return false;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.window.Window#handleShellCloseEvent()
     */
    protected void handleShellCloseEvent() {
        // Same as cancel
        cancelPressed();
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.eclipse.birt.report.designer.internal.ui.dialogs.BaseDialog#
     * getDefaultSize()
     */
    protected Point getDefaultSize() {
        return new Point(800, 500);
    }

    /**
     * Indicates whether the title area on top of the property page should be
     * shown or not. It is called only once when the dialog is created. This
     * default implementation of this method returns true. Sub classes may
     * override this method to return false if they do not wish to show the
     * title are.
     * 
     * @return boolean indicating whether the title should be displayed or not.
     */
    protected boolean isTitleVisible() {
        return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.birt.report.designer.ui.dialogs.properties.IPropertyPageContainer
     * #setMessage(java.lang.String, int)
     */
    public final void setMessage(String message, int messageType) {
        if (messageArea != null) {
            if (messageType != IMessageProvider.NONE) {
                titleImage.setVisible(false);
            } else {
                titleImage.setVisible(true);
            }
            if (message == null && messageType == IMessageProvider.ERROR) {
                messageArea.updateText("", messageType);
            } else {
                messageArea.updateText(message, messageType);
            }
        }
    }

    /**
     * Convenience method to set a message with the message type
     * IMessageProvider.NONE. It in turn calls the
     * {@link #setMessage (String, int) setMessage}method.
     * 
     * @param message
     *            The message to display in the title are
     */
    public final void setMessage(String message) {
        setMessage(message, IMessageProvider.NONE);
    }

    /**
     * @param attribute
     */
    protected void setDefaultNode(String attribute) {
        nodeId = attribute;

    }

    protected String getDeafultNode() {
        return nodeId;
    }

    protected PropertyNode getCurrentNode() {
        return currentNode;
    }

    private Sash createSash(final Composite composite) {
        final Sash sash = new Sash(composite, SWT.VERTICAL);
        sash.setLayoutData(new GridData(GridData.FILL_VERTICAL));
        return sash;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(
     * org.eclipse.jface.dialogs.IPageChangedListener)
     */
    public void addPageChangedListener(IPageChangedListener listener) {
        pageChangedListeners.add(listener);
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.eclipse.jface.dialogs.IPageChangeProvider#getSelectedPage()
     */
    public Object getSelectedPage() {
        return currentNode != null ? currentNode.getPage() : null;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener
     * (org.eclipse.jface.dialogs.IPageChangedListener)
     */
    public void removePageChangedListener(IPageChangedListener listener) {
        pageChangedListeners.remove(listener);
    }

    /**
     * 
     * @param event
     */
    protected void firePageChanged(final PageChangedEvent event) {
        Object[] listeners = pageChangedListeners.getListeners();
        for (int i = 0; i < listeners.length; i++) {
            final IPageChangedListener l = (IPageChangedListener) listeners[i];
            SafeRunnable.run(new SafeRunnable() {

                public void run() {
                    l.pageChanged(event);
                }
            });
        }
    }

}