1 /******************************************************************************* 2 * Copyright (c) 2000, 2011 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 15 package org.eclipse.jdt.internal.ui.wizards.buildpaths.newsourcepage; 16 17 import java.util.ArrayList; 18 import java.util.List; 19 20 import org.eclipse.swt.SWT; 21 import org.eclipse.swt.events.DisposeEvent; 22 import org.eclipse.swt.events.DisposeListener; 23 import org.eclipse.swt.graphics.Color; 24 import org.eclipse.swt.graphics.Image; 25 import org.eclipse.swt.widgets.Composite; 26 import org.eclipse.swt.widgets.Control; 27 import org.eclipse.swt.widgets.Display; 28 import org.eclipse.swt.widgets.Menu; 29 30 import org.eclipse.core.runtime.CoreException; 31 import org.eclipse.core.runtime.IProgressMonitor; 32 import org.eclipse.core.runtime.NullProgressMonitor; 33 34 import org.eclipse.core.resources.IFile; 35 import org.eclipse.core.resources.IFolder; 36 import org.eclipse.core.resources.IResource; 37 import org.eclipse.core.resources.IWorkspace; 38 import org.eclipse.core.resources.IWorkspaceRunnable; 39 import org.eclipse.core.resources.ResourcesPlugin; 40 41 import org.eclipse.jface.action.IMenuListener; 42 import org.eclipse.jface.action.IMenuManager; 43 import org.eclipse.jface.action.MenuManager; 44 import org.eclipse.jface.viewers.DoubleClickEvent; 45 import org.eclipse.jface.viewers.IDoubleClickListener; 46 import org.eclipse.jface.viewers.IPostSelectionProvider; 47 import org.eclipse.jface.viewers.ISelection; 48 import org.eclipse.jface.viewers.ISelectionChangedListener; 49 import org.eclipse.jface.viewers.IStructuredSelection; 50 import org.eclipse.jface.viewers.StructuredSelection; 51 import org.eclipse.jface.viewers.TreeViewer; 52 import org.eclipse.jface.viewers.Viewer; 53 54 import org.eclipse.ui.part.ISetSelectionTarget; 55 56 import org.eclipse.jdt.core.IClasspathEntry; 57 import org.eclipse.jdt.core.IJavaProject; 58 import org.eclipse.jdt.core.IPackageFragment; 59 import org.eclipse.jdt.core.IPackageFragmentRoot; 60 import org.eclipse.jdt.core.JavaModelException; 61 62 import org.eclipse.jdt.internal.corext.buildpath.ClasspathModifier; 63 import org.eclipse.jdt.internal.corext.util.Messages; 64 65 import org.eclipse.jdt.ui.JavaElementComparator; 66 import org.eclipse.jdt.ui.JavaElementLabels; 67 68 import org.eclipse.jdt.internal.ui.JavaPlugin; 69 import org.eclipse.jdt.internal.ui.filters.LibraryFilter; 70 import org.eclipse.jdt.internal.ui.filters.OutputFolderFilter; 71 import org.eclipse.jdt.internal.ui.packageview.PackageExplorerContentProvider; 72 import org.eclipse.jdt.internal.ui.packageview.PackageFragmentRootContainer; 73 import org.eclipse.jdt.internal.ui.viewsupport.AppearanceAwareLabelProvider; 74 import org.eclipse.jdt.internal.ui.viewsupport.DecoratingJavaLabelProvider; 75 import org.eclipse.jdt.internal.ui.viewsupport.JavaElementImageProvider; 76 import org.eclipse.jdt.internal.ui.wizards.NewWizardMessages; 77 import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListElement; 78 import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListElementAttribute; 79 import org.eclipse.jdt.internal.ui.wizards.buildpaths.CPListLabelProvider; 80 import org.eclipse.jdt.internal.ui.workingsets.WorkingSetModel; 81 82 /** 83 * A package explorer widget that can be used in dialogs. It uses its own 84 * content provider, label provider, element sorter and filter to display 85 * elements that are not shown usually in the package explorer of the 86 * workspace. 87 */ 88 public class DialogPackageExplorer implements IMenuListener, IPostSelectionProvider, ISetSelectionTarget { 89 /** 90 * A extended content provider for the package explorer which can additionally display 91 * an output folder item. 92 */ 93 private final class PackageContentProvider extends PackageExplorerContentProvider { PackageContentProvider()94 public PackageContentProvider() { 95 super(false); 96 } 97 98 /** 99 * Get the elements of the current project 100 * 101 * @param element the element to get the children from, will 102 * not be used, instead the project children are returned directly 103 * @return returns the children of the project 104 */ 105 @Override getElements(Object element)106 public Object[] getElements(Object element) { 107 if (fCurrJProject == null || !fCurrJProject.exists()) 108 return new Object[0]; 109 return new Object[] {fCurrJProject}; 110 } 111 112 /** 113 * Get the children of the current <code>element</code>. If the 114 * element is of type <code>IPackageFragmentRoot</code> and 115 * displaying the output folders is selected, then an icon for 116 * the output folder is created and displayed additionally. 117 * 118 * @param element the current element to get the children from 119 * @return an array of children 120 */ 121 @Override getChildren(Object element)122 public Object[] getChildren(Object element) { 123 Object[] children= super.getChildren(element); 124 if (((element instanceof IPackageFragmentRoot && !ClasspathModifier.isInExternalOrArchive((IPackageFragmentRoot) element)) || 125 (element instanceof IJavaProject && fCurrJProject.isOnClasspath(fCurrJProject))) && fShowOutputFolders) { 126 try { 127 IClasspathEntry entry; 128 if (element instanceof IPackageFragmentRoot) 129 entry= ((IPackageFragmentRoot) element).getRawClasspathEntry(); 130 else 131 entry= ClasspathModifier.getClasspathEntryFor(fCurrJProject.getPath(), fCurrJProject, IClasspathEntry.CPE_SOURCE); 132 CPListElement parent= CPListElement.createFromExisting(entry, fCurrJProject); 133 CPListElementAttribute outputFolder= new CPListElementAttribute(parent, CPListElement.OUTPUT, 134 parent.getAttribute(CPListElement.OUTPUT), true); 135 Object[] extendedChildren= new Object[children.length + 1]; 136 System.arraycopy(children, 0, extendedChildren, 1, children.length); 137 extendedChildren[0]= outputFolder; 138 return extendedChildren; 139 } catch (JavaModelException e) { 140 JavaPlugin.log(e); 141 } 142 return null; 143 } 144 else 145 return children; 146 } 147 } 148 149 /** 150 * A extended label provider for the package explorer which can additionally display 151 * an output folder item. 152 */ 153 private final class PackageLabelProvider extends AppearanceAwareLabelProvider { 154 private CPListLabelProvider outputFolderLabel; 155 PackageLabelProvider(long textFlags, int imageFlags)156 public PackageLabelProvider(long textFlags, int imageFlags) { 157 super(textFlags, imageFlags); 158 outputFolderLabel= new CPListLabelProvider(); 159 } 160 161 @Override getText(Object element)162 public String getText(Object element) { 163 if (element instanceof CPListElementAttribute) 164 return outputFolderLabel.getText(element); 165 String text= super.getText(element); 166 try { 167 if (element instanceof IPackageFragmentRoot) { 168 IPackageFragmentRoot root= (IPackageFragmentRoot)element; 169 if (root.exists() && ClasspathModifier.filtersSet(root)) { 170 IClasspathEntry entry= root.getRawClasspathEntry(); 171 int excluded= entry.getExclusionPatterns().length; 172 if (excluded == 1) 173 return Messages.format(NewWizardMessages.DialogPackageExplorer_LabelProvider_SingleExcluded, text); 174 else if (excluded > 1) 175 return Messages.format(NewWizardMessages.DialogPackageExplorer_LabelProvider_MultiExcluded, new Object[] {text, Integer.valueOf(excluded)}); 176 } 177 } 178 if (element instanceof IJavaProject) { 179 IJavaProject project= (IJavaProject)element; 180 if (project.exists() && project.isOnClasspath(project)) { 181 IPackageFragmentRoot root= project.findPackageFragmentRoot(project.getPath()); 182 if (ClasspathModifier.filtersSet(root)) { 183 IClasspathEntry entry= root.getRawClasspathEntry(); 184 int excluded= entry.getExclusionPatterns().length; 185 if (excluded == 1) 186 return Messages.format(NewWizardMessages.DialogPackageExplorer_LabelProvider_SingleExcluded, text); 187 else if (excluded > 1) 188 return Messages.format(NewWizardMessages.DialogPackageExplorer_LabelProvider_MultiExcluded, new Object[] {text, Integer.valueOf(excluded)}); 189 } 190 } 191 } 192 if (element instanceof IFile || element instanceof IFolder) { 193 IResource resource= (IResource)element; 194 if (resource.exists() && ClasspathModifier.isExcluded(resource, fCurrJProject)) 195 return Messages.format(NewWizardMessages.DialogPackageExplorer_LabelProvider_Excluded, text); 196 } 197 } catch (JavaModelException e) { 198 JavaPlugin.log(e); 199 } 200 return text; 201 } 202 203 @Override getForeground(Object element)204 public Color getForeground(Object element) { 205 try { 206 if (element instanceof IPackageFragmentRoot) { 207 IPackageFragmentRoot root= (IPackageFragmentRoot)element; 208 if (root.exists() && ClasspathModifier.filtersSet(root)) 209 return getBlueColor(); 210 } 211 if (element instanceof IJavaProject) { 212 IJavaProject project= (IJavaProject)element; 213 if (project.exists() && project.isOnClasspath(project)) { 214 IPackageFragmentRoot root= project.findPackageFragmentRoot(project.getPath()); 215 if (root != null && ClasspathModifier.filtersSet(root)) 216 return getBlueColor(); 217 } 218 } 219 if (element instanceof IFile || element instanceof IFolder) { 220 IResource resource= (IResource)element; 221 if (resource.exists() && ClasspathModifier.isExcluded(resource, fCurrJProject)) 222 return getBlueColor(); 223 } 224 } catch (JavaModelException e) { 225 JavaPlugin.log(e); 226 } 227 return null; 228 } 229 getBlueColor()230 private Color getBlueColor() { 231 return Display.getCurrent().getSystemColor(SWT.COLOR_BLUE); 232 } 233 234 @Override getImage(Object element)235 public Image getImage(Object element) { 236 if (element instanceof CPListElementAttribute) 237 return outputFolderLabel.getImage(element); 238 return super.getImage(element); 239 } 240 241 @Override dispose()242 public void dispose() { 243 outputFolderLabel.dispose(); 244 super.dispose(); 245 } 246 } 247 248 /** 249 * A extended element sorter for the package explorer which displays the output 250 * folder (if any) as first child of a source folder. The other java elements 251 * are sorted in the normal way. 252 */ 253 private final class ExtendedJavaElementSorter extends JavaElementComparator { ExtendedJavaElementSorter()254 public ExtendedJavaElementSorter() { 255 super(); 256 } 257 258 @Override compare(Viewer viewer, Object e1, Object e2)259 public int compare(Viewer viewer, Object e1, Object e2) { 260 if (e1 instanceof CPListElementAttribute) 261 return -1; 262 if (e2 instanceof CPListElementAttribute) 263 return 1; 264 return super.compare(viewer, e1, e2); 265 } 266 } 267 268 /** 269 * An extended filter for the package explorer which filters 270 * libraries, 271 * files named ".classpath" or ".project", 272 * the default package, and 273 * hidden folders. 274 */ 275 private final class PackageFilter extends LibraryFilter { 276 private OutputFolderFilter fOutputFolderFilter= new OutputFolderFilter(); 277 @Override select(Viewer viewer, Object parentElement, Object element)278 public boolean select(Viewer viewer, Object parentElement, Object element) { 279 try { 280 if (element instanceof IFile) { 281 IFile file= (IFile) element; 282 if (file.getName().equals(".classpath") || file.getName().equals(".project")) //$NON-NLS-1$//$NON-NLS-2$ 283 return false; 284 } else if (element instanceof IPackageFragmentRoot) { 285 IClasspathEntry cpe= ((IPackageFragmentRoot)element).getRawClasspathEntry(); 286 if (cpe == null || cpe.getEntryKind() == IClasspathEntry.CPE_CONTAINER || cpe.getEntryKind() == IClasspathEntry.CPE_LIBRARY || cpe.getEntryKind() == IClasspathEntry.CPE_VARIABLE) 287 return false; 288 } else if (element instanceof PackageFragmentRootContainer) { 289 return false; 290 } else if (element instanceof IPackageFragment) { 291 IPackageFragment fragment= (IPackageFragment)element; 292 if (fragment.isDefaultPackage() && !fragment.hasChildren()) 293 return false; 294 } else if (element instanceof IFolder) { 295 IFolder folder= (IFolder)element; 296 if (folder.getName().startsWith(".")) //$NON-NLS-1$ 297 return false; 298 } 299 } catch (JavaModelException e) { 300 JavaPlugin.log(e); 301 } 302 /*if (element instanceof IPackageFragmentRoot) { 303 IPackageFragmentRoot root= (IPackageFragmentRoot)element; 304 if (root.getElementName().endsWith(".jar") || root.getElementName().endsWith(".zip")) //$NON-NLS-1$ //$NON-NLS-2$ 305 return false; 306 }*/ 307 return /*super.select(viewer, parentElement, element) &&*/ fOutputFolderFilter.select(viewer, parentElement, element); 308 } 309 } 310 311 /** The tree showing the project like in the package explorer */ 312 private TreeViewer fPackageViewer; 313 /** The tree's context menu */ 314 private Menu fContextMenu; 315 /** The action group which is used to fill the context menu. The action group 316 * is also called if the selection on the tree changes */ 317 private DialogPackageExplorerActionGroup fActionGroup; 318 /** 319 * Flag to indicate whether output folders 320 * can be created or not. This is used to 321 * set the content correctly in the case 322 * that a IPackageFragmentRoot is selected. 323 * 324 * @see #showOutputFolders(boolean) 325 */ 326 private boolean fShowOutputFolders= false; 327 328 /** Stores the current selection in the tree 329 * @see #getSelection() 330 */ 331 private IStructuredSelection fCurrentSelection; 332 333 /** The current java project 334 * @see #setInput(IJavaProject) 335 */ 336 private IJavaProject fCurrJProject; 337 private PackageContentProvider fContentProvider; 338 DialogPackageExplorer()339 public DialogPackageExplorer() { 340 fActionGroup= null; 341 fCurrJProject= null; 342 fCurrentSelection= new StructuredSelection(); 343 } 344 createControl(Composite parent)345 public Control createControl(Composite parent) { 346 fPackageViewer= new TreeViewer(parent, SWT.MULTI); 347 fPackageViewer.setComparer(WorkingSetModel.COMPARER); 348 fPackageViewer.addFilter(new PackageFilter()); 349 fPackageViewer.setComparator(new ExtendedJavaElementSorter()); 350 fPackageViewer.addDoubleClickListener(new IDoubleClickListener() { 351 @Override 352 public void doubleClick(DoubleClickEvent event) { 353 Object element= ((IStructuredSelection)event.getSelection()).getFirstElement(); 354 if (fPackageViewer.isExpandable(element)) { 355 fPackageViewer.setExpandedState(element, !fPackageViewer.getExpandedState(element)); 356 } else { 357 if (element instanceof CPListElementAttribute) { 358 CPListElementAttribute attribute= (CPListElementAttribute)element; 359 if (attribute.getKey().equals(CPListElement.OUTPUT)) { 360 fActionGroup.getEditOutputFolderAction().run(); 361 } 362 } 363 } 364 } 365 }); 366 367 MenuManager menuMgr= new MenuManager("#PopupMenu"); //$NON-NLS-1$ 368 menuMgr.setRemoveAllWhenShown(true); 369 menuMgr.addMenuListener(this); 370 fContextMenu= menuMgr.createContextMenu(fPackageViewer.getTree()); 371 fPackageViewer.getTree().setMenu(fContextMenu); 372 parent.addDisposeListener(new DisposeListener() { 373 @Override 374 public void widgetDisposed(DisposeEvent e) { 375 fContextMenu.dispose(); 376 } 377 }); 378 379 return fPackageViewer.getControl(); 380 } 381 382 /** 383 * Sets the action group for the package explorer. 384 * The action group is necessary to populate the 385 * context menu with available actions. If no 386 * context menu is needed, then this method does not 387 * have to be called. 388 * 389 * Should only be called once. 390 * 391 * @param actionGroup the action group to be used for 392 * the context menu. 393 */ setActionGroup(final DialogPackageExplorerActionGroup actionGroup)394 public void setActionGroup(final DialogPackageExplorerActionGroup actionGroup) { 395 fActionGroup= actionGroup; 396 } 397 398 /** 399 * Populate the context menu with the necessary actions. 400 * 401 * @see org.eclipse.jface.action.IMenuListener#menuAboutToShow(org.eclipse.jface.action.IMenuManager) 402 */ 403 @Override menuAboutToShow(IMenuManager manager)404 public void menuAboutToShow(IMenuManager manager) { 405 if (fActionGroup == null) // no context menu 406 return; 407 JavaPlugin.createStandardGroups(manager); 408 fActionGroup.fillContextMenu(manager); 409 } 410 411 /** 412 * Set the content and label provider of the 413 * <code>fPackageViewer</code> 414 */ setContentProvider()415 public void setContentProvider() { 416 if (fContentProvider != null) { 417 fContentProvider.dispose(); 418 } 419 fContentProvider= new PackageContentProvider(); 420 fContentProvider.setIsFlatLayout(true); 421 PackageLabelProvider labelProvider= new PackageLabelProvider(AppearanceAwareLabelProvider.DEFAULT_TEXTFLAGS | JavaElementLabels.P_COMPRESSED, 422 AppearanceAwareLabelProvider.DEFAULT_IMAGEFLAGS | JavaElementImageProvider.SMALL_ICONS); 423 fPackageViewer.setContentProvider(fContentProvider); 424 fPackageViewer.setLabelProvider(new DecoratingJavaLabelProvider(labelProvider, false)); 425 } 426 427 /** 428 * Set the input for the package explorer. 429 * 430 * @param project the project to be displayed 431 */ setInput(IJavaProject project)432 public void setInput(IJavaProject project) { 433 IJavaProject oldProject= fCurrJProject; 434 fCurrJProject= project; 435 if (fContentProvider != null) 436 fContentProvider.inputChanged(fPackageViewer, oldProject, fCurrJProject); 437 fPackageViewer.setInput(new Object[0]); 438 439 List<IJavaProject> selectedElements= new ArrayList<>(); 440 selectedElements.add(fCurrJProject); 441 setSelection(selectedElements); 442 } 443 dispose()444 public void dispose() { 445 if (fContentProvider != null) { 446 fContentProvider.dispose(); 447 fContentProvider= null; 448 } 449 if (fActionGroup != null) { 450 fActionGroup.dispose(); 451 fActionGroup= null; 452 } 453 fPackageViewer= null; 454 } 455 456 /** 457 * Set the selection and focus to the list of elements 458 * @param elements the object to be selected and displayed 459 */ setSelection(final List<?> elements)460 public void setSelection(final List<?> elements) { 461 if (elements == null || elements.isEmpty()) 462 return; 463 try { 464 ResourcesPlugin.getWorkspace().run(new IWorkspaceRunnable() { 465 @Override 466 public void run(IProgressMonitor monitor) throws CoreException { 467 fPackageViewer.refresh(); 468 IStructuredSelection selection= new StructuredSelection(elements); 469 fPackageViewer.setSelection(selection, true); 470 fPackageViewer.getTree().setFocus(); 471 472 if (elements.size() == 1 && elements.get(0) instanceof IJavaProject) 473 fPackageViewer.expandToLevel(elements.get(0), 1); 474 } 475 }, ResourcesPlugin.getWorkspace().getRoot(), IWorkspace.AVOID_UPDATE, new NullProgressMonitor()); 476 } catch (CoreException e) { 477 JavaPlugin.log(e); 478 } 479 } 480 481 /** 482 * The current list of selected elements. The 483 * list may be empty if no element is selected. 484 * 485 * @return the current selection 486 */ 487 @Override getSelection()488 public ISelection getSelection() { 489 return fCurrentSelection; 490 } 491 492 /** 493 * Get the viewer's control 494 * 495 * @return the viewers control 496 */ getViewerControl()497 public Control getViewerControl() { 498 return fPackageViewer.getControl(); 499 } 500 501 /** 502 * Method that is called whenever setting of 503 * output folders is allowed or forbidden (for example 504 * on changing a checkbox with this setting): 505 * 506 * @param showOutputFolders <code>true</code> if output 507 * folders should be shown, <code>false</code> otherwise. 508 */ showOutputFolders(boolean showOutputFolders)509 public void showOutputFolders(boolean showOutputFolders) { 510 fShowOutputFolders= showOutputFolders; 511 fActionGroup.getEditOutputFolderAction().showOutputFolders(showOutputFolders); 512 fPackageViewer.refresh(); 513 } 514 515 @Override addSelectionChangedListener(ISelectionChangedListener listener)516 public void addSelectionChangedListener(ISelectionChangedListener listener) { 517 fPackageViewer.addSelectionChangedListener(listener); 518 } 519 520 @Override removeSelectionChangedListener(ISelectionChangedListener listener)521 public void removeSelectionChangedListener(ISelectionChangedListener listener) { 522 fPackageViewer.removeSelectionChangedListener(listener); 523 } 524 525 @Override setSelection(ISelection selection)526 public void setSelection(ISelection selection) { 527 setSelection(((StructuredSelection)selection).toList()); 528 } 529 530 @Override addPostSelectionChangedListener(ISelectionChangedListener listener)531 public void addPostSelectionChangedListener(ISelectionChangedListener listener) { 532 fPackageViewer.addPostSelectionChangedListener(listener); 533 } 534 535 @Override removePostSelectionChangedListener(ISelectionChangedListener listener)536 public void removePostSelectionChangedListener(ISelectionChangedListener listener) { 537 fPackageViewer.removePostSelectionChangedListener(listener); 538 } 539 540 @Override selectReveal(ISelection selection)541 public void selectReveal(ISelection selection) { 542 setSelection(selection); 543 } 544 } 545