org.eclipse.wst.common.project.facet.ui.internal.FacetsSelectionPanel.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.wst.common.project.facet.ui.internal.FacetsSelectionPanel.java

Source

/******************************************************************************
 * Copyright (c) 2010, 2014 Oracle and Others
 * 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:
 *    Konstantin Komissarchik - initial implementation and ongoing maintenance
 *    Rastislav Wagner - [444850] FacetsSelectionPanel wont disable menu when it should
 ******************************************************************************/

package org.eclipse.wst.common.project.facet.ui.internal;

import static java.lang.Math.max;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gdfill;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gdhfill;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gdhhint;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gdhspan;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gdwhint;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.gl;
import static org.eclipse.wst.common.project.facet.ui.internal.util.GridLayoutUtil.glmargins;
import static org.eclipse.wst.common.project.facet.ui.internal.util.SwtUtil.getPreferredWidth;
import static org.eclipse.wst.common.project.facet.ui.internal.util.SwtUtil.runOnDisplayThread;

import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.resource.CompositeImageDescriptor;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.ImageRegistry;
import org.eclipse.jface.viewers.CellEditor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.CheckboxTreeViewer;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.EditingSupport;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.TreeViewerColumn;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerSorter;
import org.eclipse.jface.window.ToolTip;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.TabFolder;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.ISharedImages;
import org.eclipse.ui.PlatformUI;
import org.eclipse.wst.common.project.facet.core.ICategory;
import org.eclipse.wst.common.project.facet.core.IFacetedProjectWorkingCopy;
import org.eclipse.wst.common.project.facet.core.IPreset;
import org.eclipse.wst.common.project.facet.core.IProjectFacet;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener;
import org.eclipse.wst.common.project.facet.core.events.IProjectFacetsChangedEvent;
import org.eclipse.wst.common.project.facet.core.internal.ProjectFacet;
import org.eclipse.wst.common.project.facet.core.runtime.RuntimeManager;
import org.eclipse.wst.common.project.facet.core.runtime.events.IRuntimeLifecycleEvent;
import org.eclipse.wst.common.project.facet.core.runtime.events.IRuntimeLifecycleListener;
import org.eclipse.wst.common.project.facet.ui.IDecorationsProvider;
import org.eclipse.wst.common.project.facet.ui.ModifyFacetedProjectWizard;
import org.eclipse.wst.common.project.facet.ui.internal.util.BasicToolTip;
import org.eclipse.wst.common.project.facet.ui.internal.util.HeaderToolTip;
import org.eclipse.wst.common.project.facet.ui.internal.util.ReadOnlyComboBoxCellEditor;
import org.osgi.framework.Bundle;

/**
 * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
 */

public final class FacetsSelectionPanel

        extends Composite implements ISelectionProvider

