/******************************************************************************* * Copyright (c) 2004 Vlad Dumitrescu 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: Vlad Dumitrescu *******************************************************************************/ package org.erlide.ui.views.eval; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckboxTableViewer; import org.eclipse.jface.viewers.ICellModifier; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerSorter; import org.eclipse.swt.SWT; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IMemento; import org.eclipse.ui.ISharedImages; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchActionConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.actions.ActionFactory; import org.eclipse.ui.part.ViewPart; import org.erlide.backend.debug.BackendEvalResult; import org.erlide.backend.debug.EvalHelper; import org.erlide.engine.model.OtpRpcFactory; import org.erlide.runtime.rpc.IOtpRpc; import org.erlide.ui.ErlideUIConstants; import org.erlide.ui.internal.ErlideUIPlugin; import org.erlide.ui.prefs.PreferenceConstants; import org.erlide.ui.util.DisplayUtils; import org.erlide.ui.views.SourceViewerInformationControl; import org.erlide.util.erlang.OtpErlang; /** * @author Vlad Dumitrescu */ public class LiveExpressionsView extends ViewPart implements IResourceChangeListener { public static final String ID = "org.erlide.ui.views.eval.LiveExpressionsView"; private Label label; private List<LiveExpr> exprs; CheckboxTableViewer viewer; private Action refreshAction; private Action fAddAction; Action fRemoveAction; private final IOtpRpc backend; private final class ListenerImplementation implements Listener { private final Table t; SourceViewerInformationControl info = null; private ListenerImplementation(final Table t) { this.t = t; } @Override public void handleEvent(final Event event) { switch (event.type) { case SWT.Dispose: case SWT.KeyDown: case SWT.MouseMove: { if (info == null) { break; } info.dispose(); info = null; break; } case SWT.MouseHover: { final TableItem item = t.getItem(new Point(event.x, event.y)); if (item != null) { String str = item.getText(1); if (str.length() > 0) { // ErlLogger.debug(str); final BackendEvalResult r = EvalHelper.eval(backend, "lists:flatten(io_lib:format(\"~p\", [" + item.getText(1) + "])).", null); if (r.isOk()) { str = OtpErlang.asString(r.getValue()); } else { str = r.getErrorReason().toString(); } info = new SourceViewerInformationControl(t.getShell(), SWT.ON_TOP | SWT.TOOL | SWT.RESIZE, SWT.MULTI | SWT.WRAP, PreferenceConstants.EDITOR_TEXT_FONT, null); info.setForegroundColor( t.getDisplay().getSystemColor(SWT.COLOR_INFO_FOREGROUND)); info.setBackgroundColor( t.getDisplay().getSystemColor(SWT.COLOR_INFO_BACKGROUND)); info.setInformation(str); final Rectangle rect = item.getBounds(1); final int lw = t.getGridLineWidth(); final Point pt = t.toDisplay(rect.x + lw, rect.y + lw); info.setLocation(pt); info.setSize(rect.width + lw, t.getBounds().height - rect.y); info.setVisible(true); } } } break; } } } private static class LiveExpr { String fExpr; private String cachedValue = ""; boolean doEval = false; private final IOtpRpc b; public LiveExpr(final IOtpRpc b, final String s) { fExpr = s; this.b = b; } public void setDoEval(final boolean eval) { doEval = eval; } public String getValue() { if (doEval) { cachedValue = evaluate(); } return cachedValue; } private String evaluate() { final BackendEvalResult r = EvalHelper.eval(b, fExpr + ".", null); if (r.isOk()) { return r.getValue().toString(); } return "ERR: " + r.getErrorReason().toString(); } @Override public String toString() { return fExpr; } } /* * The content provider class is responsible for providing objects to the view. It can * wrap existing objects in adapters or simply return objects as-is. These objects may * be sensitive to the current input of the view, or ignore it and always show the * same content (like Task List, for example). */ static class ViewContentProvider implements IStructuredContentProvider { private List<LiveExpr> exprlist; public ViewContentProvider() { super(); } @Override @SuppressWarnings("unchecked") public void inputChanged(final Viewer v, final Object oldInput, final Object newInput) { if (newInput instanceof List<?>) { exprlist = (List<LiveExpr>) newInput; } } @Override public void dispose() { exprlist = null; } @Override public Object[] getElements(final Object parent) { return exprlist.toArray(); } } class ViewLabelProvider extends LabelProvider implements ITableLabelProvider { @Override public String getColumnText(final Object obj, final int index) { final LiveExpr e = (LiveExpr) obj; if (index == 0) { return e.fExpr; } if (index == 1) { e.setDoEval(viewer.getChecked(e)); return e.getValue(); } return null; } @Override public Image getColumnImage(final Object obj, final int index) { // if (index == 0) // return getImage(obj); // else return null; } @Override public Image getImage(final Object obj) { return PlatformUI.getWorkbench().getSharedImages() .getImage(ISharedImages.IMG_OBJ_ELEMENT); } } static class NameSorter extends ViewerSorter { } public LiveExpressionsView() { ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_BUILD); backend = OtpRpcFactory.getOtpRpc(); // TODO make the backend configurable (as for console) } /** * This is a callback that will allow us to create the viewer and initialize it. */ @Override public void createPartControl(final Composite parent) { label = new Label(parent, SWT.NULL); final Table t = new Table(parent, SWT.SINGLE | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.CHECK); viewer = new CheckboxTableViewer(t); final GridData labelLData = new GridData(); labelLData.verticalAlignment = GridData.BEGINNING; labelLData.horizontalAlignment = GridData.FILL; labelLData.widthHint = 300; labelLData.heightHint = 14; labelLData.horizontalIndent = 0; labelLData.horizontalSpan = 1; labelLData.verticalSpan = 1; labelLData.grabExcessHorizontalSpace = true; labelLData.grabExcessVerticalSpace = false; label.setLayoutData(labelLData); label.setSize(new org.eclipse.swt.graphics.Point(319, 14)); final GridData viewerLData = new GridData(); viewerLData.verticalAlignment = GridData.FILL; viewerLData.horizontalAlignment = GridData.FILL; viewerLData.widthHint = 600; viewerLData.heightHint = 150; viewerLData.horizontalIndent = 0; viewerLData.horizontalSpan = 1; viewerLData.verticalSpan = 1; viewerLData.grabExcessHorizontalSpace = true; viewerLData.grabExcessVerticalSpace = true; t.setLayoutData(viewerLData); t.setSize(new org.eclipse.swt.graphics.Point(600, 200)); final GridLayout thisLayout = new GridLayout(1, true); parent.setLayout(thisLayout); thisLayout.marginWidth = 5; thisLayout.marginHeight = 5; thisLayout.numColumns = 1; thisLayout.makeColumnsEqualWidth = false; thisLayout.horizontalSpacing = 0; thisLayout.verticalSpacing = 1; t.setLinesVisible(true); t.setHeaderVisible(true); // t.setFont(); final TableColumn colExpr = new TableColumn(t, SWT.LEAD, 0); colExpr.setText("Expression"); colExpr.setWidth(150); final TableColumn colValue = new TableColumn(t, SWT.LEAD, 1); colValue.setText("Value"); colValue.setWidth(t.getSize().x - 50); viewer.setColumnProperties(new String[] { "expr", "val" }); viewer.setContentProvider(new ViewContentProvider()); viewer.setLabelProvider(new ViewLabelProvider()); // viewer.setSorter(new NameSorter()); if (!restoreState()) { /* Fill LiveExpressions for first time */ exprs = new ArrayList<>(10); addExpr(new LiveExpr(backend, "erlide_time_compat:timestamp()")); } viewer.setInput(exprs); final TextCellEditor e = new TextCellEditor(t); viewer.setCellEditors(new CellEditor[] { e, null }); viewer.setCellModifier(new LiveExprCellModifier(this)); makeActions(); hookContextMenu(); contributeToActionBars(); hookGlobalActions(); final Listener tableListener = new ListenerImplementation(t); t.addListener(SWT.Dispose, tableListener); t.addListener(SWT.KeyDown, tableListener); t.addListener(SWT.MouseMove, tableListener); t.addListener(SWT.MouseHover, tableListener); // ///////////// } /* We want Live Expressions to be persistant! */ IMemento memento; // @jve:decl-index=0: @Override public void init(final IViewSite site, final IMemento aMemento) throws PartInitException { init(site); memento = aMemento; } @Override public void saveState(final IMemento aMemento) { if (exprs.isEmpty()) { return; } final IMemento aMemento2 = aMemento.createChild("LiveExpressions"); final Iterator<LiveExpr> iter = exprs.iterator(); while (iter.hasNext()) { aMemento2.createChild("expression").putTextData(iter.next().toString()); } } private boolean restoreState() { if (memento != null) { memento = memento.getChild("LiveExpressions"); } if (memento != null) { final IMemento[] expressions = memento.getChildren("expression"); if (expressions.length > 0) { exprs = new ArrayList<>(expressions.length); for (final IMemento element : expressions) { exprs.add(new LiveExpr(backend, element.getTextData())); } } return true; } memento = null; return false; } private static class LiveExprCellModifier implements ICellModifier { private final LiveExpressionsView view; public LiveExprCellModifier(final LiveExpressionsView v) { view = v; } @Override public boolean canModify(final Object element, final String property) { if ("expr".equals(property)) { return true; } return false; } @Override public Object getValue(final Object element, final String property) { Object result = null; final LiveExpr el = (LiveExpr) element; result = el.fExpr; return result; } @Override public void modify(final Object element, final String property, final Object value) { LiveExpr el; // get around bug in TableEditorImpl if (element instanceof TableItem) { el = (LiveExpr) ((TableItem) element).getData(); } else { el = (LiveExpr) element; } el.fExpr = (String) value; el.cachedValue = ""; view.updateExpr(el); } } private void hookContextMenu() { final MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); menuMgr.addMenuListener(new IMenuListener() { @Override public void menuAboutToShow(final IMenuManager manager) { LiveExpressionsView.this.fillContextMenu(manager); } }); final Menu menu = menuMgr.createContextMenu(viewer.getControl()); viewer.getControl().setMenu(menu); getSite().registerContextMenu(menuMgr, viewer); } private void contributeToActionBars() { final IActionBars bars = getViewSite().getActionBars(); fillLocalPullDown(bars.getMenuManager()); fillLocalToolBar(bars.getToolBarManager()); } private void fillLocalPullDown(final IMenuManager manager) { manager.add(refreshAction); manager.add(fAddAction); manager.add(fRemoveAction); manager.add(new Separator()); } void fillContextMenu(final IMenuManager manager) { manager.add(refreshAction); manager.add(fAddAction); manager.add(fRemoveAction); // Other plug-ins can contribute there actions here manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS)); } private void fillLocalToolBar(final IToolBarManager manager) { manager.add(refreshAction); manager.add(fAddAction); manager.add(fRemoveAction); } private void makeActions() { refreshAction = new Action() { @Override public void run() { refreshView(); } }; refreshAction.setText("Refresh"); refreshAction.setToolTipText("Refresh expressions"); refreshAction.setImageDescriptor(ErlideUIPlugin.getDefault() .getImageDescriptor(ErlideUIConstants.IMG_REFRESH)); fAddAction = new Action() { @Override public void run() { addExpr(new LiveExpr(backend, "expr")); } }; fAddAction.setText("Add expression"); fAddAction.setToolTipText("Add new expression"); fAddAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages() .getImageDescriptor(ISharedImages.IMG_TOOL_NEW_WIZARD)); fRemoveAction = new Action() { @Override public void run() { delExpr(); } }; fRemoveAction.setText("Delete expression"); fRemoveAction.setToolTipText("Delete expression"); fRemoveAction.setImageDescriptor(PlatformUI.getWorkbench().getSharedImages() .getImageDescriptor(ISharedImages.IMG_TOOL_DELETE)); } private void hookGlobalActions() { final IActionBars bars = getViewSite().getActionBars(); bars.setGlobalActionHandler(ActionFactory.DELETE.getId(), fRemoveAction); viewer.getControl().addKeyListener(new KeyAdapter() { @Override public void keyPressed(final KeyEvent event) { if (event.character == SWT.DEL && event.stateMask == 0 && fRemoveAction.isEnabled()) { fRemoveAction.run(); } } }); } void showMessage(final String message) { MessageDialog.openInformation(viewer.getControl().getShell(), "Process list view", message); } /** * Passing the focus request to the viewer's control. */ @Override public void setFocus() { viewer.getControl().setFocus(); } @Override public void resourceChanged(final IResourceChangeEvent event) { refreshView(); } private void refreshView() { if (!viewer.getControl().isDisposed()) { DisplayUtils.asyncExec(new Runnable() { @Override public void run() { if (viewer == null) { return; } if (viewer.getControl().isDisposed()) { return; } viewer.refresh(); } }); } } public void addExpr(final LiveExpr e) { exprs.add(e); refreshView(); } public void updateExpr(final LiveExpr e) { refreshView(); } public void delExpr() { final IStructuredSelection sel = (IStructuredSelection) viewer.getSelection(); @SuppressWarnings("unchecked") final Iterator<LiveExpr> iter = sel.iterator(); while (iter.hasNext()) { exprs.remove(iter.next()); } refreshView(); } }