sysperfana/perfinvestigator/com.nokia.carbide.cpp.pi.wizards/src/com/nokia/carbide/cpp/internal/pi/wizards/ui/util/PkgListTreeViewer.java
author Matti Laitinen <matti.t.laitinen@nokia.com>
Thu, 11 Feb 2010 15:32:31 +0200
changeset 2 b9ab3b238396
permissions -rw-r--r--
Initial version of Performance Investigator under EPL

/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of the License "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description: 
 *
 */

package com.nokia.carbide.cpp.internal.pi.wizards.ui.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.eclipse.core.resources.IProject;
import org.eclipse.jface.viewers.IContentProvider;
import org.eclipse.jface.viewers.IElementComparer;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Item;
import org.eclipse.swt.widgets.TreeItem;

import com.nokia.carbide.cdt.builder.CarbideBuilderPlugin;
import com.nokia.carbide.cdt.builder.project.ICarbideBuildConfiguration;
import com.nokia.carbide.cdt.builder.project.ICarbideProjectInfo;
import com.nokia.carbide.cdt.builder.project.ISISBuilderInfo;
import com.nokia.carbide.cpp.pi.util.GeneralMessages;

public class PkgListTreeViewer extends TreeViewer {
	PkgListTreeContentProvider contentProvider;
	
	private class PkgListProjectItem extends PkgListBaseCustomItem {

		PkgListProjectItem(PkgListTreeViewer myViewer, Object myElement,
				TreeItem myTreeItem) {
			super(myViewer, myElement, myTreeItem);
		}
	}
	
	private class PkgListBuildConfigurationItem extends PkgListBaseCustomItem {

		PkgListBuildConfigurationItem(PkgListTreeViewer myViewer,
				Object myElement, TreeItem myTreeItem) {
			super(myViewer, myElement, myTreeItem);
		}
	}
	
	private class PkgListFileItem extends PkgListBaseCustomItem {

		PkgListFileItem(PkgListTreeViewer myViewer, Object myElement,
				TreeItem myTreeItem) {
			super(myViewer, myElement, myTreeItem);
		}
	}
	
	private class PkgListTreeViewerComparer implements IElementComparer {

		public boolean equals(Object a, Object b) {
			// if they're both build configuration items, check that their packages and configurations match
			if (   PkgListTree.isBuildConfigurationItem(a)
				&& PkgListTree.isBuildConfigurationItem(b)) {
				ICarbideBuildConfiguration configA = (ICarbideBuildConfiguration)a;
				ICarbideBuildConfiguration configB = (ICarbideBuildConfiguration)b;
				return (   configA.getCarbideProject().getProject() == configB.getCarbideProject().getProject()
				        && configA.getDisplayString().equals(configB.getDisplayString()));
			}
			else if (PkgListTree.isFileItem(a) &&
				PkgListTree.isFileItem(b)) {
					IPkgEntry fileA = (IPkgEntry)a;
					IPkgEntry fileB = (IPkgEntry)b;
					if (fileA.getPkgFile().equals(fileB.getPkgFile()) &&
							fileA.getSdk().equals(fileB.getSdk())) {
						return true;
					}
					return false;
				}
			return a.equals(b);
		}

		public int hashCode(Object element) {
			return element.hashCode();
		}
		
	}

	public PkgListTreeViewer(Composite arg0, int arg1) {
		// Must use NO_RADIO_GROUP, otherwise SWT automatically handle unselect for only
		// one item, I haven't find a way to create new composite for each project node.
		// Plus we want want to handle that the not so normal interaction among 
		// project checkbox/config radio button manually
		super(arg0, arg1 | SWT.NO_RADIO_GROUP | SWT.V_SCROLL | SWT.MULTI);
		super.setComparer(new PkgListTreeViewerComparer());
	}
	
	@Override
	public void setContentProvider(IContentProvider provider) {
		super.setContentProvider(provider);
		if (provider instanceof PkgListTreeContentProvider) {
			contentProvider = (PkgListTreeContentProvider)provider;
		} else {
			GeneralMessages.showErrorMessage(Messages.getString("PkgListTreeViewer.invalid.content.provider")); //$NON-NLS-1$
		}
	}
	
	
	private Map<Object, PkgListBaseCustomItem> itemMap = new HashMap<Object, PkgListBaseCustomItem>();
	private int style = 0;

