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