blob: f5c15217a223903180f2450819b9d1ddccb34691 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2017 Obeo.
* 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:
* Obeo - initial API and implementation
*******************************************************************************/
package org.eclipse.sirius.ui.tools.internal.viewpoint;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryLabelProvider;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.CheckboxTableViewer;
import org.eclipse.jface.viewers.DecorationOverlayIcon;
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.ITableLabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.TableLayout;
import org.eclipse.jface.viewers.TableViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.jface.viewers.ViewerComparator;
import org.eclipse.sirius.business.api.componentization.ViewpointRegistry;
import org.eclipse.sirius.business.api.query.IdentifiedElementQuery;
import org.eclipse.sirius.business.api.query.ViewpointQuery;
import org.eclipse.sirius.business.api.session.Session;
import org.eclipse.sirius.common.tools.api.util.StringUtil;
import org.eclipse.sirius.ui.tools.api.views.ViewHelper;
import org.eclipse.sirius.viewpoint.description.Viewpoint;
import org.eclipse.sirius.viewpoint.provider.Messages;
import org.eclipse.sirius.viewpoint.provider.SiriusEditPlugin;
import org.eclipse.swt.SWT;
import org.eclipse.swt.SWTError;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.Text;
import org.osgi.framework.Bundle;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Sets;
/**
* A graphic component providing a {@link CheckboxTableViewer} with a checkbox for each viewpoint activable/deactivable
* regarding the given session. Also provides a browser allowing to see viewpoint description when one has focus.
*
* @author <a href="mailto:pierre.guilet@obeo.fr">Pierre Guilet</a>
*
*/
public class ViewpointsSelectionGraphicalHandler {
/**
* The layout of the main composite of this graphic component.
*/
private GridLayout rootGridLayout;
/** The browser for documentation */
private Browser browser;
/**
* The composite enclosing all graphical parts of this component.
*/
private Composite rootComposite;
/**
* The grid data for the browser component displaying viewpoint information.
*/
private GridData browserGridData;
/**
* The viewer containing a checkbox for each viewpoint registered in the current runtime.
*/
private CheckboxTableViewer viewer;
/**
* The layout data of the root component.
*/
private GridData rootLayoutData;
private GridData viewerGridData;
/**
* LayoutData of the error message top part of the browser component.
*/
private GridData browserErrorMessageLayoutData;
/**
* The Text containing error message in the top part of the browser component.
*/
private Text browserErrorMessageText;
/**
* The composite containing the text containing error message in the top part of the browser component.
*/
private Composite browserErrorMessageComposite;
/**
* The root composite of the browser component.
*/
private Composite browserRootComposite;
/**
* Return the composite enclosing all graphical parts of this component.
*
* @return the composite enclosing all graphical parts of this component.
*/
public Composite getRootComposite() {
return rootComposite;
}
/**
* Return the browser providing descriptions for viewpoints taking focus.
*
* @return the browser providing descriptions for viewpoints taking focus.
*/
public Browser getBrowser() {
return browser;
}
/**
* Returns the root composite of the browser component.
*
* @return the root composite of the browser component.
*/
public Composite getBrowserRootComposite() {
return browserRootComposite;
}
/**
* Return all registered viewpoints that define editors for metamodel of loaded session's semantic models.
*
* @param session
* the session from which we retrieve available viewpoints.
* @return all registered viewpoints that define editors for metamodel of loaded session's semantic models.
*/
public Collection<Viewpoint> getAvailableViewpoints(Session session) {
ViewpointRegistry registry = ViewpointRegistry.getInstance();
return Collections2.filter(registry.getViewpoints(), new Predicate<Viewpoint>() {
@Override
public boolean apply(Viewpoint viewpoint) {
for (final String ext : computeSemanticFileExtensions(session)) {
if (new ViewpointQuery(viewpoint).handlesSemanticModelExtension(ext)) {
return true;
}
}
return false;
}
});
}
/**
* compute the semantic file extensions to restrict the choice of viewpoint based on the session.
*
* @param theSession
* the session
* @return a collection of file extension
*/
public Collection<String> computeSemanticFileExtensions(Session theSession) {
final Collection<String> extensions = new HashSet<String>();
for (final Resource resource : theSession.getSemanticResources()) {
if (resource != null && resource.getURI() != null) {
final String currentFileExtension = resource.getURI().fileExtension();
if (currentFileExtension != null) {
extensions.add(currentFileExtension);
}
}
}
return extensions;
}
/**
* Initialize graphic components.
*
* @param parent
* the composite from which we attach this graphic component.
* @param makeColumnsEqual
* If true makes the two columns of the main composite equals at layout level . If false makes the two
* columns not equals at layout level. Refresh the layout if a modification is done.
*/
public void createControl(final Composite parent, boolean makeColumnsEqual) {
rootComposite = new Composite(parent, SWT.NONE);
rootGridLayout = GridLayoutFactory.swtDefaults().numColumns(2).equalWidth(makeColumnsEqual).create();
rootComposite.setLayout(rootGridLayout);
rootLayoutData = new GridData(SWT.FILL, SWT.FILL, true, true);
rootComposite.setLayoutData(rootLayoutData);
createTableViewer(rootComposite);
createBrowser(rootComposite);
setBrowserInput(null);
}
/**
* Initialize the graphic component that is a browser allowing to see a description for each viewpoint that has the
* focus.
*
* @param parent
* the parent composite to be attached to.
* @return the newly created {@link Browser}.
*/
public Browser createBrowser(final Composite parent) {
browserRootComposite = new Composite(parent, SWT.BORDER);
browserRootComposite.setLayout(GridLayoutFactory.fillDefaults().create());
browserRootComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
browserErrorMessageComposite = new Composite(browserRootComposite, SWT.None);
browserErrorMessageComposite.setLayout(GridLayoutFactory.fillDefaults().create());
browserErrorMessageLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false);
browserErrorMessageComposite.setLayoutData(browserErrorMessageLayoutData);
browserErrorMessageLayoutData.exclude = true;
Composite browserComposite = new Composite(browserRootComposite, SWT.None);
browserComposite.setLayout(GridLayoutFactory.fillDefaults().create());
browserComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
browserErrorMessageText = new Text(browserErrorMessageComposite, SWT.MULTI | SWT.WRAP);
browserErrorMessageText.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, true, false));
browserErrorMessageText.setText(""); //$NON-NLS-1$
browserErrorMessageText.setForeground(browserRootComposite.getDisplay().getSystemColor(SWT.COLOR_RED));
try {
Browser aBrowser = new Browser(browserComposite, SWT.FILL);
browserGridData = new GridData(SWT.FILL, SWT.FILL, true, true);
// necessary to avoid bad interaction with expandable toolkit sections
browserGridData.widthHint = 0;
browserGridData.heightHint = 0;
aBrowser.setLayoutData(browserGridData);
this.browser = aBrowser;
return aBrowser;
} catch (SWTError error) {
/*
* the browser could not be created, do not display further information
*/
return null;
}
}
/**
* Set an error message above the viewpoint browser.
*
* @param errorMessage
* the error message to set.
*/
public void setBrowserErrorMessageText(String errorMessage) {
browserErrorMessageLayoutData.exclude = false;
browserErrorMessageText.setText(errorMessage);
browserErrorMessageComposite.setVisible(true);
browserRootComposite.layout(true, true);
}
/**
* Clear the error message above the viewpoint browser.
*/
public void clearBrowserErrorMessageText() {
browserErrorMessageLayoutData.exclude = true;
browserErrorMessageText.setText(""); //$NON-NLS-1$
browserErrorMessageComposite.setVisible(false);
browserRootComposite.getParent().layout(true, true);
}
/**
* Sets the minimum width the browser should have.
*
* @param minwidth
* the minimum width to set.
*/
public void setBrowserMinWidth(int minwidth) {
if (browserGridData != null) {
browserGridData.minimumWidth = minwidth;
}
}
/**
* Returns the viewer containing a checkbox for each viewpoint registered in the current runtime.
*
* @return the viewer containing a checkbox for each viewpoint registered in the current runtime.
*/
public CheckboxTableViewer getViewer() {
return viewer;
}
/**
* Create the table viewer.
*
* @param parent
* the parent composite.
* @return the table viewer.
*/
private TableViewer createTableViewer(final Composite parent) {
final int style = SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER;
viewer = CheckboxTableViewer.newCheckList(parent, style);
Table table = viewer.getTable();
viewerGridData = new GridData(SWT.FILL, SWT.FILL, false, false);
viewer.getControl().setLayoutData(viewerGridData);
TableLayout layout = new TableLayout();
table.setLayout(layout);
table.setHeaderVisible(false);
table.setLinesVisible(false);
viewer.setContentProvider(new ArrayContentProvider());
viewer.setLabelProvider(new ViewpointsTableLabelProvider());
viewer.addSelectionChangedListener(new ISelectionChangedListener() {
@Override
public void selectionChanged(SelectionChangedEvent event) {
ISelection selection = event.getSelection();
if (selection instanceof IStructuredSelection) {
Object firstElement = ((IStructuredSelection) selection).getFirstElement();
if (firstElement instanceof Viewpoint) {
setBrowserInput((Viewpoint) firstElement);
}
}
}
});
viewer.setComparator(new ViewerComparator() {
@Override
public int compare(Viewer theViewer, Object e1, Object e2) {
final String e1label = new IdentifiedElementQuery((Viewpoint) e1).getLabel();
final String e2label = new IdentifiedElementQuery((Viewpoint) e2).getLabel();
return e1label.compareTo(e2label);
}
});
return viewer;
}
/***
* Set the browser input.A jface like browser viewer would have been better.
*
* @param viewpoint
* the viewpoint to document
*/
public void setBrowserInput(final Viewpoint viewpoint) {
/* browser may be null if its creation fail */
if (browser != null) {
String content = null;
if (containsHTMLDocumentation(viewpoint)) {
content = getContentWhenHtml(viewpoint);
} else {
content = getContentWhenNoHtml(viewpoint);
}
browser.setText(content);
}
}
/*
* The following code (HTML handling ) and methods could move to another class.
*/
private boolean containsHTMLDocumentation(Viewpoint viewpoint) {
if (viewpoint != null) {
final String doc = viewpoint.getEndUserDocumentation();
if (!StringUtil.isEmpty(doc)) {
return doc.startsWith("<html>"); //$NON-NLS-1$
}
}
return false;
}
private String getContentWhenHtml(Viewpoint viewpoint) {
final String document = viewpoint.getEndUserDocumentation();
Set<String> urlToRewrite = Sets.newLinkedHashSet();
extractUrlToRewrite(document, urlToRewrite);
return rewriteURLs(viewpoint, document, urlToRewrite);
}
private void extractUrlToRewrite(String document, Set<String> urlToRewrite) {
String imgSrcPattern = "img src=\""; //$NON-NLS-1$
int patternStartIndex = document.indexOf(imgSrcPattern);
if (patternStartIndex != -1) {
int imgSrcStartIndex = patternStartIndex + imgSrcPattern.length();
int imgSrcStopIndex = document.indexOf("\"", imgSrcStartIndex); //$NON-NLS-1$
if (imgSrcStopIndex != -1) {
String newToRewrite = document.substring(imgSrcStartIndex, imgSrcStopIndex);
urlToRewrite.add(newToRewrite);
extractUrlToRewrite(document.substring(imgSrcStopIndex), urlToRewrite);
}
}
}
private String rewriteURLs(Viewpoint viewpoint, String document, Set<String> urls) {
String newDocument = document;
for (final String url : urls) {
newDocument = newDocument.replace(url, rewriteURL(viewpoint, url));
}
StringBuilder css = new StringBuilder();
appendCss(css);
String headClose = "</head>"; //$NON-NLS-1$
newDocument = newDocument.replace(headClose, css.append(headClose));
return newDocument;
}
private String rewriteURL(Viewpoint viewpoint, String url) {
final URI uri = viewpoint.eResource().getURI();
String pluginId = uri.segment(1);
String rewrittenURL = ""; //$NON-NLS-1$
if (uri.isPlatformPlugin()) {
Bundle bundle = Platform.getBundle(pluginId);
URL imageURL = bundle.getEntry(url);
rewrittenURL = imageURL != null ? imageURL.toString() : rewrittenURL;
if (imageURL != null) {
try {
URL fileURL = FileLocator.toFileURL(imageURL);
rewrittenURL = fileURL.toString();
} catch (IOException e) {
// do nothing
}
}
} else {
final IWorkspace workspace = ResourcesPlugin.getWorkspace();
final IPath path = new Path("/" + pluginId + url); //$NON-NLS-1$
if (workspace.getRoot().exists(path)) {
IResource resource = workspace.getRoot().findMember(path);
rewrittenURL = resource.getLocation().toFile().toURI().toString();
}
}
return rewrittenURL;
}
private String getContentWhenNoHtml(Viewpoint viewpoint) {
StringBuilder content = new StringBuilder();
return begin(content).head(content).body(content, viewpoint).end(content);
}
private ViewpointsSelectionGraphicalHandler begin(StringBuilder content) {
content.append("<html>"); //$NON-NLS-1$
return this;
}
private ViewpointsSelectionGraphicalHandler head(StringBuilder content) {
content.append("<head>"); //$NON-NLS-1$
appendCss(content);
content.append("</head>"); //$NON-NLS-1$
return this;
}
private ViewpointsSelectionGraphicalHandler body(StringBuilder content, Viewpoint viewpoint) {
content.append("<body>"); //$NON-NLS-1$
if (viewpoint == null) {
content.append("<br><br><center><b>").append(Messages.ViewpointsSelectionWizardPage_documentation_title).append("</b></center>"); //$NON-NLS-1$ //$NON-NLS-2$
} else {
final String endUserDocumentation = viewpoint.getEndUserDocumentation();
if (!StringUtil.isEmpty(endUserDocumentation)) {
content.append(viewpoint.getEndUserDocumentation());
} else {
content.append(Messages.ViewpointsSelectionWizardPage_documentation_none);
}
}
content.append("</body>"); //$NON-NLS-1$
return this;
}
private StringBuilder appendCss(StringBuilder content) {
Font currentFont = JFaceResources.getDialogFont();
FontData data = currentFont.getFontData()[0];
String fontName = data.getName();
int fontHeight = data.getHeight() + 3;
content.append("<style type=\"text/css\">"); //$NON-NLS-1$
content.append("body{font-family:" + fontName + ",Arial, sans-serif;}"); //$NON-NLS-1$ //$NON-NLS-2$
content.append("body{font-size:" + fontHeight + "px;}"); //$NON-NLS-1$ //$NON-NLS-2$
content.append("</style>"); //$NON-NLS-1$
return content;
}
private String end(StringBuilder content) {
content.append("</html>"); //$NON-NLS-1$
return content.toString();
}
private class ViewpointsTableLabelProvider extends AdapterFactoryLabelProvider implements ITableLabelProvider {
ViewpointsTableLabelProvider() {
super(ViewHelper.INSTANCE.createAdapterFactory());
}
@Override
public Image getColumnImage(Object element, int columnIndex) {
Image image = null;
if (columnIndex == 0) {
if (element instanceof Viewpoint) {
final Viewpoint vp = (Viewpoint) element;
if (vp.getIcon() != null && vp.getIcon().length() > 0) {
final ImageDescriptor desc = SiriusEditPlugin.Implementation.findImageDescriptor(vp.getIcon());
if (desc != null) {
image = SiriusEditPlugin.getPlugin().getImage(desc);
image = getEnhancedImage(image, vp);
}
}
if (image == null) {
image = SiriusEditPlugin.getPlugin().getImage(SiriusEditPlugin.getPlugin().getItemImageDescriptor(vp));
image = getEnhancedImage(image, vp);
}
} else {
image = super.getImage(element);
}
}
return image;
}
private ImageDescriptor getOverlayedDescriptor(final Image baseImage, final String decoratorPath) {
final ImageDescriptor decoratorDescriptor = SiriusEditPlugin.Implementation.getBundledImageDescriptor(decoratorPath);
return new DecorationOverlayIcon(baseImage, decoratorDescriptor, IDecoration.BOTTOM_LEFT);
}
private Image getEnhancedImage(final Image image, final Viewpoint viewpoint) {
// Add decorator if the viewpoint comes from workspace
if (!ViewpointRegistry.getInstance().isFromPlugin(viewpoint) && image != null) {
return SiriusEditPlugin.getPlugin().getImage(getOverlayedDescriptor(image, "icons/full/decorator/folder_close.gif")); //$NON-NLS-1$
}
return image;
}
}
/**
* Sets the height the enclosing root composite of the viewpoints block must have.
*
* @param height
* the height to set.
*/
public void setHeight(int height) {
viewerGridData.grabExcessVerticalSpace = false;
viewerGridData.heightHint = height;
}
}