blob: 32176e06793de5e842faa8e0b4697ab1056a7978 [file] [log] [blame]
/*********************************************************************
* Copyright (c) 2008 The University of York.
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.epsilon.picto;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.epsilon.common.dt.EpsilonCommonsPlugin;
import org.eclipse.epsilon.common.dt.util.LogUtil;
import org.eclipse.epsilon.picto.ViewRenderer.ZoomType;
import org.eclipse.epsilon.picto.actions.*;
import org.eclipse.epsilon.picto.browser.*;
import org.eclipse.epsilon.picto.preferences.PictoPreferencePage;
import org.eclipse.epsilon.picto.source.PictoSource;
import org.eclipse.epsilon.picto.source.PictoSourceExtensionPointManager;
import org.eclipse.epsilon.picto.source.VerbatimSource;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.BrowserFunction;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.browser.ProgressListener;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPropertyListener;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.dialogs.PatternFilter;
import org.eclipse.ui.part.ViewPart;
public class PictoView extends ViewPart {
public static final String ID = "org.eclipse.epsilon.picto.PictoView";
protected ViewRenderer viewRenderer;
protected BrowserContainer browserContainer;
protected IEditorPart editor;
protected EditorPropertyListener listener = new EditorPropertyListener();
protected TreeViewer treeViewer;
protected SashForm sashForm;
protected int[] sashFormWeights = null;
protected IEditorPart renderedEditor = null;
protected boolean locked = false;
protected ToggleTreeViewerAction hideTreeAction;
protected HashMap<IEditorPart, ViewTree> activeViewHistory = new HashMap<>();
protected ViewTree activeView = null;
protected PictoSource source = null;
protected Collection<PictoSource> sources = new PictoSourceExtensionPointManager().getExtensions();
protected List<PictoBrowserFunction> browserFunctions = new BrowserFunctionExtensionPointManager().getExtensions();
protected List<PictoBrowserScript> browserScripts = new BrowserScriptExtensionPointManager().getExtensions();
protected ViewTreeLabelProvider viewTreeLabelProvider;
protected FilteredViewTree filteredTree;
protected boolean renderVerbatimSources = false;
protected ViewTreeSelectionHistory viewTreeSelectionHistory = new ViewTreeSelectionHistory();
@Override
public void createPartControl(Composite parent) {
sashForm = new SashForm(parent, SWT.HORIZONTAL);
PatternFilter filter = new PatternFilter() {
@Override
protected boolean isLeafMatch(Viewer viewer, Object element) {
ViewTree viewTree = (ViewTree) element;
return wordMatches(viewTree.getName());
}
};
filteredTree = new FilteredViewTree(sashForm,
SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER,
filter, true
);
treeViewer = filteredTree.getViewer();
treeViewer.setContentProvider(new ViewTreeContentProvider());
viewTreeLabelProvider = new ViewTreeLabelProvider();
treeViewer.setLabelProvider(viewTreeLabelProvider);
treeViewer.addSelectionChangedListener(event -> {
ViewTree view = ((ViewTree) event.getStructuredSelection().getFirstElement());
if (view != null && view.getContent() != null) {
activeViewHistory.put(renderedEditor, view);
// If the selection happens as a result of undo/redo
// we should not execute a new command
if (viewTreeSelectionHistory.isAutomatedSelection()) return;
viewTreeSelectionHistory.execute(new Command() {
protected List<String> path = null;
@Override
public void execute() {
if (path == null) {
path = view.getPath();
try {
renderView(view);
}
catch (Exception ex) {
viewRenderer.display(ex);
}
}
else {
try {
viewTreeSelectionHistory.setAutomatedSelection(true);
selectViewTree(path);
renderView(getViewTree().forPath(path));
}
catch (Exception ex) {
viewRenderer.display(ex);
}
finally {
viewTreeSelectionHistory.setAutomatedSelection(false);
}
}
}
});
}
});
treeViewer.addDoubleClickListener(event -> filteredTree.clearFilterText());
browserContainer = new BrowserContainer(sashForm, SWT.NONE);
viewRenderer = new ViewRenderer(new Browser(browserContainer, SWT.NONE));
Browser browser = viewRenderer.getBrowser();
for (PictoBrowserFunction pbf : browserFunctions) {
new BrowserFunction(browser, pbf.getName()) {
public Object function(Object[] arguments) {
pbf.accept(PictoView.this, arguments);
return null;
}
};
}
for (PictoBrowserScript pbs : browserScripts) {
browser.execute(pbs.apply(this));
}
sashFormWeights = new int[] {20, 80};
sashForm.setSashWidth(2);
sashForm.setWeights(sashFormWeights);
hideTreeAction = new ToggleTreeViewerAction();
setTreeViewerVisible(false);
IEditorPart activeEditor = getSite().getPage().getActiveEditor();
render(activeEditor != null && supports(activeEditor) ? activeEditor : null);
final PartListener partListener = new PartListener() {
@Override
public void partActivated(IWorkbenchPartReference partRef) {
if (locked) return;
Display.getCurrent().asyncExec(() -> {
IWorkbenchPart part = partRef.getPart(false);
if (editor != part && part instanceof IEditorPart) {
PictoSource source = getSource((IEditorPart) part);
if (source != null) {
if (source instanceof VerbatimSource && !renderVerbatimSources) return;
render((IEditorPart) part);
}
}
});
}
@Override
public void partClosed(IWorkbenchPartReference partRef) {
IWorkbenchPart workbenchPart = partRef.getPart(false);
if (workbenchPart == PictoView.this) {
getSite().getPage().removePartListener(this);
}
if (!(workbenchPart instanceof IEditorPart)) return;
IEditorPart editorPart = (IEditorPart) workbenchPart;
activeViewHistory.remove(editorPart);
if (locked) {
if (editor == editorPart) editor = null;
return;
}
if (editor == editorPart) {
Display.getCurrent().asyncExec(() -> render(null));
}
}
};
IToolBarManager toolbar = getViewSite().getActionBars().getToolBarManager();
toolbar.add(new BackAction(this));
toolbar.add(new ForwardAction(this));
toolbar.add(new Separator());
toolbar.add(new ZoomAction(ZoomType.IN, viewRenderer));
toolbar.add(new ZoomAction(ZoomType.ACTUAL, viewRenderer));
toolbar.add(new ZoomAction(ZoomType.OUT, viewRenderer));
toolbar.add(new Separator());
toolbar.add(new LayersMenuAction(this));
toolbar.add(new Separator());
toolbar.add(new CopyToClipboardAction(this));
toolbar.add(new PrintAction(viewRenderer));
toolbar.add(new RefreshAction(this));
toolbar.add(new LockAction(this));
toolbar.add(new Separator());
toolbar.add(hideTreeAction);
toolbar.add(new MoveTreeAction());
toolbar.add(new Separator());
toolbar.add(new ViewContentsMenuAction(this));
IMenuManager dropDownMenu = getViewSite().getActionBars().getMenuManager();
dropDownMenu.add(new ClearViewTreeLabelProviderIconCacheAction());
dropDownMenu.add(new ToggleVerbatimSourcesAction());
this.getSite().getPage().addPartListener(partListener);
}
public void render(IEditorPart editor) {
if (editor == null) {
setTreeViewerVisible(false);
viewRenderer.nothingToRender();
this.editor = null;
}
else {
if (this.editor != null) {
this.editor.removePropertyListener(listener);
}
this.editor = editor;
editor.addPropertyListener(listener);
Job job = new Job("Rendering " + editor.getTitle()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
renderEditorContent();
return Status.OK_STATUS;
}
};
job.setUser(true);
job.schedule();
}
}
protected boolean treeViewerShouldBeVisible;
protected boolean isTreeViewerVisible() {
if (sashForm.isDisposed()) return false;
return sashForm.getSashWidth() > 0;
}
protected void setTreeViewerVisible(boolean visible) {
this.treeViewerShouldBeVisible = visible;
visible = treeViewerShouldBeVisible && !hideTreeAction.isChecked();
if (isTreeViewerVisible() && !visible) { // Hide
sashFormWeights = sashForm.getWeights();
sashForm.setSashWidth(0);
sashForm.setWeights(new int[] {0, 100});
}
else if (!isTreeViewerVisible() && visible) { // Show
sashForm.setSashWidth(2);
sashForm.setWeights(sashFormWeights);
}
browserContainer.setBorderVisible(visible);
}
public void selectViewTree(List<String> path) {
ViewTree viewTree = getViewTree().forPath(path);
List<ViewTree> treePath = new ArrayList<>();
while (viewTree != null) {
treePath.add(0, viewTree);
viewTree = viewTree.getParent();
}
treeViewer.setSelection(new TreeSelection(new TreePath(treePath.toArray())));
treeViewer.refresh();
}
public void renderEditorContent() {
try {
PictoSource newSource = getSource(editor);
if (source != null) source.dispose();
source = newSource;
boolean rerender = renderedEditor == editor;
renderedEditor = editor;
if (!rerender) viewTreeSelectionHistory = new ViewTreeSelectionHistory();
final ViewTree viewTree = source.getViewTree(editor);
runInUIThread(new RunnableWithException() {
@Override
public void runWithException() throws Exception {
if (viewTree.getChildren().isEmpty()) {
if (rerender) viewTree.setScrollPosition(viewRenderer.getScrollPosition());
renderView(viewTree);
}
else {
setViewTree(viewTree, rerender);
}
setTreeViewerVisible(!viewTree.getChildren().isEmpty());
}
});
}
catch (Exception ex) {
try {
runInUIThread(new RunnableWithException() {
@Override
public void runWithException() throws Exception {
setTreeViewerVisible(false);
renderView(new ViewTree(viewRenderer.getVerbatim(ex.getMessage()), "html"));
}
});
}
catch (Exception e) {
e.printStackTrace();
}
LogUtil.log(ex);
}
}
public void runInUIThread(RunnableWithException runnable) throws Exception {
Display.getDefault().syncExec(runnable);
Exception ex = runnable.getException();
if (ex != null) throw ex;
}
protected void setViewTree(ViewTree newViewTree, boolean rerender) throws Exception {
ViewTree viewTree = getViewTree();
if (viewTree == null || !rerender) {
viewTree = newViewTree;
treeViewer.setInput(viewTree);
}
else {
viewTree.ingest(newViewTree);
}
treeViewer.refresh();
if (rerender) {
ViewTree selected = (ViewTree) treeViewer.getStructuredSelection().getFirstElement();
if (selected != null && selected.getContent() != null) {
renderView(selected);
}
else {
viewRenderer.nothingToRender();
}
}
else {
ViewTree selection = null;
ViewTree historicalView = activeViewHistory.get(renderedEditor);
if (historicalView != null) {
selection = viewTree.forPath(historicalView.getPath());
if (selection != null)
selection.setScrollPosition(historicalView.getScrollPosition());
}
if (selection == null) {
selection = viewTree.getFirstWithContent();
}
if (selection != null) {
treeViewer.setSelection(new TreeSelection(new TreePath(new Object[] {selection})), true);
treeViewer.refresh();
}
else {
viewRenderer.nothingToRender();
}
}
}
public void renderView(ViewTree view) throws Exception {
Browser browser = viewRenderer.getBrowser();
if (activeView != null) {
activeView.setScrollPosition(viewRenderer.getScrollPosition());
}
activeView = view;
browser.addProgressListener(new ProgressListener() {
@Override
public void completed(ProgressEvent event) {
viewRenderer.setScrollPosition(activeView.getScrollPosition());
browser.removeProgressListener(this);
}
@Override
public void changed(ProgressEvent event) {}
});
// Check if one of the source contents of the view is active
ViewContent content = null;
for (Iterator<ViewContent> contentIterator = view.getContents(this).iterator(); contentIterator.hasNext() && content == null; ) {
ViewContent next = contentIterator.next();
if (next.isActive()) {
content = next.getSourceContent(this);
}
}
// ... if not, show the final rendered result
if (content == null) content = view.getContent().getFinal(this);
viewRenderer.display(content.getText());
}
@Override
public void dispose() {
super.dispose();
if (source != null) {
source.dispose();
}
if (editor != null) {
editor.removePropertyListener(listener);
}
}
@Override
public void setFocus() {
viewRenderer.getBrowser().setFocus();
}
class EditorPropertyListener implements IPropertyListener {
@Override
public void propertyChanged(Object source, int propId) {
if (locked) return;
if (propId == IEditorPart.PROP_DIRTY && !editor.isDirty()) {
render(editor);
}
}
}
class ToggleTreeViewerAction extends Action {
public ToggleTreeViewerAction() {
super("Toggle tree", AS_CHECK_BOX);
setImageDescriptor(PictoPlugin.getDefault().getImageDescriptor("icons/tree.png"));
}
@Override
public void run() {
setTreeViewerVisible(treeViewerShouldBeVisible);
}
}
class MoveTreeAction extends Action {
protected String position = "left";
public MoveTreeAction() {
setText(getText("right"));
setImageDescriptor(getImageDescriptor("right"));
}
@Override
public void run() {
if ("left".equals(position)) {
browserContainer.moveAbove(filteredTree);
}
else {
filteredTree.moveAbove(browserContainer);
}
sashForm.layout(true);
setText(getText(position));
setImageDescriptor(getImageDescriptor(position));
position = position.equalsIgnoreCase("left") ? "right" : "left";
}
protected String getText(String position) {
return "Move tree to the " + position;
}
protected ImageDescriptor getImageDescriptor(String position) {
return PictoPlugin.getDefault().getImageDescriptor("icons/tree_" + position + ".png");
}
}
class ClearViewTreeLabelProviderIconCacheAction extends Action {
public ClearViewTreeLabelProviderIconCacheAction() {
super("Clear tree icon cache");
}
@Override
public void run() {
viewTreeLabelProvider.clearIconCache();
}
}
class ToggleVerbatimSourcesAction extends Action {
public ToggleVerbatimSourcesAction() {
super("Render verbatim sources", AS_CHECK_BOX);
renderVerbatimSources = EpsilonCommonsPlugin.getDefault().getPreferenceStore().getBoolean(PictoPreferencePage.PROPERTY_RENDER_VERBATIM);
setChecked(renderVerbatimSources);
}
@Override
public void run() {
renderVerbatimSources = this.isChecked();
}
}
protected boolean supports(IEditorPart editorPart) {
return getSource(editorPart) != null;
}
protected PictoSource getSource(IEditorPart editorPart) {
for (PictoSource source : sources) {
if (source.supports(editorPart)) {
return source;
}
}
return null;
}
/**
*
* @return
* @since 2.2
*/
public PictoSource getSource() {
return getSource(editor);
}
@SuppressWarnings("unchecked")
@Override
public <T> T getAdapter(Class<T> adapter) {
if (IFindReplaceTarget.class.equals(adapter)) {
return (T) new BrowserFindTarget(viewRenderer.getBrowser());
}
return super.getAdapter(adapter);
}
public IEditorPart getEditor() {
return editor;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public ViewTree getActiveView() {
return activeView;
}
public ViewTree getViewTree() {
return (ViewTree) treeViewer.getInput();
}
public ViewRenderer getViewRenderer() {
return viewRenderer;
}
public void setViewRenderer(ViewRenderer viewRenderer) {
this.viewRenderer = viewRenderer;
}
public ViewTreeSelectionHistory getViewTreeSelectionHistory() {
return viewTreeSelectionHistory;
}
}