Java tutorial
/*- ******************************************************************************* * Copyright (c) 2011, 2016 Diamond Light Source Ltd. * 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: * Matthew Gerring - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.scanning.device.ui.model; import java.net.URI; import java.util.Arrays; import java.util.Collection; import java.util.List; import org.eclipse.dawnsci.analysis.api.roi.IROI; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IContributionManager; import org.eclipse.jface.bindings.keys.IKeyLookup; import org.eclipse.jface.bindings.keys.KeyLookupFactory; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewerEditor; import org.eclipse.jface.viewers.ColumnViewerEditorActivationEvent; import org.eclipse.jface.viewers.ColumnViewerEditorActivationStrategy; import org.eclipse.jface.viewers.FocusCellOwnerDrawHighlighter; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TableViewerEditor; import org.eclipse.jface.viewers.TableViewerFocusCellManager; import org.eclipse.jface.viewers.Viewer; import org.eclipse.richbeans.widgets.internal.GridUtils; import org.eclipse.richbeans.widgets.menu.CheckableActionGroup; import org.eclipse.richbeans.widgets.table.ISeriesItemDescriptor; import org.eclipse.richbeans.widgets.table.SeriesItemView; import org.eclipse.scanning.api.IModelProvider; import org.eclipse.scanning.api.IValidator; import org.eclipse.scanning.api.ModelValidationException; import org.eclipse.scanning.api.annotation.ui.FieldRole; import org.eclipse.scanning.api.annotation.ui.FieldUtils; import org.eclipse.scanning.api.annotation.ui.FieldValue; import org.eclipse.scanning.api.annotation.ui.TypeDescriptor; import org.eclipse.scanning.api.device.IRunnableDevice; import org.eclipse.scanning.api.device.IRunnableDeviceService; import org.eclipse.scanning.api.event.scan.DeviceInformation; import org.eclipse.scanning.api.points.IPointGenerator; import org.eclipse.scanning.api.points.models.BoundingBox; import org.eclipse.scanning.api.points.models.IBoundingBoxModel; import org.eclipse.scanning.api.points.models.IScanPathModel; import org.eclipse.scanning.api.ui.CommandConstants; import org.eclipse.scanning.api.ui.auto.IModelViewer; import org.eclipse.scanning.device.ui.Activator; import org.eclipse.scanning.device.ui.ServiceHolder; import org.eclipse.scanning.device.ui.model.ModelPersistAction.PersistType; import org.eclipse.scanning.device.ui.points.ScanView; import org.eclipse.scanning.device.ui.util.PageUtil; import org.eclipse.scanning.device.ui.util.ScanRegions; import org.eclipse.scanning.device.ui.util.ViewUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.FileTransfer; import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.KeyListener; 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.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Item; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.ISelectionListener; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewReference; import org.eclipse.ui.IViewSite; import org.eclipse.ui.IWorkbenchPart; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class for editing an operation model. Shows a table or other * relevant GUI for editing the model. * * This class simply listens to the current selection and shows a GUI for editing * it if the selection is an IOperation. * * You can also call setOperation(...) to programmatically set the editing operation. * * @author Matthew Gerring * */ class ModelViewer<T> implements IModelViewer<T>, ISelectionListener, ISelectionProvider { private static final Logger logger = LoggerFactory.getLogger(ModelViewer.class); // UI private TableViewer viewer; // Edits beans with a table of values private TypeEditor<T> typeEditor; // Edits beans with TypeDescriptor custom editors private Composite content; private Composite validationComposite; private Label validationMessage; private ModelPersistAction<T> save, load; /** * Caution, the view site may be null. */ private IViewSite site; // Model private T model; // Validation private IValidator<Object> validator; // The generator or runnable device etc. for which we are editing the model private boolean validationError = false; private ModelValidationException validationException; // Services private IRunnableDeviceService dservice; ModelViewer() { super(); } public <V> void setViewSite(V site) { this.site = (IViewSite) site; if (site != null) this.site.getPage().addSelectionListener(this); } public void dispose() { if (PageUtil.getPage() != null) PageUtil.getPage().removeSelectionListener(this); } public <U> U createPartControl(U ancestor) { this.content = new Composite((Composite) ancestor, SWT.NONE); content.setLayout(new GridLayout(1, false)); content.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); GridUtils.removeMargins(content); this.viewer = createTableViewer(content); this.typeEditor = new TypeEditor<>(this, content, SWT.NONE); typeEditor.setLayoutData(new GridData(GridData.FILL_BOTH)); GridUtils.setVisible(typeEditor, false); this.validationComposite = new Composite(content, SWT.NONE); validationComposite.setLayout(new GridLayout(2, false)); validationComposite.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false)); final Label error = new Label(validationComposite, SWT.NONE); error.setImage(Activator.getImageDescriptor("icons/error.png").createImage()); error.setLayoutData(new GridData(SWT.LEFT, SWT.BOTTOM, false, false)); this.validationMessage = new Label(validationComposite, SWT.WRAP); validationMessage.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED)); validationMessage.setLayoutData(new GridData(SWT.FILL, SWT.BOTTOM, true, false)); GridUtils.setVisible(validationComposite, false); if (PageUtil.getPage() != null) { ISelection selection = PageUtil.getPage().getSelection(); processWorkbenchSelection(selection); // If model view is selected later but something it can process is the page selection... if (model == null) { // Go and look for the model on a view final IViewPart part = PageUtil.getPage().findView(ScanView.ID); final Object model = part != null ? part.getAdapter(IScanPathModel.class) : null; processObject(model); } } createActions(); return (U) content; } private TableViewer createTableViewer(Composite content2) { TableViewer viewer = new TableViewer(content, SWT.SINGLE | SWT.BORDER | SWT.FULL_SELECTION); viewer.setContentProvider(createContentProvider()); viewer.getTable().setLinesVisible(true); viewer.getTable().setHeaderVisible(true); viewer.getTable().setLayoutData(new GridData(GridData.FILL_BOTH)); // resize the row height using a MeasureItem listener viewer.getTable().addListener(SWT.MeasureItem, new Listener() { public void handleEvent(Event event) { event.height = 24; } }); //added 'event.height=rowHeight' here just to check if it will draw as I want viewer.getTable().addListener(SWT.EraseItem, new Listener() { public void handleEvent(Event event) { event.height = 24; } }); TableViewerFocusCellManager focusCellManager = new TableViewerFocusCellManager(viewer, new FocusCellOwnerDrawHighlighter(viewer)); ColumnViewerEditorActivationStrategy actSupport = new ColumnViewerEditorActivationStrategy(viewer) { @Override protected boolean isEditorActivationEvent(ColumnViewerEditorActivationEvent event) { // TODO see AbstractComboBoxCellEditor for how list is made visible return super.isEditorActivationEvent(event) || (event.eventType == ColumnViewerEditorActivationEvent.KEY_PRESSED && (event.keyCode == KeyLookupFactory.getDefault() .formalKeyLookup(IKeyLookup.ENTER_NAME))); } }; TableViewerEditor.create(viewer, focusCellManager, actSupport, ColumnViewerEditor.TABBING_HORIZONTAL | ColumnViewerEditor.TABBING_MOVE_TO_ROW_NEIGHBOR | ColumnViewerEditor.TABBING_VERTICAL | ColumnViewerEditor.KEYBOARD_ACTIVATION); createColumns(viewer); createDropTarget(viewer); viewer.getTable().addKeyListener(new KeyListener() { public void keyReleased(KeyEvent e) { } public void keyPressed(KeyEvent e) { if (e.keyCode == SWT.F1) { // TODO Help! } if (e.character == SWT.DEL) { try { Object ob = ((IStructuredSelection) viewer.getSelection()).getFirstElement(); ((FieldValue) ob).set(null); viewer.setSelection(new StructuredSelection(ob)); refresh(); // Must do global refresh because might effect units of other parameters. } catch (Exception ne) { logger.error("Cannot delete item " + (IStructuredSelection) viewer.getSelection(), ne); } } } }); return viewer; } private void createActions() { if (site == null) return; List<IContributionManager> mans = Arrays.asList(site.getActionBars().getToolBarManager(), site.getActionBars().getMenuManager()); // TODO We should be able to switch around different roles // and show parameters in the table depending on role. CheckableActionGroup group = new CheckableActionGroup(); createFieldRoleActions(group); this.save = new ModelPersistAction<T>(typeEditor, PersistType.SAVE); this.load = new ModelPersistAction<T>(typeEditor, PersistType.LOAD); ViewUtil.addGroups("save", mans, save, load); } private void createFieldRoleActions(CheckableActionGroup group) { FieldRole[] roles = FieldRole.values(); for (FieldRole role : roles) { IAction action = new Action(role.getLabel(), IAction.AS_CHECK_BOX) { public void run() { setFieldRole(role); } }; //action.setImageDescriptor(newImage); group.add(action); } } private void setFieldRole(FieldRole simple) { // TODO Filter table! } public <U> U getControl() { return (U) content; } private void createDropTarget(TableViewer viewer) { final Table table = (Table) viewer.getControl(); // Create drop target for file paths. DropTarget target = new DropTarget(table, DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_DEFAULT); final TextTransfer textTransfer = TextTransfer.getInstance(); final FileTransfer fileTransfer = FileTransfer.getInstance(); Transfer[] types = new Transfer[] { fileTransfer, textTransfer }; target.setTransfer(types); target.addDropListener(new DropTargetAdapter() { private boolean checkLocation(DropTargetEvent event) { if (event.item == null || !(event.item instanceof Item)) { return false; } Item item = (Item) event.item; // will accept text but prefer to have files dropped Rectangle bounds = ((TableItem) item).getBounds(1); Point coordinates = new Point(event.x, event.y); coordinates = table.toControl(coordinates); if (!bounds.contains(coordinates)) { return false; } return true; } public void drop(DropTargetEvent event) { String path = null; if (textTransfer.isSupportedType(event.currentDataType)) { path = (String) event.data; } if (fileTransfer.isSupportedType(event.currentDataType)) { String[] files = (String[]) event.data; path = files[0]; } if (path == null) return; if (!checkLocation(event)) return; TableItem item = (TableItem) event.item; FieldValue field = (FieldValue) item.getData(); if (field != null) { if (field.isFileProperty()) { try { field.set(path); refresh(); } catch (Exception e) { logger.error("Cannot set the field " + field.getName() + " with value " + path, e); } } } } }); } private void createColumns(TableViewer viewer) { TableViewerColumn var = new TableViewerColumn(viewer, SWT.LEFT, 0); var.getColumn().setText("Name"); var.getColumn().setWidth(200); var.setLabelProvider(new EnableIfColumnLabelProvider() { public String getText(Object element) { return ((FieldValue) element).getDisplayName(); } }); var = new TableViewerColumn(viewer, SWT.LEFT, 1); var.getColumn().setText("Value"); var.getColumn().setWidth(300); ColumnLabelProvider prov = new ModelFieldLabelProvider(this); var.setLabelProvider(prov); var.setEditingSupport(new ModelFieldEditingSupport(this, viewer, prov)); } public void setFocus() { viewer.getControl().setFocus(); } public void refresh() { validate(); // Must be first because refresh() then rerenders the values. viewer.refresh(); } @Override public void updateModel(T model) { T old = this.model; this.model = model; validate(); setSelection(new StructuredSelection(model)); setSeriesItem(old, model); } private void setSeriesItem(T old, T model) { // Find a view reference claiming to edit the thing which we changed. IViewReference ref = Arrays.stream(PageUtil.getPage(site).getViewReferences()) .filter(des -> isScanView(des)).findFirst().orElse(null); if (ref != null) { SeriesItemView view = (SeriesItemView) ref.getPart(false); ISeriesItemDescriptor des = view.find(d -> isDescriptor(d, old)); if (des != null) { try { @SuppressWarnings("unchecked") IPointGenerator<Object> gen = (IPointGenerator<Object>) des.getSeriesObject(); gen.setModel(model); } catch (Exception ne) { logger.error("Problem setting the model of a descriptor after the model changed!", ne); } } } } private boolean isScanView(IViewReference des) { IViewPart part = des.getView(false); if (part instanceof SeriesItemView) { SeriesItemView view = (SeriesItemView) part; return view.isSeriesOf(IPointGenerator.class); } return false; } private boolean isDescriptor(ISeriesItemDescriptor des, Object oldModel) { try { IPointGenerator<?> gen = (IPointGenerator<?>) des.getSeriesObject(); return gen.getModel().equals(oldModel); } catch (Exception e) { return false; } } private void validate() { if (validator == null) { validationError = false; } else { try { validator.validate(model); validationError = false; } catch (Exception ne) { validationException = ne instanceof ModelValidationException ? (ModelValidationException) ne : null; if (ne.getMessage() != null) validationMessage.setText(ne.getMessage()); validationError = true; } } GridUtils.setVisible(validationComposite, validationError); validationComposite.getParent().layout(new Control[] { validationComposite }); } @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { processWorkbenchSelection(selection); } private void processWorkbenchSelection(ISelection selection) { if (selection instanceof IStructuredSelection) { Object ob = ((IStructuredSelection) selection).getFirstElement(); processObject(ob); } } private void processObject(Object ob) { if (ob == null) return; try { if (site != null) site.getActionBars().getStatusLineManager().setErrorMessage(null); if (ob instanceof IValidator) setValidator((IValidator<?>) ob); // Special case for device information, we read the latest if (ob instanceof DeviceInformation) { String name = ((DeviceInformation<?>) ob).getName(); if (dservice == null) dservice = ServiceHolder.getEventService().createRemoteService( new URI(CommandConstants.getScanningBrokerUri()), IRunnableDeviceService.class); IRunnableDevice<?> device = dservice.getRunnableDevice(name); setValidator(device); } if (ob instanceof IModelProvider) setModel((T) ((IModelProvider<?>) ob).getModel()); if (ob instanceof IScanPathModel) setModel((T) ob); if (ob instanceof IROI && getModel() instanceof IBoundingBoxModel) { try { BoundingBox box = ScanRegions.createBoxFromPlot(model); ((IBoundingBoxModel) getModel()).setBoundingBox(box); refresh(); } catch (Exception ne) { logger.info("Unable to process box from plot!", ne); } } } catch (Exception ne) { logger.error("Cannot set model for object " + ob, ne); if (site != null) site.getActionBars().getStatusLineManager() .setErrorMessage("Cannot connect to server " + ne.getMessage()); } } /** * Specifically set the operation we would like to edit * @param des */ @SuppressWarnings("unchecked") protected void setValidator(IValidator<?> v) { if (viewer.getTable().isDisposed()) return; this.validator = (IValidator<Object>) v; } @SuppressWarnings("unchecked") @Override public void setModel(T model) throws Exception { if (viewer.getTable().isDisposed()) return; if (viewer.isCellEditorActive()) return; this.model = model; if (save != null) this.save.setModelClass((Class<T>) model.getClass()); if (load != null) this.load.setModelClass((Class<T>) model.getClass()); // Switch UI as appropriate if (typeEditor.isCustomEditor(model)) { GridUtils.setVisible(viewer.getTable(), false); GridUtils.setVisible(typeEditor, true); typeEditor.setModel(model); content.layout(new Control[] { typeEditor }); // Intentionally no viewer.refresh() in this case. } else { GridUtils.setVisible(viewer.getTable(), true); GridUtils.setVisible(typeEditor, false); viewer.setInput(model); content.layout(new Control[] { viewer.getTable() }); } validate(); } public T getModel() { return model; } private IContentProvider createContentProvider() { return new IStructuredContentProvider() { @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public Object[] getElements(Object inputElement) { Object model = null; if (inputElement instanceof IPointGenerator) { IPointGenerator<IScanPathModel> op = (IPointGenerator<IScanPathModel>) inputElement; model = op.getModel(); } else { model = inputElement; } try { final Collection<FieldValue> col = FieldUtils.getModelFields(model); return col.toArray(new FieldValue[col.size()]); } catch (Exception ne) { return new FieldValue[] {}; } } }; } @Override public void addSelectionChangedListener(ISelectionChangedListener listener) { viewer.addSelectionChangedListener(listener); } @Override public ISelection getSelection() { return viewer.getSelection(); } @Override public void removeSelectionChangedListener(ISelectionChangedListener listener) { viewer.removeSelectionChangedListener(listener); } @Override public void setSelection(ISelection selection) { viewer.setSelection(selection); } Composite getTable() { return viewer.getTable(); } public boolean isValidationError() { return validationError; } public boolean isValidationError(FieldValue field) { if (!validationError) return false; if (validationException != null) { return validationException.isField(field); // There is a validation error and this field is it. } return validationError; } public void setValidationError(boolean validationError) { this.validationError = validationError; } @Override public void applyEditorValue() { if (!viewer.isCellEditorActive()) return; viewer.applyEditorValue(); } }