	@Override
	protected void inputChanged(Object input, Object oldInput) {
//		collapseAll();	// just highlight the selected on, setSelectedItems() takes care of expand
		super.inputChanged(input, oldInput);
	}
	
	public void initializeSelectedItems(TreePath[] paths) {
		for (TreePath path : paths) {
			reveal(path);
		}
		TreeSelection elementSelection = new TreeSelection(paths);
		setSelection(elementSelection, true);
	}

	public IPkgEntry[] getSelectedPkgs() {
		List<Object> selectionList = getSelectionFromWidget();
		ArrayList<IPkgEntry> selectedPkgItems = new ArrayList<IPkgEntry>();
		for (Object item : selectionList) {
			if (item instanceof IPkgEntry) {
				selectedPkgItems.add((IPkgEntry) item);
			}
		}
		return selectedPkgItems.toArray(new IPkgEntry[selectedPkgItems.size()]);
	}

	public IProject[] getSelectedProjects() {
		List<Object> selectionList = getSelectionFromWidget();
		ArrayList<IProject> selectedProjects = new ArrayList<IProject>();
		for (Object item : selectionList) {
			if (item instanceof IProject) {
				selectedProjects.add((IProject) item);
			}
		}
		return selectedProjects.toArray(new IProject[selectedProjects.size()]);
	}

	public ICarbideBuildConfiguration[] getSelectedConfigs() {
		List<Object> selectionList = getSelectionFromWidget();
		ArrayList<ICarbideBuildConfiguration> selectedConfigs = new ArrayList<ICarbideBuildConfiguration>();
		for (Object item : selectionList) {
			if (item instanceof ICarbideBuildConfiguration) {
				selectedConfigs.add((ICarbideBuildConfiguration) item);
			}
		}
		return selectedConfigs.toArray(new ICarbideBuildConfiguration[selectedConfigs.size()]);
	}
		
	/**
	 * Set additional style bits for radio button appearance.
	 * Most likely style added may be FLAT when in a non-3D view.
	 * @param style int
	 * @see SWT#FLAT
	 */
	public void setStyle(int style) {
		this.style  = style;
	}
		
