/*******************************************************************************
* Copyright (c) 2012, 2013 Wind River Systems, Inc. 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:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.te.ui.views.internal.categories;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.expressions.IEvaluationContext;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionItem;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.graphics.Image;
import org.eclipse.tcf.te.ui.views.ViewsUtil;
import org.eclipse.tcf.te.ui.views.extensions.CategoriesExtensionPointManager;
import org.eclipse.tcf.te.ui.views.interfaces.ICategory;
import org.eclipse.tcf.te.ui.views.interfaces.IUIConstants;
import org.eclipse.tcf.te.ui.views.interfaces.categories.ICategorizable;
import org.eclipse.ui.ISources;
import org.eclipse.ui.actions.CompoundContributionItem;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.menus.IWorkbenchContribution;
import org.eclipse.ui.services.IServiceLocator;
/**
* Abstract categories dynamic menu contribution implementation.
*/
public abstract class AbstractCategoryContributionItem extends CompoundContributionItem implements IWorkbenchContribution {
// Service locator to located the handler service.
protected IServiceLocator serviceLocator;
/**
* Abstract category action implementation.
*/
protected abstract static class AbstractCategoryAction extends Action {
// The parent contribution item
private final AbstractCategoryContributionItem item;
// The selection
private final ISelection selection;
// The category
private final ICategory category;
/**
* Constructor.
*
* @param item The parent contribution item. Must not be <code>null</code>:
* @param selection The selection. Must not be <code>null</code>.
* @param category The category. Must not be <code>null</code>.
* @param single <code>True</code> if the action is the only item added, <code>false</code> otherwise.
*/
public AbstractCategoryAction(AbstractCategoryContributionItem item, ISelection selection, ICategory category, boolean single) {
super();
Assert.isNotNull(item);
this.item = item;
Assert.isNotNull(selection);
this.selection = selection;
Assert.isNotNull(category);
this.category = category;
initialize(single);
}
/**
* Initialize the action state.
*
* @param single <code>True</code> if the action is the only item added, <code>false</code> otherwise.
*/
protected void initialize(boolean single) {
setText(single ? makeSingleText(category.getLabel()) : category.getLabel());
Image image = category.getImage();
if (image != null) setImageDescriptor(ImageDescriptor.createFromImage(image));
}
/**
* Returns the action label in "single" mode.
*
* @param text The original label. Must not be <code>null</code>.
* @return The "single" mode label.
*/
protected String makeSingleText(String text) {
Assert.isNotNull(text);
return text;
}
/* (non-Javadoc)
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
boolean refresh = false;
Iterator<?> iterator = ((IStructuredSelection)selection).iterator();
while (iterator.hasNext()) {
Object element = iterator.next();
refresh |= execute(element, category);
}
// Refresh the view
if (refresh) ViewsUtil.refresh(IUIConstants.ID_EXPLORER);
}
}
/**
* Returns the categorizable for the given element.
*
* @param element The element or <code>null</code>.
* @return The categorizable or <code>null</code>.
*/
protected ICategorizable getCategorizable(Object element) {
return item.getCategorizable(element);
}
/**
* Executes the operation to do on the given element.
*
* @param selection The selection. Must not be <code>null</code>.
* @param category The category. Must not be <code>null</code>.
*
* @return <code>True</code> if the view needs refreshment, <code>false</code> otherwise.
*/
protected abstract boolean execute(Object element, ICategory category);
}
/* (non-Javadoc)
* @see org.eclipse.ui.menus.IWorkbenchContribution#initialize(org.eclipse.ui.services.IServiceLocator)
*/
@Override
public void initialize(IServiceLocator serviceLocator) {
this.serviceLocator = serviceLocator;
}
/* (non-Javadoc)
* @see org.eclipse.ui.actions.CompoundContributionItem#getContributionItems()
*/
@Override
protected IContributionItem[] getContributionItems() {
// Get the selected node.
IHandlerService service = (IHandlerService)serviceLocator.getService(IHandlerService.class);
IEvaluationContext state = service.getCurrentState();
ISelection selection = (ISelection)state.getVariable(ISources.ACTIVE_CURRENT_SELECTION_NAME);
IStructuredSelection iss = (IStructuredSelection)selection;
List<IContributionItem> items = new ArrayList<IContributionItem>();
ICategory[] categories = getCategories(iss, true);
// Generate the action contribution items
for (ICategory category : categories) {
IAction action = createAction(this, iss, category, categories.length == 1);
if (action != null) {
action.setEnabled(isEnabled(iss, category));
items.add(new ActionContributionItem(action));
}
}
return items.toArray(new IContributionItem[items.size()]);
}
/**
* Returns the list of valid categories for the given selection.
*
* @param selection The selection. Must not be <code>null</code>.
* @param onlyEnabled If <code>true</code>, returns categories which are valid and enabled only.
*
* @return The list of valid categories for the given selection, or an empty list.
*/
protected ICategory[] getCategories(IStructuredSelection selection, boolean onlyEnabled) {
Assert.isNotNull(selection);
List<ICategory> categories = new ArrayList<ICategory>();
ICategory[] allCategories = CategoriesExtensionPointManager.getInstance().getCategories(false);
// Analyze the selection and add categories valid for all items in the selection
boolean firstRun = true;
Iterator<?> iterator = selection.iterator();
while (iterator.hasNext()) {
Object element = iterator.next();
if (getCategorizable(element) == null) continue;
ICategory parentCategory = getParentCategory(element, selection);
List<ICategory> candidates = new ArrayList<ICategory>();
for (ICategory category : allCategories) {
if (!category.isEnabled()) continue;
if (isValid(parentCategory, element, category)
&& (!onlyEnabled || isEnabled(selection, category))) {
candidates.add(category);
}
}
// On first run, we remember the candidates as is
if (firstRun) {
categories.addAll(candidates);
firstRun = false;
} else {
// Eliminate all categories not being listed as candidate too
Iterator<ICategory> catIterator = categories.iterator();
while (catIterator.hasNext()) {
ICategory category = catIterator.next();
if (!candidates.contains(category)) {
catIterator.remove();
}
}
}
}
return categories.toArray(new ICategory[categories.size()]);
}
/**
* Creates the category action instance.
*
* @param item The parent contribution item. Must not be <code>null</code>:
* @param selection The selection. Must not be <code>null</code>.
* @param category The category. Must not be <code>null</code>.
* @param single <code>True</code> if the action is the only item added, <code>false</code> otherwise.
*
* @return The category action instance.
*/
protected abstract IAction createAction(AbstractCategoryContributionItem item, ISelection selection, ICategory category, boolean single);
/**
* Tests if the given combination is valid. If not valid, the combination
* will not be added to the menu.
*
* @param parentCategory The parent category or <code>null</code>.
* @param element The element. Must not be <code>null</code>.
* @param category The category. Must not be <code>null</code>.
*
* @return <code>True</code> if the given combination is valid, <code>false</code> otherwise.
*/
protected abstract boolean isValid(ICategory parentCategory, Object element, ICategory category);
/**
* Tests if the given combination is enabled.
*
* @param element The selection. Must not be <code>null</code>.
* @param category The category. Must not be <code>null</code>.
*
* @return <code>True</code> if the given combination is enabled, <code>false</code> otherwise.
*/
protected abstract boolean isEnabled(ISelection selection, ICategory category);
/**
* Determines the parent category for the given element, based on the
* given selection.
*
* @param element The element. Must not be <code>null</code>.
* @param selection The selection. Must not be <code>null</code>.
*
* @return The parent category or <code>null</code>.
*/
protected ICategory getParentCategory(Object element, IStructuredSelection selection) {
Assert.isNotNull(element);
Assert.isNotNull(selection);
ICategory parent = null;
if (selection instanceof ITreeSelection) {
TreePath[] pathes = ((ITreeSelection)selection).getPathsFor(element);
for (TreePath path : pathes) {
TreePath parentPath = path.getParentPath();
while (parentPath != null) {
if (parentPath.getLastSegment() instanceof ICategory) {
parent = (ICategory)parentPath.getLastSegment();
break;
}
parentPath = parentPath.getParentPath();
}
if (parent != null) break;
}
}
return parent;
}
/**
* Returns the categorizable for the given element.
*
* @param element The element or <code>null</code>.
* @return The categorizable or <code>null</code>.
*/
protected ICategorizable getCategorizable(Object element) {
ICategorizable categorizable = element instanceof IAdaptable ? (ICategorizable)((IAdaptable)element).getAdapter(ICategorizable.class) : null;
if (categorizable == null) categorizable = (ICategorizable)Platform.getAdapterManager().getAdapter(element, ICategorizable.class);
return categorizable;
}
}