{
    private static final String IMG_DOWN_ARROW = "##down-arrow##"; //$NON-NLS-1$

    private final Composite topComposite;
    private final SashForm sform1;
    private final SashForm sform2;
    private final Label presetsLabel;
    private final Combo presetsCombo;
    private final Button savePresetButton;
    private final Button deletePresetButton;
    private final CheckboxTreeViewer treeViewer;
    private final Tree tree;
    private final TreeViewerColumn colFacet;
    private final TreeViewerColumn colVersion;
    private final FixedFacetToolTip fixedFacetToolTip;
    private final Menu popupMenu;
    private final MenuItem popupMenuLockUnlock;
    private final MenuItem popupMenuChangeVersion;
    private final TableViewer problemsView;
    private final TabItem detailsTabItem;
    private final DetailsPanel detailsPanel;
    private final TabItem runtimesTabItem;
    private final RuntimesPanel runtimesPanel;

    private boolean showToolTips;

    private final IFacetedProjectWorkingCopy fpjwc;
    private final List<IFacetedProjectListener> registeredWorkingCopyListeners;
    private final Map<IProjectFacet, IProjectFacetVersion> selectedVersions;
    private final List<ISelectionChangedListener> selectionListeners;
    private Object selection;

    /**
     * Holds images used throughout the panel.
     */

    private final ImageRegistry imageRegistry;

    private final IRuntimeLifecycleListener runtimeLifecycleListener;

    public interface IFilter {
        boolean check(IProjectFacetVersion fv);
    }

    public FacetsSelectionPanel(final Composite parent, final IFacetedProjectWorkingCopy fpjwc) {
        super(parent, SWT.NONE);

        this.fpjwc = fpjwc;
        this.registeredWorkingCopyListeners = new ArrayList<IFacetedProjectListener>();
        this.selectedVersions = new HashMap<IProjectFacet, IProjectFacetVersion>();
        this.selection = null;
        this.selectionListeners = new ArrayList<ISelectionChangedListener>();
        this.showToolTips = false;

        // Initialize the image registry.

        this.imageRegistry = new ImageRegistry();
        final Bundle bundle = Platform.getBundle(FacetUiPlugin.PLUGIN_ID);

        URL url = bundle.getEntry("images/down-arrow.gif"); //$NON-NLS-1$
        this.imageRegistry.put(IMG_DOWN_ARROW, ImageDescriptor.createFromURL(url));

        // Layout the panel.

        setLayout(glmargins(gl(1), 0, 0));

        this.topComposite = new Composite(this, SWT.NONE);
        this.topComposite.setLayout(glmargins(gl(4), 0, 0));

        this.presetsLabel = new Label(this.topComposite, SWT.NONE);
        this.presetsLabel.setText(Resources.presetsLabel);

        this.presetsCombo = new Combo(this.topComposite, SWT.READ_ONLY);
        this.presetsCombo.setLayoutData(gdhfill());

        this.savePresetButton = new Button(this.topComposite, SWT.PUSH);
        this.savePresetButton.setText(Resources.saveButtonLabel);
        GridDataFactory.defaultsFor(this.savePresetButton).applyTo(this.savePresetButton);

        this.savePresetButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(final SelectionEvent e) {
                handleSavePreset();
            }
        });

        this.deletePresetButton = new Button(this.topComposite, SWT.PUSH);
        this.deletePresetButton.setText(Resources.deleteButtonLabel);
        GridDataFactory.defaultsFor(this.deletePresetButton).applyTo(this.deletePresetButton);

        this.deletePresetButton.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(final SelectionEvent e) {
                handleDeletePreset();
            }
        });

        this.sform1 = new SashForm(this.topComposite, SWT.VERTICAL | SWT.SMOOTH);
        this.sform1.setLayoutData(gdhspan(gdfill(), 4));

        this.sform2 = new SashForm(this.sform1, SWT.HORIZONTAL | SWT.SMOOTH);
        this.sform2.setLayoutData(gdhspan(gdfill(), 4));

        this.treeViewer = new CheckboxTreeViewer(this.sform2, SWT.BORDER);
        this.tree = this.treeViewer.getTree();

        this.tree.setHeaderVisible(true);

        this.treeViewer.setContentProvider(new ContentProvider());
        this.treeViewer.setSorter(new Sorter());

        this.colFacet = new TreeViewerColumn(this.treeViewer, SWT.NONE);
        this.colFacet.getColumn().setText(Resources.facetColumnLabel);
        this.colFacet.getColumn().setResizable(true);
        this.colFacet.setLabelProvider(new FacetColumnLabelProvider());
        this.colFacet.getColumn().setWidth(computeDefaultFacetColumnWidth());

        this.colVersion = new TreeViewerColumn(this.treeViewer, SWT.NONE);
        this.colVersion.getColumn().setText(Resources.versionColumnLabel);
        this.colVersion.getColumn().setResizable(true);
        this.colVersion.setLabelProvider(new FacetVersionColumnLabelProvider());
        this.colVersion.setEditingSupport(new FacetVersionColumnEditingSupport(this.treeViewer));
        this.colVersion.getColumn().setWidth(computeDefaultVersionColumnWidth());

        this.popupMenu = new Menu(getShell(), SWT.POP_UP);

        this.popupMenuChangeVersion = new MenuItem(this.popupMenu, SWT.PUSH);
        this.popupMenuChangeVersion.setText(Resources.changeVersionMenuItem);

        this.popupMenuChangeVersion.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(final SelectionEvent e) {
                handleChangeVersionMenuSelected();
            }
        });

        this.popupMenuLockUnlock = new MenuItem(this.popupMenu, SWT.PUSH);

        this.popupMenuLockUnlock.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(final SelectionEvent e) {
                handleFacetLockUnlock();
            }
        });

        new FacetToolTip(this.tree);
        new CategoryToolTip(this.tree);
        this.fixedFacetToolTip = new FixedFacetToolTip(this.tree);

        this.treeViewer.setInput(new Object());

        this.treeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
            public void selectionChanged(final SelectionChangedEvent e) {
                FacetsSelectionPanel.this.handleSelectionChangedEvent();
            }
        });

        this.treeViewer.addCheckStateListener(new ICheckStateListener() {
            public void checkStateChanged(final CheckStateChangedEvent e) {
                FacetsSelectionPanel.this.handleCheckStateChanged(e);
            }
        });

        this.tree.addListener(SWT.PaintItem, new Listener() {
            public void handleEvent(final Event event) {
                handlePaintItemEvent(event);
            }
        });

        this.tree.addListener(SWT.MouseDown, new Listener() {
            public void handleEvent(final Event event) {
                handleMouseDownEvent(event);
            }
        });

        this.tree.addMenuDetectListener(new MenuDetectListener() {

            public void menuDetected(MenuDetectEvent arg0) {
                updatePopupMenu();
            }
        });

        final TabFolder tabFolder = new TabFolder(this.sform2, SWT.NONE);
        tabFolder.setLayoutData(gdhhint(gdhfill(), 80));

        this.detailsPanel = new DetailsPanel(tabFolder, this);
        this.detailsTabItem = new TabItem(tabFolder, SWT.NULL);
        this.detailsTabItem.setControl(this.detailsPanel);
        this.detailsTabItem.setText(Resources.detailsTabLabel);

        this.runtimesPanel = new RuntimesPanel(tabFolder, this.fpjwc);
        this.runtimesTabItem = new TabItem(tabFolder, SWT.NULL);
        this.runtimesTabItem.setControl(this.runtimesPanel);
        this.runtimesTabItem.setText(Resources.runtimesTabLabel);

        this.problemsView = new TableViewer(this.sform1, SWT.BORDER);
        this.problemsView.setContentProvider(new ProblemsContentProvider());
        this.problemsView.setLabelProvider(new ProblemsLabelProvider());
        this.problemsView.setInput(new Object());

        addDisposeListener(new DisposeListener() {
            public void widgetDisposed(final DisposeEvent e) {
                handleDisposeEvent();
            }
        });

        Dialog.applyDialogFont(parent);

        // Setup runtime lifecycle listener.

        this.runtimeLifecycleListener = new IRuntimeLifecycleListener() {
            public void handleEvent(final IRuntimeLifecycleEvent event) {
                handleValidationProblemsChangedEvent();
            }
        };

        RuntimeManager.addListener(this.runtimeLifecycleListener,
                IRuntimeLifecycleEvent.Type.VALIDATION_STATUS_CHANGED);

        // Bind to the model.

        addWorkingCopyListener(new IFacetedProjectListener() {
            public void handleEvent(final IFacetedProjectEvent event) {
                handleProjectFacetsChangedEvent(event);
            }
        }, IFacetedProjectEvent.Type.PROJECT_FACETS_CHANGED);

        handleProjectFacetsChangedEvent(null);

        addWorkingCopyListener(new IFacetedProjectListener() {
            public void handleEvent(final IFacetedProjectEvent event) {
                handleValidationProblemsChangedEvent();
            }
        }, IFacetedProjectEvent.Type.VALIDATION_PROBLEMS_CHANGED, IFacetedProjectEvent.Type.PROJECT_MODIFIED);

        handleValidationProblemsChangedEvent();

        addWorkingCopyListener(new IFacetedProjectListener() {
            public void handleEvent(final IFacetedProjectEvent event) {
                handleSelectedPresetChangedEvent();
            }
        }, IFacetedProjectEvent.Type.SELECTED_PRESET_CHANGED);

        ModifyFacetedProjectWizard.syncWithPresetsModel(this.fpjwc, this.presetsCombo);

        addWorkingCopyListener(new IFacetedProjectListener() {
            public void handleEvent(final IFacetedProjectEvent event) {
                handleModelChangedEvent(event);
            }
        }, IFacetedProjectEvent.Type.FIXED_FACETS_CHANGED, IFacetedProjectEvent.Type.SELECTED_PRESET_CHANGED,
                IFacetedProjectEvent.Type.TARGETED_RUNTIMES_CHANGED);

        // Set the preferred dimensions of the panel.

        final int prefWidthTree = getPreferredWidth(this.tree);
        int prefWidthRuntimesPanel = getPreferredWidth(this.runtimesPanel);
        prefWidthRuntimesPanel = max(prefWidthRuntimesPanel, 300);
        final int prefWidth = prefWidthTree + prefWidthRuntimesPanel + 80;

        this.topComposite.setLayoutData(gdwhint(gdhhint(gdfill(), 500), prefWidth));

        this.sform1.setWeights(new int[] { 70, 30 });
        this.sform2.setWeights(new int[] { prefWidthTree, prefWidthRuntimesPanel });

        // Select the first item in the table.

        if (this.tree.getItemCount() > 0) {
            final TreeItem firstItem = this.tree.getItem(0);
            this.treeViewer.setSelection(new StructuredSelection(firstItem.getData()));
        }

        handleSelectionChangedEvent();
    }

    public IFacetedProjectWorkingCopy getFacetedProjectWorkingCopy() {
        return this.fpjwc;
    }

    public boolean isSelectionValid() {
        return (this.fpjwc.validate().getSeverity() != IStatus.ERROR);
    }

    public boolean setFocus() {
        return this.tree.setFocus();
    }

    public void addSelectionChangedListener(final ISelectionChangedListener listener) {
        this.selectionListeners.add(listener);
    }

    public void removeSelectionChangedListener(final ISelectionChangedListener listener) {
        this.selectionListeners.remove(listener);
    }

    public ISelection getSelection() {
        if (this.selection != null) {
            return new StructuredSelection(this.selection);
        } else {
            return new StructuredSelection(new Object[0]);
        }
    }

    public void setSelection(final ISelection selection) {
        throw new UnsupportedOperationException();
    }

    private void notifySelectionChangedListeners() {
        final SelectionChangedEvent event = new SelectionChangedEvent(this, getSelection());

        for (ISelectionChangedListener listener : this.selectionListeners) {
            listener.selectionChanged(event);
        }
    }

    private ImageRegistry getImageRegistry() {
        return this.imageRegistry;
    }

    public Image getImage(final IProjectFacet facet, final boolean showDecorations) {
        final boolean isFixed = getFacetedProjectWorkingCopy().isFixedProjectFacet(facet);
        final String id = (isFixed && showDecorations ? "F:" : "f:") + facet.getId(); //$NON-NLS-1$ //$NON-NLS-2$

        Image image = getImageRegistry().get(id);

        if (image == null) {
            final IDecorationsProvider decprov = (IDecorationsProvider) facet
                    .getAdapter(IDecorationsProvider.class);

            ImageDescriptor imgdesc = decprov.getIcon();

            if (isFixed && showDecorations) {
                imgdesc = new FixedFacetImageDescriptor(imgdesc);
            }

            getImageRegistry().put(id, imgdesc);
            image = getImageRegistry().get(id);
        }

        return image;
    }

    public Image getImage(final ICategory category) {
        final String id = "c:" + category.getId(); //$NON-NLS-1$

        Image image = getImageRegistry().get(id);

        if (image == null) {
            final IDecorationsProvider decprov = (IDecorationsProvider) category
                    .getAdapter(IDecorationsProvider.class);

            final ImageDescriptor imgdesc = decprov.getIcon();

            getImageRegistry().put(id, imgdesc);
            image = getImageRegistry().get(id);
        }

        return image;

    }

    private void refresh() {
        // Somehow the checked state of nested items gets lost when a refresh
        // is performed, so we have to do this workaround.

        final Object[] checked = this.treeViewer.getCheckedElements();
        this.treeViewer.refresh();
        this.treeViewer.setCheckedElements(checked);
    }

    public void setCategoryExpandedState(final ICategory category, final boolean expanded) {
        this.treeViewer.setExpandedState(category, expanded);
    }

    public boolean getShowToolTips() {
        return this.showToolTips;
    }

    public void setShowToolTips(final boolean showToolTips) {
        this.showToolTips = showToolTips;
    }

    private void refreshCategoryState(final ICategory category) {
        if (category == null) {
            return;
        }

        int available = 0;
        int selected = 0;

        for (IProjectFacet f : category.getProjectFacets()) {
            if (this.fpjwc.isFacetAvailable(f)) {
                available++;
            }

            if (this.fpjwc.hasProjectFacet(f)) {
                selected++;
            }
        }

        if (selected == 0) {
            this.treeViewer.setChecked(category, false);
            this.treeViewer.setGrayed(category, false);
        } else if (selected == available) {
            this.treeViewer.setChecked(category, true);
            this.treeViewer.setGrayed(category, false);
        } else {
            this.treeViewer.setGrayChecked(category, true);
        }
    }

    private void addWorkingCopyListener(final IFacetedProjectListener listener,
            final IFacetedProjectEvent.Type... types) {
        this.fpjwc.addListener(listener, types);
        this.registeredWorkingCopyListeners.add(listener);
    }

    public IProjectFacet getSelectedProjectFacet() {
        final IProjectFacetVersion fv = getSelectedProjectFacetVersion();

        if (fv != null) {
            return fv.getProjectFacet();
        }

        return null;
    }

    public IProjectFacetVersion getSelectedProjectFacetVersion() {
        if (this.selection != null && this.selection instanceof IProjectFacetVersion) {
            return (IProjectFacetVersion) this.selection;
        }

        return null;
    }

    private IProjectFacetVersion getSelectedVersion(final IProjectFacet f) {
        final Set<IProjectFacetVersion> availableVersions = this.fpjwc.getAvailableVersions(f);

        if (availableVersions.isEmpty()) {
            throw new IllegalStateException();
        }

        IProjectFacetVersion selectedVersion = this.fpjwc.getProjectFacetVersion(f);

        if (selectedVersion == null) {
            selectedVersion = this.selectedVersions.get(f);

            if (selectedVersion == null) {
                selectedVersion = f.getDefaultVersion();
            }

            if (!availableVersions.contains(selectedVersion)) {
                selectedVersion = this.fpjwc.getHighestAvailableVersion(f);
            }
        }

        this.selectedVersions.put(f, selectedVersion);

        return selectedVersion;
    }

    private void setSelectedVersion(final IProjectFacet f, final IProjectFacetVersion fv) {
        if (this.fpjwc.getProjectFacetVersion(f) != null) {
            this.fpjwc.changeProjectFacetVersion(fv);
        }

        this.selectedVersions.put(f, fv);

        if (f == this.getSelectedProjectFacet()) {
            this.selection = fv;
            notifySelectionChangedListeners();
        }
    }

    private void handleSelectionChangedEvent() {
        Object selection = getCurrentSelection();

        if (selection != this.selection) {
            this.selection = selection;
            notifySelectionChangedListeners();
        }
    }

    private void handleCheckStateChanged(final CheckStateChangedEvent event) {
        final Object el = event.getElement();
        final boolean checked = event.getChecked();

        if (el instanceof IProjectFacet) {
            final IProjectFacet f = (IProjectFacet) el;

            if (this.fpjwc.getFixedProjectFacets().contains(f)) {
                if (!checked) {
                    this.treeViewer.setChecked(el, true);

                    final String msg = NLS.bind(Resources.couldNotDeselectFixedFacetMessage, f.getLabel());

                    this.fixedFacetToolTip.setMessage(msg);

                    final Point cursorLocation = getDisplay().getCursorLocation();
                    this.fixedFacetToolTip.show(this.tree.toControl(cursorLocation));
                }

                return;
            }

            if (checked) {
                this.fpjwc.addProjectFacet(getSelectedVersion(f));
            } else {
                this.fpjwc.removeProjectFacet(f);
            }

            refreshCategoryState(f.getCategory());
        } else {
            final ContentProvider cp = (ContentProvider) this.treeViewer.getContentProvider();

            final Set<IProjectFacetVersion> facets = new HashSet<IProjectFacetVersion>(
                    this.fpjwc.getProjectFacets());

            final Object[] children = cp.getChildren(el);
            int selected = 0;

            for (Object child : children) {
                final IProjectFacet f = (IProjectFacet) child;

                if (!this.fpjwc.getFixedProjectFacets().contains(f)) {
                    final IProjectFacetVersion fv = getSelectedVersion(f);

                    if (checked) {
                        facets.add(fv);
                    } else {
                        facets.remove(fv);
                    }

                    this.treeViewer.setChecked(f, checked);
                }

                if (this.fpjwc.hasProjectFacet(f)) {
                    selected++;
                }
            }

            if (selected == 0 || selected == children.length) {
                this.treeViewer.setGrayed(el, false);
            } else {
                this.treeViewer.setGrayChecked(el, true);
            }

            this.fpjwc.setProjectFacets(facets);
        }

        this.fpjwc.setSelectedPreset(null);
    }

    private void handleMouseDownEvent(final Event event) {
        handleMouseDownEventHelper(event, this.tree.getItems());
    }

    private boolean handleMouseDownEventHelper(final Event event, final TreeItem[] items) {
        for (TreeItem item : items) {
            if (item.getBounds(1).contains(event.x, event.y)) {
                final TreeItem[] newSelection = new TreeItem[] { item };

                if (!Arrays.equals(this.tree.getSelection(), newSelection)) {
                    this.tree.setSelection(new TreeItem[] { item });
                    this.treeViewer.editElement(item.getData(), 1);
                }

                return true;
            } else if (handleMouseDownEventHelper(event, item.getItems())) {
                return true;
            }
        }

        return false;
    }

    private void handlePaintItemEvent(final Event event) {
        final TreeItem item = (TreeItem) event.item;
        final Object itemData = item.getData();

        if (itemData instanceof IProjectFacet && event.index == 1) {
            final IProjectFacet f = (IProjectFacet) itemData;

            if (this.fpjwc.getAvailableVersions(f).size() > 1) {
                final Image arrowImage = getImageRegistry().get(IMG_DOWN_ARROW);
                final Rectangle arrowImageBounds = arrowImage.getBounds();

                final int columnWidth = this.colVersion.getColumn().getWidth();
                final int itemHeight = this.tree.getItemHeight();

                int x, y;

                x = event.x + columnWidth - arrowImageBounds.width - 10;
                y = event.y;
                event.gc.fillRectangle(x, y, arrowImageBounds.width + 10, itemHeight);

                y = event.y + (itemHeight - arrowImageBounds.height) / 2;
                event.gc.drawImage(arrowImage, x, y);
            }
        }
    }

    private void handleDisposeEvent() {
        this.imageRegistry.dispose();

        for (IFacetedProjectListener listener : this.registeredWorkingCopyListeners) {
            this.fpjwc.removeListener(listener);
        }

        RuntimeManager.removeListener(this.runtimeLifecycleListener);
    }

    private void handleSavePreset() {
        final Set<IProjectFacetVersion> facets = this.fpjwc.getProjectFacets();
        final IPreset preset = SavePresetDialog.showDialog(getShell(), facets);

        if (preset != null) {
            this.fpjwc.setSelectedPreset(preset.getId());
        }
    }

    private void handleDeletePreset() {
        final IPreset preset = this.fpjwc.getSelectedPreset();
        this.fpjwc.setSelectedPreset(null);
        ProjectFacetsManager.deletePreset(preset);
    }

    private void handleProjectFacetsChangedEvent(final IFacetedProjectEvent event) {
        if (!Thread.currentThread().equals(getDisplay().getThread())) {
            final Runnable uiRunnable = new Runnable() {
                public void run() {
                    handleProjectFacetsChangedEvent(event);
                }
            };

            getDisplay().asyncExec(uiRunnable);

            return;
        }

        final Set<ICategory> affectedCategories = new HashSet<ICategory>();

        if (event != null) {
            final IFacetedProjectWorkingCopy fpjwc = event.getWorkingCopy();

            final IProjectFacetsChangedEvent evt = (IProjectFacetsChangedEvent) event;

            for (IProjectFacetVersion fv : evt.getAllAffectedFacets()) {
                final IProjectFacet f = fv.getProjectFacet();
                final boolean checked = fpjwc.hasProjectFacet(fv);

                this.treeViewer.setChecked(f, checked);
                this.treeViewer.update(f, null);

                final ICategory category = f.getCategory();

                if (category != null) {
                    affectedCategories.add(category);
                }
            }
        } else {
            final List<IProjectFacet> facets = new ArrayList<IProjectFacet>();

            for (IProjectFacetVersion fv : this.fpjwc.getProjectFacets()) {
                facets.add(fv.getProjectFacet());
            }

            this.treeViewer.setCheckedElements(facets.toArray());

            for (IProjectFacet facet : this.fpjwc.getAvailableFacets().keySet()) {
                final ICategory category = facet.getCategory();

                if (category != null) {
                    affectedCategories.add(category);
                }
            }

            this.treeViewer.update(this.fpjwc.getAvailableFacets().keySet().toArray(), null);
        }

        for (ICategory category : affectedCategories) {
            refreshCategoryState(category);
        }
    }

    private void handleValidationProblemsChangedEvent() {
        if (!Thread.currentThread().equals(getDisplay().getThread())) {
            final Runnable uiRunnable = new Runnable() {
                public void run() {
                    handleValidationProblemsChangedEvent();
                }
            };

            getDisplay().asyncExec(uiRunnable);

            return;
        }

        this.problemsView.refresh();

        if (getFilteredProblems().length == 0) {
            if (this.sform1.getMaximizedControl() == null) {
                this.sform1.setMaximizedControl(this.sform2);
            }
        } else {
            if (this.sform1.getMaximizedControl() != null) {
                this.sform1.setMaximizedControl(null);
            }
        }
    }

    private void handleSelectedPresetChangedEvent() {
        final IPreset preset = this.fpjwc.getSelectedPreset();

        if (preset == null) {
            this.savePresetButton.setEnabled(true);
            this.deletePresetButton.setEnabled(false);
        } else {
            this.savePresetButton.setEnabled(false);
            this.deletePresetButton.setEnabled(preset.getType() == IPreset.Type.USER_DEFINED);
        }
    }

    private void handleModelChangedEvent(final IFacetedProjectEvent event) {
        switch (event.getType()) {
        case FIXED_FACETS_CHANGED:
        case TARGETED_RUNTIMES_CHANGED: {
            final Runnable runnable = new Runnable() {
                public void run() {
                    refresh();
                }
            };

            runOnDisplayThread(getDisplay(), runnable);

            break;
        }
        }
    }

    private void handleChangeVersionMenuSelected() {
        final IProjectFacet f = getSelectedProjectFacet();
        final IProjectFacetVersion fv = getSelectedVersion(f);
        final SortedSet<IProjectFacetVersion> versions = this.fpjwc.getAvailableVersions(f);

        final IProjectFacetVersion newVersion = ChangeFacetVersionDialog.showDialog(getShell(), f, fv, versions);

        if (newVersion != null) {
            this.fpjwc.changeProjectFacetVersion(newVersion);
        }
    }

    private void handleFacetLockUnlock() {
        final IProjectFacet f = getSelectedProjectFacet();

        final Set<IProjectFacet> fixedFacets = new HashSet<IProjectFacet>(this.fpjwc.getFixedProjectFacets());

        if (fixedFacets.contains(f)) {
            fixedFacets.remove(f);
        } else {
            fixedFacets.add(f);
        }

        this.fpjwc.setFixedProjectFacets(fixedFacets);
    }

    private void updatePopupMenu() {
        Object selection = getCurrentSelection();

        if (selection instanceof IProjectFacetVersion) {
            this.tree.setMenu(this.popupMenu);

            final IProjectFacet f = ((IProjectFacetVersion) selection).getProjectFacet();

            if (this.fpjwc.isFixedProjectFacet(f)) {
                this.popupMenuLockUnlock.setText(Resources.unlockMenuItem);
            } else {
                this.popupMenuLockUnlock.setText(Resources.lockMenuItem);
            }

            this.popupMenuLockUnlock.setEnabled(this.fpjwc.hasProjectFacet(f));

            final int numAvailableVersions = this.fpjwc.getAvailableVersions(f).size();
            this.popupMenuChangeVersion.setEnabled(numAvailableVersions > 1);
        } else {
            this.tree.setMenu(null);
        }
    }

    private Object getCurrentSelection() {
        Object selection = ((IStructuredSelection) this.treeViewer.getSelection()).getFirstElement();

        if (selection != null && selection instanceof IProjectFacet) {
            return getSelectedVersion((IProjectFacet) selection);
        }
        return null;
    }

    private TreeItem getTreeItem(final int x, final int y) {
        return getTreeItemHelper(x, y, this.tree.getItems());
    }

    private static TreeItem getTreeItemHelper(final int x, final int y, final TreeItem[] items) {
        for (TreeItem item : items) {
            if (item.getBounds().contains(x, y)) {
                return item;
            }

            final TreeItem res = getTreeItemHelper(x, y, item.getItems());

            if (res != null) {
                return res;
            }
        }

        return null;
    }

    private int computeDefaultFacetColumnWidth() {
        final GC gc = new GC(this.getDisplay());
        int maxFacetLabelWidth = 0;

        try {
            gc.setFont(this.tree.getFont());

            for (IProjectFacet f : ProjectFacetsManager.getProjectFacets()) {
                maxFacetLabelWidth = max(maxFacetLabelWidth, gc.textExtent(f.getLabel()).x);
            }
        } finally {
            gc.dispose();
        }

        return max(maxFacetLabelWidth + 100, 200);
    }

    private int computeDefaultVersionColumnWidth() {
        final GC gc = new GC(this.getDisplay());
        int maxVersionStringWidth = 0;
        final int columnLabelWidth;

        try {
            gc.setFont(this.tree.getFont());

            for (IProjectFacet f : ProjectFacetsManager.getProjectFacets()) {
                for (IProjectFacetVersion fv : f.getVersions()) {
                    final int textExtent = gc.textExtent(fv.getVersionString()).x;
                    maxVersionStringWidth = max(maxVersionStringWidth, textExtent + 30);
                }
            }

            columnLabelWidth = gc.textExtent(Resources.versionColumnLabel).x + 30;
        } finally {
            gc.dispose();
        }

        return max(maxVersionStringWidth, columnLabelWidth);
    }

    private IStatus[] getFilteredProblems() {
        final IStatus[] unfiltered = this.fpjwc.validate().getChildren();
        boolean somethingToRemove = false;

        for (IStatus st : unfiltered) {
            if (st.getCode() == IFacetedProjectWorkingCopy.PROBLEM_PROJECT_NAME) {
                somethingToRemove = true;
                break;
            }
        }

        if (!somethingToRemove) {
            return unfiltered;
        }

        final List<IStatus> filtered = new ArrayList<IStatus>();

        for (IStatus st : unfiltered) {
            if (st.getCode() != IFacetedProjectWorkingCopy.PROBLEM_PROJECT_NAME) {
                filtered.add(st);
            }
        }

        return filtered.toArray(new IStatus[filtered.size()]);
    }

    private final class ContentProvider

            implements ITreeContentProvider

    {
        public Object[] getElements(final Object element) {
            final IFacetedProjectWorkingCopy fpjwc = getFacetedProjectWorkingCopy();
            final List<Object> list = new ArrayList<Object>();
            final Set<ICategory> categories = ProjectFacetsManager.getCategories();

            for (ICategory cat : categories) {
                boolean visible = false;

                for (IProjectFacet f : cat.getProjectFacets()) {
                    if (!fpjwc.getAvailableVersions(f).isEmpty()) {
                        visible = true;
                        break;
                    }
                }

                if (visible) {
                    list.add(cat);
                }
            }

            for (Map.Entry<IProjectFacet, SortedSet<IProjectFacetVersion>> entry : fpjwc.getAvailableFacets()
                    .entrySet()) {
                final IProjectFacet f = entry.getKey();
                final SortedSet<IProjectFacetVersion> availableVersions = entry.getValue();

                if (f.getCategory() == null && !availableVersions.isEmpty()) {
                    list.add(f);
                }
            }

            return list.toArray();
        }

        public Object[] getChildren(final Object parent) {
            if (parent instanceof ICategory) {
                final IFacetedProjectWorkingCopy fpjwc = getFacetedProjectWorkingCopy();
                final ICategory category = (ICategory) parent;
                final List<IProjectFacet> facets = new ArrayList<IProjectFacet>();

                for (IProjectFacet f : category.getProjectFacets()) {
                    if (!fpjwc.getAvailableVersions(f).isEmpty()) {
                        facets.add(f);
                    }
                }

                return facets.toArray();
            } else {
                return new Object[0];
            }
        }

        public Object getParent(final Object element) {
            if (element instanceof IProjectFacet) {
                final IProjectFacet f = (IProjectFacet) element;
                return f.getCategory();
            } else {
                return null;
            }
        }

        public boolean hasChildren(final Object element) {
            return (element instanceof ICategory) && !((ICategory) element).getProjectFacets().isEmpty();
        }

        public void dispose() {
        }

        public void inputChanged(final Viewer viewer, final Object oldObject, final Object newObject) {
        }
    }

    private final class FacetColumnLabelProvider

            extends ColumnLabelProvider

    {
        @Override
        public String getText(final Object element) {
            if (element instanceof ICategory) {
                return ((ICategory) element).getLabel();
            } else {
                return ((IProjectFacet) element).getLabel();
            }
        }

        @Override
        public Image getImage(final Object element) {
            if (element instanceof IProjectFacet) {
                return FacetsSelectionPanel.this.getImage((IProjectFacet) element, true);
            } else {
                return FacetsSelectionPanel.this.getImage((ICategory) element);
            }
        }
    }

    private final class FacetVersionColumnLabelProvider

            extends ColumnLabelProvider

    {
        @Override
        public String getText(final Object element) {
            if (element instanceof IProjectFacet) {
                final ProjectFacet f = (ProjectFacet) element;

                if (!f.isVersionHidden()) {
                    return getSelectedVersion(f).getVersionString();
                }
            }

            return null;
        }
    }

    private final class FacetVersionColumnEditingSupport

            extends EditingSupport

    {
        private final ReadOnlyComboBoxCellEditor ceditor;
        private final IFacetedProjectWorkingCopy fpjwc;

        public FacetVersionColumnEditingSupport(final TreeViewer treeViewer) {
            super(treeViewer);
            this.ceditor = new ReadOnlyComboBoxCellEditor(treeViewer.getTree(), new String[0],
                    SWT.DROP_DOWN | SWT.READ_ONLY);
            this.fpjwc = getFacetedProjectWorkingCopy();
        }

        @Override
        public boolean canEdit(final Object element) {
            return element instanceof IProjectFacet
                    && this.fpjwc.getAvailableVersions((IProjectFacet) element).size() > 1;
        }

        @Override
        protected CellEditor getCellEditor(final Object element) {
            final IProjectFacet f = (IProjectFacet) element;

            if (f == null) {
                throw new IllegalStateException();
            }

            final SortedSet<IProjectFacetVersion> versions = this.fpjwc.getAvailableVersions(f);
            final String[] verstrs = new String[versions.size()];
            Integer value = null;

            int i = 0;

            for (IProjectFacetVersion fv : versions) {
                verstrs[i] = fv.getVersionString();

                if (fv == getSelectedVersion(f)) {
                    value = new Integer(i);
                }

                i++;
            }

            this.ceditor.setItems(verstrs);
            this.ceditor.setValue(value);

            return this.ceditor;
        }

        @Override
        public Object getValue(final Object element) {
            final IProjectFacet f = (IProjectFacet) element;
            int i = 0;

            for (IProjectFacetVersion fv : this.fpjwc.getAvailableVersions(f)) {
                if (fv == getSelectedVersion(f)) {
                    return new Integer(i);
                }

                i++;
            }

            return new IllegalStateException();
        }

        @Override
        public void setValue(final Object element, final Object value) {
            final IProjectFacet f = (IProjectFacet) element;
            final int index = ((Integer) value).intValue();

            if (index != -1) {
                int i = 0;

                for (IProjectFacetVersion fv : this.fpjwc.getAvailableVersions(f)) {
                    if (i == index) {
                        setSelectedVersion(f, fv);
                        FacetsSelectionPanel.this.treeViewer.update(f, null);

                        break;
                    }

                    i++;
                }
            }
        }
    }

    private static final class FixedFacetImageDescriptor

            extends CompositeImageDescriptor

    {
        private static final String OVERLAY_IMG_LOCATION = "images/lock.gif"; //$NON-NLS-1$

        private static final ImageData OVERLAY = FacetUiPlugin.getImageDescriptor(OVERLAY_IMG_LOCATION)
                .getImageData();

        private final ImageData base;
        private final Point size;

        public FixedFacetImageDescriptor(final ImageDescriptor base) {
            this.base = base.getImageData();
            this.size = new Point(this.base.width, this.base.height);
        }

        protected void drawCompositeImage(final int width, final int height) {
            drawImage(this.base, 0, 0);
            drawImage(OVERLAY, 0, height - OVERLAY.height);
        }

        protected Point getSize() {
            return this.size;
        }
    }

    private static final class Sorter

            extends ViewerSorter

    {
        public int compare(final Viewer viewer, final Object a, final Object b) {
            return getLabel(a).compareToIgnoreCase(getLabel(b));
        }

        private static String getLabel(final Object obj) {
            if (obj instanceof IProjectFacet) {
                return ((IProjectFacet) obj).getLabel();
            } else {
                return ((ICategory) obj).getLabel();
            }
        }
    }

    private final class FacetToolTip

            extends HeaderToolTip

    {
        public FacetToolTip(final Control control) {
            super(control);
        }

        @Override
        protected final boolean shouldCreateToolTip(final Event event) {
            if (getShowToolTips() == false) {
                return false;
            }

            final TreeItem treeItem = getTreeItem(event.x, event.y);
            String description = null;

            if (treeItem != null && treeItem.getBounds(0).contains(event.x, event.y)) {
                final Object treeItemData = treeItem.getData();

                if (treeItemData instanceof IProjectFacet) {
                    description = ((IProjectFacet) treeItemData).getDescription();
                }
            }

            return (description != null && description.trim().length() > 0);
        }

        @Override
        protected String getToolTipTitle(final Event event) {
            final IProjectFacet f = (IProjectFacet) getTreeItem(event.x, event.y).getData();
            return getSelectedVersion(f).toString();
        }

        @Override
        protected Composite createContentArea(final Event event, final Composite parent) {
            final Display display = parent.getDisplay();

            final Composite composite = new Composite(parent, SWT.NONE);
            composite.setLayout(gl(1));
            composite.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));

            final Label label = new Label(composite, SWT.WRAP);
            label.setLayoutData(gdfill());
            label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));

            final IProjectFacet f = (IProjectFacet) getTreeItem(event.x, event.y).getData();
            label.setText(f.getDescription());

            return composite;
        }
    }

    private final class CategoryToolTip

            extends HeaderToolTip

    {
        public CategoryToolTip(final Control control) {
            super(control);
        }

        @Override
        protected final boolean shouldCreateToolTip(final Event event) {
            if (getShowToolTips() == false) {
                return false;
            }

            final TreeItem treeItem = getTreeItem(event.x, event.y);
            String description = null;

            if (treeItem != null && treeItem.getBounds(0).contains(event.x, event.y)) {
                final Object treeItemData = treeItem.getData();

                if (treeItemData instanceof ICategory) {
                    description = ((ICategory) treeItemData).getDescription();
                }
            }

            return (description != null && description.trim().length() > 0);
        }

        @Override
        protected String getToolTipTitle(final Event event) {
            return ((ICategory) getTreeItem(event.x, event.y).getData()).getLabel();
        }

        @Override
        protected Composite createContentArea(final Event event, final Composite parent) {
            final Display display = parent.getDisplay();

            final Composite composite = new Composite(parent, SWT.NONE);
            composite.setLayout(gl(1));
            composite.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));

            final Label label = new Label(composite, SWT.WRAP);
            label.setLayoutData(gdfill());
            label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND));

            final ICategory category = (ICategory) getTreeItem(event.x, event.y).getData();
            label.setText(category.getDescription());

            return composite;
        }
    }

    private final class FixedFacetToolTip

            extends BasicToolTip

    {
        private static final int FAKE_EVENT_TYPE = -9999;

        public FixedFacetToolTip(final Control control) {
            super(control);
            setPopupDelay(0);
        }

        public void show(final Point location) {
            // The JFace ToolTip class does not support alternative methods of tool tip activation.
            // An enhancement request https://bugs.eclipse.org/bugs/show_bug.cgi?id=174844 tracks
            // this issue. When that enhancement request has been resolved, this hacky 
            // implementation should be replaced with something more sensible.

            final Event fakeEvent = new Event();
            fakeEvent.type = FAKE_EVENT_TYPE;
            fakeEvent.x = location.x;
            fakeEvent.y = location.y;

            try {
                final Method method = ToolTip.class.getDeclaredMethod("toolTipCreate", Event.class); //$NON-NLS-1$

                method.setAccessible(true);
                method.invoke(this, fakeEvent);
            } catch (Exception e) {
                FacetUiPlugin.log(e);
            }
        }

        @Override
        protected final boolean shouldCreateToolTip(final Event event) {
            return (event.type == FAKE_EVENT_TYPE);
        }
    }

    private final class ProblemsContentProvider

            implements IStructuredContentProvider

    {
        public Object[] getElements(final Object element) {
            return getFilteredProblems();
        }

        public void inputChanged(final Viewer viewer, final Object oldObject, final Object newObject) {
        }

        public void dispose() {
        }
    }

    private final class ProblemsLabelProvider

            implements ITableLabelProvider

    {
        public String getColumnText(final Object element, final int column) {
            return ((IStatus) element).getMessage();
        }

        public Image getColumnImage(final Object element, final int column) {
            final ISharedImages sharedImages = PlatformUI.getWorkbench().getSharedImages();
            final String imageType;

            if (((IStatus) element).getSeverity() == IStatus.ERROR) {
                imageType = ISharedImages.IMG_OBJS_ERROR_TSK;
            } else {
                imageType = ISharedImages.IMG_OBJS_WARN_TSK;
            }

            return sharedImages.getImage(imageType);
        }

        public boolean isLabelProperty(final Object obj, final String s) {
            return false;
        }

        public void dispose() {
        }

        public void addListener(final ILabelProviderListener listener) {
        }

        public void removeListener(ILabelProviderListener listener) {
        }
    }

    private static final class Resources

            extends NLS

    {
        public static String presetsLabel;
        public static String saveButtonLabel;
        public static String deleteButtonLabel;
        public static String facetColumnLabel;
        public static String versionColumnLabel;
        public static String couldNotDeselectFixedFacetMessage;
        public static String detailsTabLabel;
        public static String runtimesTabLabel;
        public static String lockMenuItem;
        public static String unlockMenuItem;
        public static String changeVersionMenuItem;

        static {
            initializeMessages(FacetsSelectionPanel.class.getName(), Resources.class);
        }
    }

}