| /******************************************************************************* |
| * |
| * Copyright (c) 2010, 2017 BestSolution.at 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: |
| * Tom Schindl <tom.schindl@bestsolution.at> - initial API and implementation |
| * Steven Spungin <steven@spungin.tv> - Bug 404136, Bug 424730, Bug 436281 |
| ******************************************************************************/ |
| package org.eclipse.e4.tools.emf.ui.internal.common.component.dialogs; |
| |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| |
| import org.eclipse.core.databinding.observable.list.IObservableList; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.resources.IResource; |
| import org.eclipse.core.resources.IResourceVisitor; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.e4.core.contexts.IEclipseContext; |
| import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.ContributionData; |
| import org.eclipse.e4.tools.emf.ui.common.IClassContributionProvider.Filter; |
| import org.eclipse.e4.tools.emf.ui.common.ResourceSearchScope; |
| import org.eclipse.e4.tools.emf.ui.internal.Messages; |
| import org.eclipse.e4.tools.emf.ui.internal.StringMatcher; |
| import org.eclipse.e4.tools.emf.ui.internal.common.ClassContributionCollector; |
| import org.eclipse.e4.tools.emf.ui.internal.common.component.tabs.empty.E; |
| import org.eclipse.e4.tools.emf.ui.internal.common.resourcelocator.TargetPlatformIconContributionCollector; |
| import org.eclipse.jface.viewers.ComboViewer; |
| import org.eclipse.jface.viewers.ISelectionChangedListener; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.SelectionChangedEvent; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.StyledCellLabelProvider; |
| import org.eclipse.jface.viewers.StyledString; |
| import org.eclipse.jface.viewers.TableViewerColumn; |
| import org.eclipse.jface.viewers.ViewerCell; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.events.DisposeEvent; |
| import org.eclipse.swt.events.DisposeListener; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.layout.GridData; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Control; |
| import org.eclipse.swt.widgets.Event; |
| import org.eclipse.swt.widgets.Label; |
| import org.eclipse.swt.widgets.Listener; |
| import org.eclipse.swt.widgets.Shell; |
| |
| /** |
| * A FilteredContributionDialog with additional options for icon resources. |
| * Features in include |
| * <ul> |
| * <li>Rebuilding the viewer when row height decreases. (Workaround for an SWT limitation) |
| * <li>Icon previews are displace in the first column. |
| * <li>Limited the maximum image size. |
| * </ul> |
| * |
| * @author "Steven Spungin" |
| * |
| * @see FilteredContributionDialog |
| */ |
| public abstract class AbstractIconDialogWithScopeAndFilter extends FilteredContributionDialog { |
| private final IProject project; |
| private final Map<IFile, Image> icons = Collections.synchronizedMap(new HashMap<IFile, Image>()); |
| |
| static public class Entry { |
| IFile file; |
| String installLocation; |
| } |
| |
| protected Integer maxDisplayedImageSize; |
| protected int maxImageHeight; |
| public static TargetPlatformIconContributionCollector collector; |
| |
| public AbstractIconDialogWithScopeAndFilter(Shell parentShell, IEclipseContext context) { |
| super(parentShell, context); |
| project = context.get(IProject.class); |
| } |
| |
| @Override |
| protected String getFilterTextMessage() { |
| return Messages.AbstractIconDialogWithScopeAndFilter_typeToStartSearch; |
| } |
| |
| @Override |
| protected String getResourceNameText() { |
| return Messages.AbstractIconDialogWithScopeAndFilter_iconName; |
| } |
| |
| @Override |
| protected ClassContributionCollector getCollector() { |
| if (collector == null) { |
| collector = TargetPlatformIconContributionCollector.getInstance(); |
| } |
| return collector; |
| } |
| |
| @Override |
| protected void createOptions(Composite compOptions) { |
| super.createOptions(compOptions); |
| |
| final Label lblMaxSize = new Label(compOptions, SWT.NONE); |
| lblMaxSize.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false)); |
| lblMaxSize.setText(Messages.AbstractIconDialogWithScopeAndFilter_maxDisplayedImageSize); |
| |
| final ComboViewer cv = new ComboViewer(compOptions); |
| cv.getCombo().setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1)); |
| cv.add(10); |
| cv.add(20); |
| cv.add(30); |
| cv.add(50); |
| cv.add(100); |
| cv.add(150); |
| cv.add(200); |
| maxDisplayedImageSize = 30; |
| cv.setSelection(new StructuredSelection(maxDisplayedImageSize)); |
| |
| cv.addSelectionChangedListener(new ISelectionChangedListener() { |
| |
| @Override |
| public void selectionChanged(SelectionChangedEvent event) { |
| maxDisplayedImageSize = (Integer) ((IStructuredSelection) cv.getSelection()).getFirstElement(); |
| rebuildViewer(); |
| refreshSearch(); |
| // combo viewer cannot make rows smaller, so we will need to |
| // rebuild it in that case. |
| } |
| }); |
| } |
| |
| @Override |
| protected void rebuildViewer() { |
| super.rebuildViewer(); |
| getViewer().getTable().setHeaderVisible(true); |
| |
| ((GridData) getViewer().getTable().getLayoutData()).minimumHeight = 100; |
| |
| final TableViewerColumn colIcon = new TableViewerColumn(getViewer(), SWT.NONE); |
| colIcon.getColumn().setText(Messages.AbstractIconDialogWithScopeAndFilter_icon); |
| final TableViewerColumn colText = new TableViewerColumn(getViewer(), SWT.NONE); |
| colText.getColumn().setText(Messages.AbstractIconDialogWithScopeAndFilter_details); |
| |
| // resize the row height using a MeasureItem listener |
| getViewer().getTable().addListener(SWT.MeasureItem, new Listener() { |
| @Override |
| public void handleEvent(Event event) { |
| // height cannot be per row so simply set |
| event.height = maxDisplayedImageSize; |
| } |
| }); |
| |
| colIcon.setLabelProvider(new StyledCellLabelProvider() { |
| @Override |
| public void update(ViewerCell cell) { |
| IFile file; |
| if (cell.getElement() instanceof ContributionData) { |
| file = new ContributionDataFile((ContributionData) cell.getElement()); |
| } else if (cell.getElement() instanceof Entry) { |
| file = ((Entry) cell.getElement()).file; |
| } else { |
| file = (IFile) cell.getElement(); |
| } |
| |
| Image img = icons.get(file); |
| if (img == null) { |
| try (InputStream in = file.getContents()) { |
| img = new Image(cell.getControl().getDisplay(), in); |
| icons.put(file, img); |
| } catch (final Exception e) { |
| // e.printStackTrace(); |
| return; |
| } |
| } |
| |
| // scale image if larger then max height |
| // also remember max width for column resizing |
| |
| double scale1 = (double) maxDisplayedImageSize / img.getImageData().height; |
| final double scale2 = (double) maxDisplayedImageSize / img.getImageData().width; |
| if (scale2 < scale1) { |
| scale1 = scale2; |
| } |
| if (scale1 < 1) { |
| int width = (int) (img.getImageData().width * scale1); |
| if (width == 0) { |
| width = 1; |
| } |
| int height = (int) (img.getImageData().height * scale1); |
| if (height == 0) { |
| height = 1; |
| } |
| final Image img2 = new Image(img.getDevice(), img.getImageData().scaledTo(width, height)); |
| img.dispose(); |
| img = img2; |
| icons.put(file, img); |
| } |
| final int width = AbstractIconDialogWithScopeAndFilter.this.getViewer().getTable().getColumn(0) |
| .getWidth(); |
| if (img.getImageData().width > width) { |
| AbstractIconDialogWithScopeAndFilter.this.getViewer().getTable().getColumn(0) |
| .setWidth(img.getImageData().width); |
| } |
| final int height = img.getImageData().height; |
| if (height > maxImageHeight) { |
| maxImageHeight = height; |
| } |
| |
| cell.setImage(img); |
| } |
| }); |
| colIcon.getColumn().setWidth(30); |
| |
| colText.setLabelProvider(new StyledCellLabelProvider() { |
| @Override |
| public void update(ViewerCell cell) { |
| IFile file; |
| String installLocation = null; |
| if (cell.getElement() instanceof ContributionData) { |
| final ContributionData contributionData = (ContributionData) cell.getElement(); |
| file = new ContributionDataFile(contributionData); |
| installLocation = contributionData.installLocation; |
| } else if (cell.getElement() instanceof Entry) { |
| file = ((Entry) cell.getElement()).file; |
| installLocation = ((Entry) cell.getElement()).installLocation; |
| } else { |
| file = (IFile) cell.getElement(); |
| } |
| final StyledString styledString = new StyledString(file.getProjectRelativePath().toString(), null); |
| final String bundle = FilteredContributionDialog.getBundle(file); |
| if (bundle != null) { |
| styledString.append(" - " + bundle, StyledString.DECORATIONS_STYLER); //$NON-NLS-1$ |
| } else if (installLocation != null) { |
| styledString.append(" - " + installLocation, StyledString.DECORATIONS_STYLER); //$NON-NLS-1$ |
| } |
| |
| cell.setText(styledString.getString()); |
| cell.setStyleRanges(styledString.getStyleRanges()); |
| } |
| }); |
| colText.getColumn().setWidth(400); |
| |
| getShell().addDisposeListener(new DisposeListener() { |
| |
| @Override |
| public void widgetDisposed(DisposeEvent e) { |
| clearImages(); |
| } |
| }); |
| } |
| |
| @Override |
| protected Control createDialogArea(Composite parent) { |
| final Composite comp = (Composite) super.createDialogArea(parent); |
| maxDisplayedImageSize = 20; |
| rebuildViewer(); |
| |
| return comp; |
| } |
| |
| private IconMatchCallback callback; |
| private String returnValue; |
| private SearchThread task; |
| |
| @Override |
| protected boolean doSearch() { |
| |
| // if (getSearchScopes().contains(SearchScope.TARGET_PLATFORM) || |
| // getSearchScopes().contains(SearchScope.WORKSPACE)) { |
| if (getSearchScopes().contains(ResourceSearchScope.TARGET_PLATFORM)) { |
| return false; |
| } |
| final Timer timer = new Timer(true); |
| |
| if (callback != null) { |
| callback.cancel = true; |
| } |
| if (task != null) { |
| task.cancel(); |
| } |
| task = null; |
| |
| clearImages(); |
| |
| callback = new IconMatchCallback((IObservableList) getViewer().getInput()); |
| final Filter filter = new Filter(project, getFilterTextBox().getText()); |
| filter.setSearchScope(getSearchScopes()); |
| filter.setBundles(getFilterBundles()); |
| filter.setLocations(getFilterLocations()); |
| filter.setPackages(getFilterPackages()); |
| filter.setIncludeNonBundles(includeNonBundles); |
| task = new SearchThread(callback, filter); |
| timer.schedule(task, 500); |
| // } |
| return true; |
| } |
| |
| private void clearImages() { |
| for (final Image img : icons.values()) { |
| img.dispose(); |
| } |
| icons.clear(); |
| } |
| |
| @Override |
| protected void okPressed() { |
| returnValue = null; |
| IFile file = getSelectedIfile(); |
| if (file != null) { |
| String installLocation = null; |
| if (file instanceof ContributionDataFile) { |
| final ContributionDataFile cdf = (ContributionDataFile) file; |
| installLocation = cdf.getContributionData().installLocation; |
| |
| } |
| file = checkResourceAccessible(file, installLocation); |
| if (file != null) { |
| final String bundle = getBundle(file); |
| String uri; |
| uri = "platform:/plugin/" + bundle + "/" + file.getProjectRelativePath().toString(); //$NON-NLS-1$ //$NON-NLS-2$ |
| returnValue = uri; |
| super.okPressed(); |
| } |
| } |
| } |
| |
| private class IconMatchCallback { |
| private volatile boolean cancel; |
| private final IObservableList list; |
| |
| private IconMatchCallback(IObservableList list) { |
| this.list = list; |
| } |
| |
| public void match(final IFile file, final String installLocation) { |
| if (!cancel) { |
| list.getRealm().exec(new Runnable() { |
| |
| @Override |
| public void run() { |
| final Entry entry = new Entry(); |
| entry.file = file; |
| entry.installLocation = installLocation; |
| list.add(entry); |
| } |
| }); |
| } |
| } |
| } |
| |
| private static class SearchThread extends TimerTask { |
| private final IconMatchCallback callback; |
| private final StringMatcher matcherGif; |
| private final StringMatcher matcherJpg; |
| private final StringMatcher matcherPng; |
| private final Filter filter; |
| private boolean includeNonBundles; |
| |
| public SearchThread(IconMatchCallback callback, Filter filter) { |
| matcherGif = new StringMatcher("*" + filter.namePattern + "*.gif", true, false); //$NON-NLS-1$//$NON-NLS-2$ |
| matcherJpg = new StringMatcher("*" + filter.namePattern + "*.jpg", true, false); //$NON-NLS-1$//$NON-NLS-2$ |
| matcherPng = new StringMatcher("*" + filter.namePattern + "*.png", true, false); //$NON-NLS-1$//$NON-NLS-2$ |
| this.callback = callback; |
| this.filter = filter; |
| } |
| |
| @Override |
| public void run() { |
| List<IProject> projects; |
| if (filter.getSearchScope().contains(ResourceSearchScope.TARGET_PLATFORM)) { |
| // never should be here because it is cached and not run as |
| // thread |
| return; |
| } else if (filter.getSearchScope().contains(ResourceSearchScope.WORKSPACE)) { |
| projects = Arrays.asList(filter.project.getWorkspace().getRoot().getProjects()); |
| } else if (filter.getSearchScope().contains(ResourceSearchScope.PROJECT)) { |
| if (filter.getSearchScope().contains(ResourceSearchScope.REFERENCES)) { |
| projects = new ArrayList<>(); |
| projects.add(filter.project); |
| try { |
| for (final IProject ref : filter.project.getReferencedProjects()) { |
| projects.add(ref); |
| } |
| } catch (final CoreException e) { |
| e.printStackTrace(); |
| } |
| } else { |
| projects = Arrays.asList(filter.project); |
| } |
| } else { |
| return; |
| } |
| |
| try { |
| for (final IProject project : projects) { |
| // Only search bundles unless requested |
| if (includeNonBundles == false && filter.project.getFile("/META-INF/MANIFEST.MF").exists() == false) { //$NON-NLS-1$ |
| continue; |
| } |
| project.accept(new IResourceVisitor() { |
| |
| @Override |
| public boolean visit(IResource resource) throws CoreException { |
| if (callback.cancel) { |
| return false; |
| } |
| |
| if (resource.getType() == IResource.FOLDER || resource.getType() == IResource.PROJECT) { |
| return true; |
| } else if (resource.getType() == IResource.FILE && !resource.isLinked()) { |
| final String path = resource.getProjectRelativePath().toString(); |
| if (matcherGif.match(path) || matcherPng.match(path) || matcherJpg.match(path)) { |
| if (E.notEmpty(filter.getPackages())) { |
| if (!filter.getPackages().contains( |
| resource.getProjectRelativePath().removeLastSegments(1).toOSString())) { |
| return false; |
| } |
| } |
| if (E.notEmpty(filter.getLocations())) { |
| if (!filter.getLocations().contains(project.getLocation().toOSString())) { |
| return false; |
| } |
| } |
| if (E.notEmpty(filter.getBundles())) { |
| final String bundle = getBundle(project); |
| if (bundle == null || !filter.getBundles().contains(bundle)) { |
| return false; |
| } |
| } |
| if (!filter.isIncludeNonBundles()) { |
| final String bundle = getBundle(project); |
| if (bundle == null) { |
| return false; |
| } |
| |
| } |
| callback.match((IFile) resource, project.getLocation().toOSString()); |
| } |
| } |
| return false; |
| } |
| |
| }); |
| } |
| } catch (final CoreException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| public String getValue() { |
| return returnValue; |
| } |
| } |