/******************************************************************************* * Copyright (c) 2010 Sonatype, Inc. * 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: * Sonatype, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.m2e.core.ui.internal.components; import java.io.File; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.TreeMap; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.eclipse.core.filesystem.EFS; import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IWorkspaceRoot; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.CoreException; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.IDecoration; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Tree; import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.ui.internal.MavenImages; import org.eclipse.m2e.core.ui.internal.Messages; @SuppressWarnings({"restriction", "synthetic-access"}) public class NestedProjectsComposite extends Composite implements IMenuListener { private static final Logger log = LoggerFactory.getLogger(NestedProjectsComposite.class); private static final String SEPARATOR = System.getProperty("file.separator"); //$NON-NLS-1$ CheckboxTreeViewer codebaseViewer; Map<String, IProject> projectPaths; Collection<IProject> projects; IProject[] selectedProjects; private Link includeOutDateProjectslink; private Composite warningArea; private Button addOutOfDateBtn; private boolean showOutOfDateUI; public NestedProjectsComposite(Composite parent, int style, IProject[] initialSelection, boolean showOutOfDateWarning) { super(parent, style); this.showOutOfDateUI = showOutOfDateWarning; setLayout(new GridLayout(2, false)); Label lblAvailable = new Label(this, SWT.NONE); lblAvailable.setText(Messages.UpdateDepenciesDialog_availableCodebasesLabel); lblAvailable.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1)); codebaseViewer = new CheckboxTreeViewer(this, SWT.BORDER); codebaseViewer.setContentProvider(new ITreeContentProvider() { public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } public Object[] getElements(Object element) { if(element instanceof Collection) { return ((Collection<?>) element).toArray(); } return null; } public Object[] getChildren(Object parentElement) { if(parentElement instanceof IProject) { String elePath = getElePath(parentElement); String prevPath = null; List<IProject> children = new ArrayList<IProject>(); for(String path : projectPaths.keySet()) { if(path.length() != elePath.length() && path.startsWith(elePath)) { if(prevPath == null || !path.startsWith(prevPath)) { prevPath = path; children.add(getProject(path)); } } } return children.toArray(); } else if(parentElement instanceof Collection) { return ((Collection<?>) parentElement).toArray(); } return null; } public Object getParent(Object element) { String elePath = getElePath(element); String prevPath = null; for(String path : projectPaths.keySet()) { if(elePath.length() != path.length() && elePath.startsWith(path) && (prevPath == null || prevPath.length() < path.length())) { prevPath = path; } } return prevPath == null ? projects : getProject(prevPath); } public boolean hasChildren(Object element) { if(element instanceof IProject) { String elePath = getElePath(element); for(String path : projectPaths.keySet()) { if(elePath.length() != path.length() && path.startsWith(elePath)) { return true; } } } else if(element instanceof Collection) { return !((Collection<?>) element).isEmpty(); } return false; } }); codebaseViewer.setLabelProvider(new MavenProjectLabelProvider() { public Image getImage(Object element) { Image img = super.getImage(element); if(showOutOfDateUI && requiresUpdate((IProject) element)) { img = MavenImages.createOverlayImage(MavenImages.OOD_MVN_PROJECT, img, MavenImages.OUT_OF_DATE_OVERLAY, IDecoration.BOTTOM_RIGHT); } return img; } }); codebaseViewer.setComparator(new ViewerComparator()); projects = getMavenCodebases(); // prevent flicker codebaseViewer.getTree().setRedraw(false); try { codebaseViewer.setInput(projects); codebaseViewer.expandAll(); if(initialSelection != null) { // windowbuilder compat for(IProject project : initialSelection) { setSubtreeChecked(project, true); } // Reveal the first element if(initialSelection.length > 0) { codebaseViewer.reveal(initialSelection[0]); } } } finally { codebaseViewer.getTree().setRedraw(true); } Tree tree = codebaseViewer.getTree(); GridData gd = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); gd.heightHint = 300; gd.widthHint = 300; tree.setLayoutData(gd); GridLayout layout = new GridLayout(2, false); layout.marginLeft = 10; Composite selectionActionComposite = new Composite(this, SWT.NONE); selectionActionComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1)); GridLayout gl_selectionActionComposite = new GridLayout(1, false); gl_selectionActionComposite.marginWidth = 0; gl_selectionActionComposite.marginHeight = 0; selectionActionComposite.setLayout(gl_selectionActionComposite); createButtons(selectionActionComposite); createOutOfDateProjectsWarning(parent); createMenu(); codebaseViewer.addSelectionChangedListener(new ISelectionChangedListener() { public void selectionChanged(SelectionChangedEvent event) { updateSelectedProjects(); } }); updateSelectedProjects(); } private void createOutOfDateProjectsWarning(Composite composite) { if(!showOutOfDateUI) { return; } warningArea = new Composite(composite, SWT.NONE); warningArea.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 2, 1)); warningArea.setLayout(new RowLayout(SWT.HORIZONTAL)); Label warningImg = new Label(warningArea, SWT.NONE); warningImg.setImage(JFaceResources.getImage(Dialog.DLG_IMG_MESSAGE_WARNING)); includeOutDateProjectslink = new Link(warningArea, SWT.NONE); includeOutDateProjectslink.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { includeOutOfDateProjects(); } @Override public void widgetDefaultSelected(SelectionEvent e) { includeOutOfDateProjects(); } }); } private void updateIncludeOutDateProjectsLink(int outOfDateProjectsCount) { boolean visibility = true; String text = ""; //$NON-NLS-1$ String btnTooltip; if(outOfDateProjectsCount == 0) { visibility = false; btnTooltip = Messages.NestedProjectsComposite_OutOfDateProjectBtn_Generic_Tooltip; } else if(outOfDateProjectsCount > 1) { text = NLS.bind(Messages.NestedProjectsComposite_Multiple_OOD_Projects_Link, outOfDateProjectsCount); btnTooltip = NLS.bind(Messages.NestedProjectsComposite_OutOfDateProjectBtn_AddProjects_Tooltip, outOfDateProjectsCount); } else { text = Messages.NestedProjectsComposite_Single_OOD_Project_Link; btnTooltip = Messages.NestedProjectsComposite_OutOfDateProjectBtn_AddOneProject_Tooltip; } if(includeOutDateProjectslink != null && addOutOfDateBtn != null && warningArea != null) { includeOutDateProjectslink.setText(text); addOutOfDateBtn.setToolTipText(btnTooltip); warningArea.setVisible(visibility); warningArea.getParent().layout(new Control[] {warningArea}); } } private int computeOutOfDateProjectsCount() { int outOfDateProjectsCount = 0; for(IProject p : projectPaths.values()) { if(requiresUpdate(p) && !codebaseViewer.getChecked(p)) { outOfDateProjectsCount++ ; } } return outOfDateProjectsCount; } private void includeOutOfDateProjects() { for(IProject project : projectPaths.values()) { if(requiresUpdate(project)) { codebaseViewer.setChecked(project, true); } } updateSelectedProjects(); } private void updateSelectedProjects() { selectedProjects = internalGetSelectedProjects(); updateIncludeOutDateProjectsLink(computeOutOfDateProjectsCount()); } private void setSubtreeChecked(Object obj, boolean checked) { // CheckBoxTreeViewer#setSubtreeChecked is severely inefficient codebaseViewer.setChecked(obj, checked); Object[] children = ((ITreeContentProvider) codebaseViewer.getContentProvider()).getChildren(obj); if(children != null) { for(Object child : children) { setSubtreeChecked(child, checked); } } } protected void createButtons(Composite selectionActionComposite) { Button selectAllBtn = new Button(selectionActionComposite, SWT.NONE); selectAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); selectAllBtn.setText(Messages.UpdateDepenciesDialog_selectAll); selectAllBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { for(IProject project : projects) { setSubtreeChecked(project, true); } updateSelectedProjects(); } }); if(showOutOfDateUI) { addOutOfDateBtn = new Button(selectionActionComposite, SWT.NONE); addOutOfDateBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1)); addOutOfDateBtn.setText(Messages.NestedProjectsComposite_Add_OutOfDate); addOutOfDateBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { includeOutOfDateProjects(); } }); } Button deselectAllBtn = new Button(selectionActionComposite, SWT.NONE); deselectAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.TOP, false, false, 1, 1)); deselectAllBtn.setText(Messages.UpdateDepenciesDialog_deselectAll); deselectAllBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { codebaseViewer.setCheckedElements(new Object[0]); updateSelectedProjects(); } }); Button expandAllBtn = new Button(selectionActionComposite, SWT.NONE); expandAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1)); expandAllBtn.setText(Messages.UpdateDepenciesDialog_expandAll); expandAllBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { codebaseViewer.expandAll(); } }); Button collapseAllBtn = new Button(selectionActionComposite, SWT.NONE); collapseAllBtn.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, false, false, 1, 1)); collapseAllBtn.setText(Messages.UpdateDepenciesDialog_collapseAll); collapseAllBtn.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { codebaseViewer.collapseAll(); } }); } String getElePath(Object element) { if(element instanceof IProject) { IProject project = (IProject) element; URI locationURI = project.getLocationURI(); try { IFileStore store = EFS.getStore(locationURI); File file = store.toLocalFile(0, null); return file.toString() + SEPARATOR; } catch(CoreException ex) { log.error(ex.getMessage(), ex); } } return null; } IProject getProject(String path) { return projectPaths.get(path); } private Collection<IProject> getMavenCodebases() { projectPaths = new TreeMap<String, IProject>(); for(IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) { try { if(isInteresting(project)) { if(project.getLocationURI() != null) { String path = getElePath(project); if(path != null) { projectPaths.put(path, project); } } } } catch(CoreException ex) { log.error(ex.getMessage(), ex); } } if(projectPaths.isEmpty()) { return Collections.<IProject> emptyList(); } List<IProject> projects = new ArrayList<IProject>(); String previous = projectPaths.keySet().iterator().next(); addProject(projects, previous); for(String path : projectPaths.keySet()) { if(!path.startsWith(previous)) { previous = path; IProject project = getProject(path); if(project != null) { projects.add(project); } } } return projects; } protected boolean isInteresting(IProject project) throws CoreException { return project.isAccessible() && project.hasNature(IMavenConstants.NATURE_ID); } private static void addProject(Collection<IProject> projects, String location) { IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot(); for(IContainer container : root.findContainersForLocationURI(new File(location).toURI())) { if(container instanceof IProject) { projects.add((IProject) container); break; } } } private void createMenu() { MenuManager menuMgr = new MenuManager(); Menu contextMenu = menuMgr.createContextMenu(codebaseViewer.getControl()); menuMgr.addMenuListener(this); codebaseViewer.getControl().setMenu(contextMenu); menuMgr.setRemoveAllWhenShown(true); } public void menuAboutToShow(IMenuManager manager) { if(codebaseViewer.getSelection().isEmpty()) { return; } if(codebaseViewer.getSelection() instanceof IStructuredSelection) { manager.add(selectTree); manager.add(deselectTree); } } private final Action selectTree = new Action(Messages.UpdateDepenciesDialog_selectTree) { public void run() { setSubtreeChecked(getSelection(), true); updateSelectedProjects(); } }; private final Action deselectTree = new Action(Messages.UpdateDepenciesDialog_deselectTree) { public void run() { setSubtreeChecked(getSelection(), false); updateSelectedProjects(); } }; public IProject getSelection() { ISelection selection = codebaseViewer.getSelection(); if(selection instanceof IStructuredSelection) { return (IProject) ((IStructuredSelection) selection).getFirstElement(); } return null; } public IProject[] getSelectedProjects() { return selectedProjects; } IProject[] internalGetSelectedProjects() { Object[] obj = codebaseViewer.getCheckedElements(); IProject[] projects = new IProject[obj.length]; for(int i = 0; i < obj.length; i++ ) { projects[i] = (IProject) obj[i]; } return projects; } public void refresh() { projects = getMavenCodebases(); codebaseViewer.setInput(projects); codebaseViewer.expandAll(); } public void reset() { projects = getMavenCodebases(); codebaseViewer.setInput(projects); codebaseViewer.expandAll(); codebaseViewer.setCheckedElements(new Object[0]); updateSelectedProjects(); } public void addSelectionChangeListener(ISelectionChangedListener listener) { codebaseViewer.addSelectionChangedListener(listener); } //XXX probably move to a utility class private boolean requiresUpdate(IProject project) { try { IMarker[] markers = project.findMarkers(IMavenConstants.MARKER_CONFIGURATION_ID, true, IResource.DEPTH_ZERO); for(IMarker marker : markers) { String message = (String) marker.getAttribute(IMarker.MESSAGE); //XXX need a better way to identify these than rely on the marker message if(org.eclipse.m2e.core.internal.Messages.ProjectConfigurationUpdateRequired.equals(message)) { return true; } } } catch(CoreException ex) { log.error(ex.getMessage(), ex); } return false; } }