/**
* This file Copyright (c) 2005-2010 Aptana, Inc. This program is
* dual-licensed under both the Aptana Public License and the GNU General
* Public license. You may elect to use one or the other of these licenses.
*
* This program is distributed in the hope that it will be useful, but
* AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or
* NONINFRINGEMENT. Redistribution, except as permitted by whichever of
* the GPL or APL you select, is prohibited.
*
* 1. For the GPL license (GPL), you can redistribute and/or modify this
* program under the terms of the GNU General Public License,
* Version 3, as published by the Free Software Foundation. You should
* have received a copy of the GNU General Public License, Version 3 along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Aptana provides a special exception to allow redistribution of this file
* with certain Eclipse Public Licensed code and certain additional terms
* pursuant to Section 7 of the GPL. You may view the exception and these
* terms on the web at http://www.aptana.com/legal/gpl/.
*
* 2. For the Aptana Public License (APL), this program and the
* accompanying materials are made available under the terms of the APL
* v1.0 which accompanies this distribution, and is available at
* http://www.aptana.com/legal/apl/.
*
* You may view the GPL, Aptana's exception and additional terms, and the
* APL in the file titled license.html at the root of the corresponding
* plugin containing this source file.
*
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.ide.installer.wizard;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.viewers.AbstractTreeViewer;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITableLabelProvider;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.ITreeViewerListener;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.viewers.TreeExpansionEvent;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
import org.eclipse.swt.widgets.TreeItem;
import org.eclipse.ui.dialogs.ContainerCheckedTreeViewer;
import com.aptana.ide.core.ui.update.PluginsImageRegistry;
import com.aptana.ide.update.manager.InstallerCategory;
import com.aptana.ide.update.manager.Plugin;
/**
* @author Michael Xia (mxia@aptana.com)
*/
public class PluginsTreeViewer implements ICheckStateListener
{
public static interface Listener
{
public void itemsChecked(int count);
}
private static final Color FOREGROUND_INSTALLED = Display.getDefault().getSystemColor(SWT.COLOR_GRAY);
private static final Color FOREGROUND_REGULAR = Display.getDefault().getSystemColor(SWT.COLOR_LIST_FOREGROUND);
/**
* The content provider for the plugin tree viewer.
*/
private static class ContentProvider implements ITreeContentProvider
{
public Object[] getChildren(Object parentElement)
{
if (parentElement instanceof PluginsTreeModel)
{
// the first level of the tree contains all the plugins with
// category "Platforms" and all the other categories
PluginsTreeModel model = (PluginsTreeModel) parentElement;
return model.getCategories();
}
if (parentElement instanceof PluginTreeNode)
{
PluginTreeNode node = (PluginTreeNode) parentElement;
return node.getChildren();
}
return null;
}
public Object getParent(Object element)
{
return null;
}
public boolean hasChildren(Object element)
{
return getChildren(element).length > 0;
}
public Object[] getElements(Object inputElement)
{
return getChildren(inputElement);
}
public void dispose()
{
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput)
{
}
}
/**
* The label provider for the plugin tree viewer.
*/
private class LabelProvider implements ITableLabelProvider
{
public Image getColumnImage(Object element, int columnIndex)
{
switch (columnIndex)
{
case 0:
Image image = null;
if (element instanceof PluginTreeNode)
{
Plugin plugin = getPlugin((PluginTreeNode) element);
if (plugin != null)
{
image = fImages.getImage((Plugin) plugin);
}
}
return image == null ? PluginsImageRegistry.getDefaultImage() : image;
}
return null;
}
public String getColumnText(Object element, int columnIndex)
{
switch (columnIndex)
{
case 0:
if (element instanceof PluginTreeNode)
{
PluginTreeNode node = (PluginTreeNode) element;
Object data = node.getData();
if (data == null)
{
return node.getID();
}
String name = data.toString();
if (node.isInstalled())
{
return name + " (installed)"; //$NON-NLS-1$
}
return name;
}
break;
case 1:
if (element instanceof PluginTreeNode)
{
Plugin plugin = getPlugin((PluginTreeNode) element);
if (plugin != null)
{
String description = plugin.getDescription();
if (description != null)
{
return description;
}
}
}
break;
}
return null;
}
public void addListener(ILabelProviderListener listener)
{
}
public void dispose()
{
}
public boolean isLabelProperty(Object element, String property)
{
return false;
}
public void removeListener(ILabelProviderListener listener)
{
}
}
/*
* Returns the Plugin instance attached to the given node.
*
* @param node A PluginTreeNode
* @return The Plugin, or <code>null</code> in case that the node data does not hold a Plugin
*/
private Plugin getPlugin(PluginTreeNode node) {
Object data = node.getData();
if (data != null && data instanceof Plugin) {
return (Plugin) data;
}
return null;
}
private class PluginsComparator extends ViewerComparator
{
public int category(Object element)
{
if (element instanceof PluginTreeNode)
{
PluginTreeNode node = (PluginTreeNode) element;
Object plugin = node.getData();
if (plugin != null)
{
if (plugin instanceof InstallerCategory)
{
return ((InstallerCategory) plugin).getSortWeight();
}
if (plugin instanceof Plugin)
{
return ((Plugin) plugin).getSortweight();
}
}
}
return super.category(element);
}
}
private ContainerCheckedTreeViewer fTreeViewer;
private PluginsTreeModel fModel;
private Object[] fCheckedElements;
private PluginsImageRegistry fImages;
private List<Listener> fListeners;
public PluginsTreeViewer(Composite parent)
{
fImages = new PluginsImageRegistry(parent.getDisplay());
fCheckedElements = new Object[0];
fListeners = new ArrayList<Listener>();
createContents(parent);
}
public void addListener(Listener listener)
{
fListeners.add(listener);
}
public void removeListener(Listener listener)
{
fListeners.remove(listener);
}
/**
* Disposes the viewer.
*/
public void dispose()
{
fModel.clear();
fListeners.clear();
}
/**
* Returns the main control of the viewer
*
* @return the main control
*/
public Control getControl()
{
return fTreeViewer.getControl();
}
/**
* Returns the list of plug-ins user selected to install.
*
* @return an array of plug-ins to install
*/
public Plugin[] getSelectedPlugins()
{
List<Plugin> plugins = new ArrayList<Plugin>();
Object[] elements = fTreeViewer.getCheckedElements();
PluginTreeNode node;
for (Object element : elements)
{
if (element instanceof PluginTreeNode)
{
node = (PluginTreeNode) element;
// filters out the ones that either are already installed or do
// not correspond to a plug-in node
if (!node.isInstalled() && node.getData() instanceof Plugin)
{
plugins.add((Plugin) node.getData());
}
}
}
return plugins.toArray(new Plugin[plugins.size()]);
}
/**
* Makes the specific categories expanded.
*
* @param categoryIDs the array of category ids
*/
public void setExpandedCategories(String[] categoryIDs)
{
if (categoryIDs == null || categoryIDs.length == 0)
{
return;
}
// finds the categories that need to be expanded
List<PluginTreeNode> categories = new ArrayList<PluginTreeNode>();
PluginTreeNode category;
for (String id : categoryIDs)
{
category = fModel.getCategory(id);
if (category != null)
{
categories.add(category);
}
}
fTreeViewer.setExpandedElements(categories.toArray(new PluginTreeNode[categories.size()]));
if (categories.size() > 0)
{
// auto-selects the first element
fTreeViewer.setSelection(new StructuredSelection(categories.get(0)));
}
}
/**
* @see org.eclipse.jface.viewers.ICheckStateListener#checkStateChanged(org.eclipse.jface.viewers.CheckStateChangedEvent)
*/
public void checkStateChanged(CheckStateChangedEvent event)
{
Object element = event.getElement();
if (element instanceof PluginTreeNode)
{
PluginTreeNode node = (PluginTreeNode) element;
if (node.isInstalled() || hasInstalledChildren(node))
{
// disables checking of the box for installed plug-ins and any
// node that contains them
fTreeViewer.setCheckedElements(fCheckedElements);
}
else
{
// stores only the list of non-grayed, checked elements
List<Object> checked = new ArrayList<Object>();
Object[] elements = fTreeViewer.getCheckedElements();
for (Object checkedElement : elements)
{
if (!fTreeViewer.getGrayed(checkedElement))
{
checked.add(checkedElement);
}
}
fCheckedElements = checked.toArray(new Object[checked.size()]);
fireItemsCheckedEvent();
}
}
}
private void createContents(Composite parent)
{
fTreeViewer = new ContainerCheckedTreeViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION | SWT.H_SCROLL
| SWT.V_SCROLL | SWT.BORDER);
Tree tree = fTreeViewer.getTree();
tree.setHeaderVisible(true);
fTreeViewer.setAutoExpandLevel(AbstractTreeViewer.ALL_LEVELS);
TreeColumn column = new TreeColumn(tree, SWT.LEFT);
column.setText("Plugin"); //$NON-NLS-1$
column.setWidth(350);
column = new TreeColumn(tree, SWT.LEFT);
column.setText("Description"); //$NON-NLS-1$
column.setWidth(350);
fTreeViewer.setContentProvider(new ContentProvider());
fTreeViewer.setLabelProvider(new LabelProvider());
fTreeViewer.setComparator(new PluginsComparator());
fTreeViewer.setInput(fModel = new PluginsTreeModel());
fTreeViewer.addCheckStateListener(this);
fTreeViewer.addTreeListener(new ITreeViewerListener()
{
public void treeCollapsed(TreeExpansionEvent event)
{
updateForeground();
}
public void treeExpanded(TreeExpansionEvent event)
{
updateForeground();
}
});
updateExpansionState();
updateForeground();
}
private void updateExpansionState()
{
fTreeViewer.setExpandedElements(fModel.getExpandedCategories());
}
private void updateForeground()
{
// SWT currently does not have an API to disable individual TreeItem, so
// have to modify the appearance manually
List<TreeItem> items = new ArrayList<TreeItem>();
getAllVisibleItems(items, fTreeViewer.getTree().getItems());
Object data;
PluginTreeNode node;
for (TreeItem item : items)
{
data = item.getData();
if (data != null && data instanceof PluginTreeNode)
{
node = (PluginTreeNode) data;
if (node.isInstalled())
{
item.setForeground(FOREGROUND_INSTALLED);
}
else
{
item.setForeground(FOREGROUND_REGULAR);
}
}
}
}
private void fireItemsCheckedEvent()
{
int count = fCheckedElements.length;
for (Listener listener : fListeners)
{
listener.itemsChecked(count);
}
}
private static void getAllVisibleItems(List<TreeItem> items, TreeItem[] topLevelItems)
{
for (TreeItem item : topLevelItems)
{
items.add(item);
getAllVisibleItems(items, item.getItems());
}
}
private static boolean hasInstalledChildren(PluginTreeNode node)
{
PluginTreeNode[] children = node.getChildren();
for (PluginTreeNode child : children)
{
if (child.isInstalled() || hasInstalledChildren(child))
{
return true;
}
}
return false;
}
}