package org.simantics.interop.update.editor; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Stack; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.resource.LocalResourceManager; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.CheckStateChangedEvent; import org.eclipse.jface.viewers.CheckboxCellEditor; import org.eclipse.jface.viewers.CheckboxTreeViewer; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ColumnViewer; import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.ICheckStateListener; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.viewers.TreeViewerColumn; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Text; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.Session; import org.simantics.db.Statement; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.exception.DatabaseException; import org.simantics.db.request.Read; import org.simantics.interop.test.GraphChanges; import org.simantics.interop.update.Activator; import org.simantics.interop.update.model.ModelUpdate; import org.simantics.interop.update.model.ModelUpdate.WarningListener; import org.simantics.interop.update.model.PropertyChange; import org.simantics.interop.update.model.UpdateList; import org.simantics.interop.update.model.UpdateNode; import org.simantics.interop.update.model.UpdateOp; import org.simantics.interop.update.model.UpdateStatus; import org.simantics.interop.update.model.UpdateTree; import org.simantics.interop.utils.TableUtils; import org.simantics.utils.datastructures.Pair; import org.simantics.utils.ui.ExceptionUtils; /** * Editor for updating models. * * @author Marko Luukkainen * */ public abstract class ModelUpdateEditor extends Composite implements WarningListener{ private Composite errorComposite; private CheckboxTreeViewer changeBrowser; private TableViewer changeViewer; private Button updateAllButton; private Button updateSelectedButton; private LocalResourceManager manager = new LocalResourceManager(JFaceResources.getResources()); private Image checked; private Image unchecked; private Image warning; private Color containsColor; private Color deletedColor; private Color addedColor; private Color disabledColor; private ModelUpdate update; private HashSet selectedStructure = new HashSet(); public ModelUpdateEditor(Composite parent) { super(parent,SWT.NONE); checked = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/tick.png")); unchecked = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/cross.png")); warning = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/error.png")); containsColor = new Color(parent.getDisplay(), new RGB(255,255,220)); deletedColor = new Color(parent.getDisplay(), new RGB(255,220,220)); addedColor = new Color(parent.getDisplay(), new RGB(220,255,220)); disabledColor = new Color(parent.getDisplay(), new RGB(128,128,128)); this.setLayout(new GridLayout(1,false)); errorComposite = new Composite(this, SWT.BORDER); GridData data = new GridData(); data.grabExcessHorizontalSpace = true; data.grabExcessVerticalSpace = false; data.horizontalAlignment = SWT.FILL; data.verticalAlignment = SWT.TOP; errorComposite.setLayoutData(data); errorComposite.setLayout(new GridLayout(2, false)); errorComposite.setVisible(false); // IEditorInput input = getEditorInput(); // if (!(input instanceof UpdateEditorInput)) { // Label label = new Label(composite, SWT.NONE); // label.setText("Unknown input."); // return; // } Composite fillComposite = new Composite(this, SWT.NONE); data = new GridData(); data.grabExcessHorizontalSpace = true; data.grabExcessVerticalSpace = true; data.horizontalAlignment = SWT.FILL; data.verticalAlignment = SWT.FILL; fillComposite.setLayoutData(data); fillComposite.setLayout(new FillLayout(SWT.VERTICAL)); { changeBrowser = new CheckboxTreeViewer(fillComposite,SWT.MULTI|SWT.V_SCROLL|SWT.BORDER|SWT.FULL_SELECTION ); changeBrowser.setContentProvider(new UpdateTreeContentProvider()); changeBrowser.getTree().setHeaderVisible(true); ColumnViewerToolTipSupport.enableFor(changeBrowser); TreeViewerColumn dataColumn = TableUtils.addColumn(changeBrowser, "Data", true, 600); dataColumn.setLabelProvider(new UpdateNodeLabelProvider()); changeBrowser.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { UpdateNode node = (UpdateNode) event.getElement(); if (node.getOp() != null) { node.getOp().select(Boolean.TRUE.equals(event.getChecked())); } refreshChecked(); } }); changeBrowser.addTreeListener(new ITreeViewerListener() { @Override public void treeExpanded(TreeExpansionEvent event) { event.getTreeViewer().getControl().getDisplay().asyncExec(new Runnable() { @Override public void run() { // TreeViewer uses lazy load, checked states must be updated when the tree is expanded. refreshChecked(); } }); } @Override public void treeCollapsed(TreeExpansionEvent event) { } }); changeBrowser.setUseHashlookup(true); } { changeViewer = new TableViewer(fillComposite,SWT.MULTI|SWT.V_SCROLL|SWT.BORDER|SWT.FULL_SELECTION); changeViewer.getTable().setHeaderVisible(true); changeViewer.getTable().setLinesVisible(true); changeViewer.setContentProvider(new ModificationListContentProvider()); changeViewer.setUseHashlookup(true); TableViewerColumn cols[] = new TableViewerColumn[getChangeListColumnCount()]; TableViewerColumn selection = TableUtils.addColumn(changeViewer, getColumntTitle(0), false, false, getChangeListColumnWidth(0)); cols[0] = selection; for (int i = 1 ; i < getChangeListColumnCount(); i++) { TableViewerColumn column = TableUtils.addColumn(changeViewer, getColumntTitle(i), true, getChangeListColumnSortable(i), getChangeListColumnWidth(i)); cols[i] = column; column.setLabelProvider(getLabelProvider(i)); configureChangeListColumn(i, column); } selection.setLabelProvider(new SelectionLabelProvider()); selection.getColumn().addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { if (update.getUpdateList().getChanges().size() > 0) { if (update.getUpdateList().getSelected().size() > 0) { update.getUpdateList().clearSelected(); } else { for (PropertyChange nr : update.getUpdateList().getChanges()) nr.select(true); } changeViewer.refresh(); } } }); selection.setEditingSupport(new SelectionEditingSupport(changeViewer)); } Composite buttonComposite = new Composite(this, SWT.NONE); data = new GridData(); data.grabExcessHorizontalSpace = true; data.grabExcessVerticalSpace = false; data.horizontalAlignment = SWT.FILL; data.verticalAlignment = SWT.BOTTOM; buttonComposite.setLayoutData(data); buttonComposite.setLayout(new GridLayout(3, false)); Label label = new Label(buttonComposite, SWT.NONE); data = new GridData(); data.grabExcessHorizontalSpace = true; data.grabExcessVerticalSpace = false; data.horizontalAlignment = SWT.FILL; data.verticalAlignment = SWT.CENTER; label.setLayoutData(data); updateAllButton = new Button(buttonComposite, SWT.PUSH); updateAllButton.setText("Update All"); updateAllButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { applyAll(); } }); updateSelectedButton = new Button(buttonComposite, SWT.PUSH); updateSelectedButton.setText("Update Selected"); updateSelectedButton.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { applySelected(); } }); } protected int getChangeListColumnCount() { return 6; } protected int getChangeListColumnWidth(int col) { if (col == 0) return 20; return 100; } protected boolean getChangeListColumnSortable(int col) { if (col == 0) return false; return true; } protected void configureChangeListColumn(int col, TableViewerColumn column) { } protected Session getSession() { return Simantics.getSession(); } protected String getColumntTitle(int i) { switch (i) { case 0: return "!"; case 1: return "Diagram"; case 2: return "Symbol"; case 3: return "Property"; case 4: return "Old Value"; case 5: return "New Value"; default: throw new RuntimeException("Unknown column index" + i); } } protected abstract ColumnLabelProvider getLabelProvider(int i); public GraphChanges getChanges() { return update.getChanges(); } public UpdateTree getUpdateTree() { return update.getUpdateTree(); } public UpdateList getUpdateList() { return update.getUpdateList(); } public CheckboxTreeViewer getChangeBrowser() { return changeBrowser; } public TableViewer getChangeViewer() { return changeViewer; } public void showWarning(ModelUpdate update, String text) { errorComposite.setVisible(true); Label label = new Label(errorComposite, SWT.NONE); label.setImage(warning); label = new Label(errorComposite, SWT.NONE); label.setText(text); //this.setStatusMessage("Update contains structural changes (new or deleted symbols), please create a new model."); this.layout(true); } private List checkStateListeners = new ArrayList<>(); public void addCheckStateListener(ICheckStateListener listener) { checkStateListeners.add(listener); } public void removeCheckStateListener(ICheckStateListener listener) { checkStateListeners.remove(listener); } public void refreshChecked() { Stack nodeStack = new Stack(); nodeStack.push((UpdateNode)update.getUpdateTree().getRootNode()); while (!nodeStack.isEmpty()) { UpdateNode n = nodeStack.pop(); if (n.getOp() != null) { UpdateOp op = n.getOp(); if (!op.isChange()) { changeBrowser.setGrayed(n, true); changeBrowser.setChecked(n, true); } else { boolean applied = op.applied(); if (applied) { changeBrowser.setChecked(n, true); changeBrowser.setGrayed(n, true); selectedStructure.remove(n); } else { boolean sel = op.selected(); if (sel) { selectedStructure.add(n); } else { selectedStructure.remove(n); } changeBrowser.setChecked(n, sel); changeBrowser.setGrayed(n, !op.enabled()); } } } else { changeBrowser.setGrayed(n, true); changeBrowser.setChecked(n, true); } for (UpdateNode c : n.getChildren()) { nodeStack.add((UpdateNode)c); } } changeBrowser.refresh(); for (ICheckStateListener l : checkStateListeners) { l.checkStateChanged(new CheckStateChangedEvent(changeBrowser, null, false)); } changeViewer.refresh(); } protected abstract ModelUpdate createUpdate(); public void load(UpdateEditorInput uei) { Resource oldModel = uei.getR1(); // old model that is being updated, contains user made changes Resource newModel = uei.getR2(); // new model, Resource originalModel = uei.getR3(); // original old model without user made changes boolean newDistinct = uei.isNewDistinct(); try { if (update != null) update.removeListener(this); update = createUpdate(); update.addListener(this); update.setInput(oldModel, newModel, originalModel, newDistinct); } catch (DatabaseException e) { Text text = new Text(this, SWT.MULTI); text.setText(e.getMessage()); e.printStackTrace(); return; } setInputs(); refreshChecked(); } protected void setInputs() { changeViewer.setInput(update.getUpdateList().getChanges()); changeBrowser.setInput(update.getUpdateTree()); } private void applyAll() { updateAllButton.setEnabled(false); updateSelectedButton.setEnabled(false); getSession().asyncRequest(new WriteRequest(){ @Override public void perform(WriteGraph graph) throws DatabaseException { update.applyAll(graph); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { updateAllButton.setEnabled(true); updateSelectedButton.setEnabled(true); refreshChecked(); changeViewer.refresh(); } }); } }, e -> { if (e != null) ExceptionUtils.logAndShowError("Cannot update model", e); }); } private void applySelected() { updateAllButton.setEnabled(false); updateSelectedButton.setEnabled(false); getSession().asyncRequest(new WriteRequest(){ @Override public void perform(WriteGraph graph) throws DatabaseException { update.applySelected(graph); Display.getDefault().asyncExec(new Runnable() { @Override public void run() { changeViewer.refresh(); updateAllButton.setEnabled(true); updateSelectedButton.setEnabled(true); refreshChecked(); } }); } }); } private class ModificationListContentProvider implements IStructuredContentProvider { @SuppressWarnings("unchecked") @Override public Object[] getElements(Object inputElement) { if (inputElement == null) return null; Collection> coll = (Collection>)inputElement; return coll.toArray(); } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public void dispose() { } } private class UpdateTreeContentProvider implements ITreeContentProvider { @Override public Object[] getElements(Object inputElement) { if (inputElement instanceof UpdateTree) return new Object[]{((UpdateTree)inputElement).getRootNode()}; if (inputElement instanceof UpdateNode) { UpdateNode node = (UpdateNode)inputElement; return node.getChildren().toArray(); } return new Object[0]; } @Override public Object getParent(Object element) { return null; } @Override public Object[] getChildren(Object parentElement) { UpdateNode node = (UpdateNode)parentElement; return node.getChildren().toArray(); } @Override public boolean hasChildren(Object element) { UpdateNode node = (UpdateNode)element; return node.getChildren().size() > 0; } @Override public void dispose() { } @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } } private class SelectionLabelProvider extends ColumnLabelProvider { public SelectionLabelProvider() { } @Override public String getText(Object element) { return ""; } @Override public Image getImage(Object element) { if (update == null || !update.isInit()) return null; PropertyChange pc = (PropertyChange)element; if (pc.selected()) return checked; else return unchecked; } @Override public Color getForeground(Object element) { PropertyChange pc = (PropertyChange)element; if (!pc.enabled()) return disabledColor; return null; } } protected abstract class PropertyChangeLabelProvider extends ColumnLabelProvider { public PropertyChangeLabelProvider() { } @Override public Color getForeground(Object element) { PropertyChange pc = (PropertyChange)element; if (!pc.enabled()) return disabledColor; return null; } } private class UpdateNodeLabelProvider extends ColumnLabelProvider { @Override public String getText(Object element) { final UpdateNode node = (UpdateNode)element; return node.getLabel(); } @Override public Image getImage(Object element) { final UpdateNode node = (UpdateNode)element; try { ImageDescriptor id = getSession().syncRequest(new Read() { @Override public ImageDescriptor perform(ReadGraph graph) throws DatabaseException { return node.getImage(graph); } }); return manager.createImage(id); } catch (Exception e) { return null; } } @Override public String getToolTipText(Object element) { final UpdateNode node = (UpdateNode)element; if (node.getOp() != null) { return node.getOp().toString(); } else { return null; } } @Override public int getToolTipDisplayDelayTime(Object object) { return 1000; } @Override public int getToolTipTimeDisplayed(Object object) { return 10000; } @Override public Color getBackground(Object element) { final UpdateNode node = (UpdateNode)element; UpdateStatus status = node.getStatus(); if (status == UpdateStatus.CONTAINS) return containsColor; if (status == UpdateStatus.DELETED) return deletedColor; if (status == UpdateStatus.NEW) return addedColor; return null; } @Override public Color getForeground(Object element) { final UpdateNode node = (UpdateNode)element; if (node.getOp() != null && !node.getOp().enabled()) return disabledColor; return null; } } private class SelectionEditingSupport extends EditingSupport { public SelectionEditingSupport(ColumnViewer viewer) { super(viewer); } @Override protected boolean canEdit(Object element) { return true; } @Override protected CellEditor getCellEditor(Object element) { return new CheckboxCellEditor(null, SWT.CHECK); } @Override protected Object getValue(Object element) { if (update == null || !update.isInit()) return false; PropertyChange pc = (PropertyChange)element; return pc.selected(); } @Override protected void setValue(Object element, Object value) { if (update == null || !update.isInit()) return; PropertyChange pc = (PropertyChange)element; if (Boolean.TRUE.equals(value)) pc.select(true); else pc.select(false); getViewer().refresh(element); } } }