	/**
	 * We override associate to maintain each items optional
	 * radio button information.
	 */
	@Override
	protected void associate(final Object element, Item item) {
		super.associate(element, item);
		if (!(item instanceof TreeItem)) {
			return;
		}
		
		TreeItem treeItem = (TreeItem)item;
		
		PkgListBaseCustomItem baseItem = itemMap.get(getItemMapKey(element));
		if (baseItem == null) {
			int buttonStyle = style;
			SelectionAdapter selectionAdapter;
			boolean enabled = true;
			
			if (PkgListTree.isBuildConfigurationItem(element)) {
				baseItem = new PkgListBuildConfigurationItem(this, element, treeItem);
				buttonStyle = buttonStyle | SWT.RADIO;
				selectionAdapter = new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						toggleSelectionAndUpdateSelectionToViewer(element);
					}
				};
				// check for bad PKG, which may happen on regular Symbian stationary
				List<ISISBuilderInfo> sisBuilderInfoList = ((ICarbideBuildConfiguration)element).getSISBuilderInfoList();
				boolean allPkgGood = true && sisBuilderInfoList.size() > 0;
				for (ISISBuilderInfo sisBuilderInfo : sisBuilderInfoList)
				{
					if (sisBuilderInfo == null) {
						allPkgGood = false;
					} else if (sisBuilderInfo.getPKGFullPath().toFile().exists() == false) {
						allPkgGood = false;
					}
			
				}
				enabled = allPkgGood;
			} else if (PkgListTree.isProjectItem(element)) {
				baseItem = new PkgListProjectItem(this, element, treeItem);
				buttonStyle = buttonStyle | SWT.CHECK;
				selectionAdapter = new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						toggleSelectionAndUpdateSelectionToViewer(element);
					}						
				};
				ICarbideProjectInfo cpi = CarbideBuilderPlugin.getBuildManager().getProjectInfo((IProject)element);
				if (cpi != null) {
					enabled = false;
					List<ICarbideBuildConfiguration> bc = cpi.getBuildConfigurations();
					for (ICarbideBuildConfiguration config : bc) {
						// exclude configuration with (none) PKG
						List<ISISBuilderInfo> sisBuilderInfoList = ((ICarbideBuildConfiguration)config).getSISBuilderInfoList();
						for (ISISBuilderInfo sisBuilderInfo : sisBuilderInfoList)
						{
							if (sisBuilderInfo == null) {
								continue;
							} else if (sisBuilderInfo.getPKGFullPath().toFile().exists()) {
								enabled = true;
								break;
							}
					
						}					
					}
				}
			} else if (PkgListTree.isFileItem(element)) {
				baseItem = new PkgListFileItem(this, element, treeItem);
				buttonStyle = buttonStyle | SWT.CHECK;
				selectionAdapter = new SelectionAdapter() {
					@Override
					public void widgetSelected(SelectionEvent e) {
						toggleSelectionAndUpdateSelectionToViewer(element);
					}						
				};
			} else {
				return;	// not a custom item
			}

			baseItem.createButton(buttonStyle, selectionAdapter);
			baseItem.setEnabled(enabled);
			
			itemMap.put(getItemMapKey(element), baseItem);
		}
	}
	
	/**
	 * We override disassociate to clean up any
	 * allocated radio button information and to 
	 * help implement force selection.
	 */
	@Override
	protected void disassociate(Item item) {
		Object element = item.getData();
		if(PkgListTree.isBuildConfigurationItem(element) ||
				PkgListTree.isFileItem(element) ||
				PkgListTree.isProjectItem(element))
		{	
			PkgListBaseCustomItem baseItem = itemMap.get(getItemMapKey(element));
			if (baseItem != null) {
				if (baseItem instanceof PkgListBuildConfigurationItem ||
						baseItem instanceof PkgListProjectItem ||
						baseItem instanceof PkgListFileItem ) 
				{
					baseItem.dispose();
					itemMap.remove(getItemMapKey(element));
				}
				else {
//					shouldn't get here
				}
			}
		}
		super.disassociate(item);
	}
	
	// We override this underlying selection to insert UI handling of our custom
	// UI associated, can't use Listeners from TreeViewer to propagate because 
	// we are not SWT.CHECKBOX
	/*
	 * (non-Javadoc)
	 * @see org.eclipse.jface.viewers.AbstractTreeViewer#setSelectionToWidget(java.util.List, boolean)
	 */
	@Override
	protected void setSelectionToWidget(List l, boolean reveal) {
		super.setSelectionToWidget(l, reveal);
		// handle UI
		refreshSelectionOnUI();
	}
	
	public void refreshSelectionOnUI () {
		for (Iterator<Entry<Object, PkgListBaseCustomItem>> iter = itemMap.entrySet().iterator(); iter.hasNext();) {
			Entry<Object, PkgListBaseCustomItem> entry = iter.next();
			entry.getValue().setSelection(false);
		}
		List selectedList = getSelectionFromWidget();
		for (Object o : selectedList) {
			reveal(o);		// only expanded/visible items are available for selecting programatically
							// hidden items that never been expanded cannot
			PkgListBaseCustomItem plbci = itemMap.get(getItemMapKey(o));
			if(plbci != null) {
				plbci.setSelection(true);
			}
		}
	}
	
	private ArrayList<TreePath> buildArrayWithoutElement(TreePath[] selectedPaths, Object element) {
		ArrayList<TreePath> returnPath = new ArrayList<TreePath> ();
		for (TreePath path : selectedPaths) {
			boolean skip = false;
			for (int i = 0; i < path.getSegmentCount(); i++) {
				if (path.getSegment(i).equals(element)) {
					skip = true;
					break;
				}
			}
			if (skip == false) {
				returnPath.add(path);
			}
		}
		return returnPath;
	}
	
	public TreePath getTreePathForElement(Object element) {
		// go upward from element to root, then flip the list
		ArrayList<Object> segments = new ArrayList<Object>();
		Object current = element;
		
		do {
			segments.add(current);
			current = contentProvider.getParent(current);
		} while (current != null);
		
		Object[] reverse = segments.toArray(new Object[segments.size()]);
		Object[] inOrder = new Object[segments.size()];
		
		for (int i = 0; i < segments.size(); i++) {
			inOrder[i] = reverse[segments.size() - 1 - i ];
		}
		
		return new TreePath(inOrder);
	}
	
	/*
	 * Build and update the tree selection reflecting the element you want to toggle
	 */
	private void toggleSelectionAndUpdateSelectionToViewer(final Object element)
	{
		ArrayList<TreePath> myNewTreePath;
		ISelection iSelection = this.getSelection();
		ITreeSelection iTreeSelection;
		if (iSelection instanceof TreeSelection) {
			iTreeSelection = (TreeSelection)iSelection;
		} else {
			return;
		}
		TreePath[] selectedPaths = iTreeSelection.getPaths();

		if (PkgListTree.isFileItem(element)) {
			if (iTreeSelection.getPathsFor(element).length > 0) {
				// file already selected, clear self
				myNewTreePath = buildArrayWithoutElement(selectedPaths, element);
			} else {
				// file not yet selected, select self
				myNewTreePath = new ArrayList<TreePath>();
				for (TreePath path: selectedPaths) {
					myNewTreePath.add(path);
				}
				TreePath self = getTreePathForElement(element);
				if (self != null) {
					myNewTreePath.add(self);
				}
			}
		} else if (PkgListTree.isBuildConfigurationItem(element)) {
			Object projectSelection = contentProvider.getParent(element);
			myNewTreePath = buildArrayWithoutElement(selectedPaths, projectSelection);
			if (iTreeSelection.getPathsFor(element).length > 0) {
				// configuration already selected, clear self and parent project
				// do nothing, myNewTreePath already copied without parent project and self
			} else {
				// configuration not yet selected, clear siblings, select self
				TreePath self = getTreePathForElement(element);
				// myNewTreePath already copied without parent project and self
				if (self != null) {
					myNewTreePath.add(self);	// add self back
				}
				TreePath projectPath = getTreePathForElement(projectSelection);
				if (projectPath != null) {
					myNewTreePath.add(projectPath);	// add project back
				}
			}
		} else if (PkgListTree.isProjectItem(element)) {
			myNewTreePath = buildArrayWithoutElement(selectedPaths, element);
			if (iTreeSelection.getPathsFor(element).length > 0) {
				// project already selected, clear self and all children configurations
				// do nothing, myNewTreePath already copied without parent project and self
			} else {
				// project not yet selected, select self and a child configuration(default or next valid)
				TreePath self = getTreePathForElement(element);
				
				// so children are associated
				expandToLevel(self, 1);
				// I don't know why it didn't repaint proper without this
				collapseToLevel(self, 1);
				
				IProject project = (IProject) element;
				Object[] children = this.getRawChildren(project);
				
				ICarbideProjectInfo cpi = CarbideBuilderPlugin.getBuildManager().getProjectInfo(project);
				ICarbideBuildConfiguration configDefault = cpi.getDefaultConfiguration();
				ICarbideBuildConfiguration configDefaultOrFirstGood = null;
				
				// enabled default or first enabled child
				for (Object child : children) {
					if (itemMap.get(getItemMapKey(child)) == null) {
						continue;
					}
					if (itemMap.get(getItemMapKey(child)).getEnabled()) {
						if (configDefaultOrFirstGood == null || 
							((ICarbideBuildConfiguration)child).getDisplayString().equals(configDefault.getDisplayString())) {
							configDefaultOrFirstGood = (ICarbideBuildConfiguration)child;
						}
					}
				}
				if (configDefaultOrFirstGood  != null) {
					// myNewTreePath already copied without self and children
					if (self != null) {
						myNewTreePath.add(self);	// add self back
					}
					TreePath child = getTreePathForElement(configDefaultOrFirstGood);
					// myNewTreePath already copied without self and children
					if (child != null) {
						myNewTreePath.add(child);	// add child back
					}
				}
			}
		} else {
			myNewTreePath = new ArrayList<TreePath> ();
			for (TreePath path : selectedPaths) {
				myNewTreePath.add(path);
			}
		}

		// fire event
		//updateSelection(getSelection());
		
		// select from viewer, viewer will fire event and eventually change UI with our
		// overrode routine
		ITreeSelection treeSelection = new TreeSelection(myNewTreePath.toArray(new TreePath[myNewTreePath.size()]));
		setSelection(treeSelection, true);
	}
	
	public boolean getElementEnabled(Object element) {
		PkgListBaseCustomItem item = itemMap.get(getItemMapKey(element));
		if (item != null) {
			return item.getEnabled();
		}
		return false;
	}
	
	private Object getItemMapKey(Object element) {
		// because ICarbideBuildConfiguration says keys are equal even when their projects
		// do not match, use the project concatenated with configuration as the key for ICarbideBuildConfiguration
		if (element instanceof ICarbideBuildConfiguration) {
			ICarbideBuildConfiguration config = (ICarbideBuildConfiguration) element;
			return config.getCarbideProject().getProject() + "/" + config.getDisplayString();
		} else {
			return element;
		}
	}
}