1 package org.simantics.interop.update.editor;
3 import java.util.ArrayList;
4 import java.util.Collection;
5 import java.util.HashSet;
7 import java.util.Stack;
8 import java.util.Map.Entry;
10 import org.eclipse.jface.resource.ImageDescriptor;
11 import org.eclipse.jface.resource.JFaceResources;
12 import org.eclipse.jface.resource.LocalResourceManager;
13 import org.eclipse.jface.viewers.CellEditor;
14 import org.eclipse.jface.viewers.CheckStateChangedEvent;
15 import org.eclipse.jface.viewers.CheckboxCellEditor;
16 import org.eclipse.jface.viewers.CheckboxTreeViewer;
17 import org.eclipse.jface.viewers.ColumnLabelProvider;
18 import org.eclipse.jface.viewers.ColumnViewer;
19 import org.eclipse.jface.viewers.ColumnViewerToolTipSupport;
20 import org.eclipse.jface.viewers.EditingSupport;
21 import org.eclipse.jface.viewers.ICheckStateListener;
22 import org.eclipse.jface.viewers.IStructuredContentProvider;
23 import org.eclipse.jface.viewers.ITreeContentProvider;
24 import org.eclipse.jface.viewers.ITreeViewerListener;
25 import org.eclipse.jface.viewers.TableViewer;
26 import org.eclipse.jface.viewers.TableViewerColumn;
27 import org.eclipse.jface.viewers.TreeExpansionEvent;
28 import org.eclipse.jface.viewers.TreeViewerColumn;
29 import org.eclipse.jface.viewers.Viewer;
30 import org.eclipse.swt.SWT;
31 import org.eclipse.swt.events.SelectionAdapter;
32 import org.eclipse.swt.events.SelectionEvent;
33 import org.eclipse.swt.graphics.Color;
34 import org.eclipse.swt.graphics.Image;
35 import org.eclipse.swt.graphics.RGB;
36 import org.eclipse.swt.layout.FillLayout;
37 import org.eclipse.swt.layout.GridData;
38 import org.eclipse.swt.layout.GridLayout;
39 import org.eclipse.swt.widgets.Button;
40 import org.eclipse.swt.widgets.Composite;
41 import org.eclipse.swt.widgets.Display;
42 import org.eclipse.swt.widgets.Label;
43 import org.eclipse.swt.widgets.Text;
44 import org.simantics.db.ReadGraph;
45 import org.simantics.db.Resource;
46 import org.simantics.db.Session;
47 import org.simantics.db.Statement;
48 import org.simantics.db.WriteGraph;
49 import org.simantics.db.common.request.WriteRequest;
50 import org.simantics.db.exception.DatabaseException;
51 import org.simantics.db.layer0.util.Layer0Utils;
52 import org.simantics.db.request.Read;
53 import org.simantics.interop.test.GraphChanges;
54 import org.simantics.interop.test.GraphComparator;
55 import org.simantics.interop.update.Activator;
56 import org.simantics.interop.update.model.UpdateList;
57 import org.simantics.interop.update.model.UpdateNode;
58 import org.simantics.interop.update.model.UpdateNode.Status;
59 import org.simantics.interop.update.model.UpdateOp;
60 import org.simantics.interop.update.model.UpdateTree;
61 import org.simantics.interop.utils.TableUtils;
62 import org.simantics.ui.SimanticsUI;
63 import org.simantics.utils.datastructures.Callback;
64 import org.simantics.utils.datastructures.Pair;
65 import org.simantics.utils.ui.ExceptionUtils;
69 * Editor for updating models.
71 * @author Marko Luukkainen <marko.luukkainen@vtt.fi>
74 public abstract class ModelUpdateEditor extends Composite{
76 private Composite errorComposite;
78 private CheckboxTreeViewer changeBrowser;
79 private TableViewer changeViewer;
81 private GraphChanges changes;
82 private UpdateTree updateTree;
83 private UpdateList updateList;
85 private GraphChanges changes2;
86 private UpdateTree updateTree2;
87 private UpdateList updateList2;
89 private Button updateAllButton;
90 private Button updateSelectedButton;
92 private LocalResourceManager manager = new LocalResourceManager(JFaceResources.getResources());
94 private Image checked;
95 private Image unchecked;
96 private Image warning;
98 private Color containsColor;
99 private Color deletedColor;
100 private Color addedColor;
103 private HashSet<UpdateNode> selectedStructure = new HashSet<UpdateNode>();
105 private List<ChangeFilter> filters = new ArrayList<ChangeFilter>();
107 public ModelUpdateEditor(Composite parent) {
108 super(parent,SWT.NONE);
109 checked = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/tick.png"));
110 unchecked = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/cross.png"));
111 warning = manager.createImage(Activator.imageDescriptorFromPlugin("com.famfamfam.silk", "icons/error.png"));
114 containsColor = new Color(parent.getDisplay(), new RGB(255,255,220));
115 deletedColor = new Color(parent.getDisplay(), new RGB(255,220,220));
116 addedColor = new Color(parent.getDisplay(), new RGB(220,255,220));
118 this.setLayout(new GridLayout(1,false));
120 errorComposite = new Composite(this, SWT.BORDER);
121 GridData data = new GridData();
122 data.grabExcessHorizontalSpace = true;
123 data.grabExcessVerticalSpace = false;
124 data.horizontalAlignment = SWT.FILL;
125 data.verticalAlignment = SWT.TOP;
126 errorComposite.setLayoutData(data);
127 errorComposite.setLayout(new GridLayout(2, false));
129 errorComposite.setVisible(false);
131 // IEditorInput input = getEditorInput();
132 // if (!(input instanceof UpdateEditorInput)) {
133 // Label label = new Label(composite, SWT.NONE);
134 // label.setText("Unknown input.");
138 Composite fillComposite = new Composite(this, SWT.NONE);
139 data = new GridData();
140 data.grabExcessHorizontalSpace = true;
141 data.grabExcessVerticalSpace = true;
142 data.horizontalAlignment = SWT.FILL;
143 data.verticalAlignment = SWT.FILL;
144 fillComposite.setLayoutData(data);
145 fillComposite.setLayout(new FillLayout(SWT.VERTICAL));
148 changeBrowser = new CheckboxTreeViewer(fillComposite,SWT.MULTI|SWT.V_SCROLL|SWT.BORDER|SWT.FULL_SELECTION );
150 changeBrowser.setContentProvider(new UpdateTreeContentProvider());
152 changeBrowser.getTree().setHeaderVisible(true);
154 ColumnViewerToolTipSupport.enableFor(changeBrowser);
157 TreeViewerColumn dataColumn = TableUtils.addColumn(changeBrowser, "Data", true, 600);
159 dataColumn.setLabelProvider(new UpdateNodeLabelProvider());
161 changeBrowser.addCheckStateListener(new ICheckStateListener() {
164 public void checkStateChanged(CheckStateChangedEvent event) {
165 UpdateNode node = (UpdateNode) event.getElement();
166 if (node.getOp() != null) {
167 node.getOp().select(Boolean.TRUE.equals(event.getChecked()));
174 changeBrowser.addTreeListener(new ITreeViewerListener() {
177 public void treeExpanded(TreeExpansionEvent event) {
178 event.getTreeViewer().getControl().getDisplay().asyncExec(new Runnable() {
182 // TreeViewer uses lazy load, checked states must be updated when the tree is expanded.
190 public void treeCollapsed(TreeExpansionEvent event) {
194 changeBrowser.setUseHashlookup(true);
198 changeViewer = new TableViewer(fillComposite,SWT.MULTI|SWT.V_SCROLL|SWT.BORDER|SWT.FULL_SELECTION);
200 changeViewer.getTable().setHeaderVisible(true);
201 changeViewer.getTable().setLinesVisible(true);
203 changeViewer.setContentProvider(new ModificationListContentProvider());
205 changeViewer.setUseHashlookup(true);
207 TableViewerColumn selection = TableUtils.addColumn(changeViewer, getColumntTitle(0), false, false, 20);
208 TableViewerColumn diagram = TableUtils.addColumn(changeViewer, getColumntTitle(1), true, true, 100);
209 TableViewerColumn symbol = TableUtils.addColumn(changeViewer, getColumntTitle(2), true, true, 100);
210 TableViewerColumn property = TableUtils.addColumn(changeViewer, getColumntTitle(3), true, true, 100);
211 TableViewerColumn oldValue = TableUtils.addColumn(changeViewer, getColumntTitle(4), true, true, 100);
212 TableViewerColumn newValue = TableUtils.addColumn(changeViewer, getColumntTitle(5), true, true, 100);
214 diagram.setLabelProvider(getLabelProvider(1));
215 symbol.setLabelProvider(getLabelProvider(2));
216 property.setLabelProvider(getLabelProvider(3));
217 oldValue.setLabelProvider(getLabelProvider(4));
218 newValue.setLabelProvider(getLabelProvider(5));
220 selection.setLabelProvider(new SelectionLabelProvider());
221 selection.getColumn().addSelectionListener(new SelectionAdapter() {
223 public void widgetSelected(SelectionEvent e) {
224 if (updateList.getChanges().size() > 0) {
225 if (updateList.getSelected().size() > 0) {
226 updateList.clearSelected();
228 for (Pair<Statement, Statement> nr : updateList.getChanges())
229 updateList.addSelected(nr);
231 changeViewer.refresh();
235 selection.setEditingSupport(new SelectionEditingSupport(changeViewer));
238 Composite buttonComposite = new Composite(this, SWT.NONE);
240 data = new GridData();
241 data.grabExcessHorizontalSpace = true;
242 data.grabExcessVerticalSpace = false;
243 data.horizontalAlignment = SWT.FILL;
244 data.verticalAlignment = SWT.BOTTOM;
246 buttonComposite.setLayoutData(data);
248 buttonComposite.setLayout(new GridLayout(3, false));
250 Label label = new Label(buttonComposite, SWT.NONE);
251 data = new GridData();
252 data.grabExcessHorizontalSpace = true;
253 data.grabExcessVerticalSpace = false;
254 data.horizontalAlignment = SWT.FILL;
255 data.verticalAlignment = SWT.CENTER;
256 label.setLayoutData(data);
258 updateAllButton = new Button(buttonComposite, SWT.PUSH);
259 updateAllButton.setText("Update All");
260 updateAllButton.addSelectionListener(new SelectionAdapter() {
262 public void widgetSelected(SelectionEvent e) {
266 updateSelectedButton = new Button(buttonComposite, SWT.PUSH);
267 updateSelectedButton.setText("Update Selected");
268 updateSelectedButton.addSelectionListener(new SelectionAdapter() {
270 public void widgetSelected(SelectionEvent e) {
276 protected Session getSession() {
277 return SimanticsUI.getSession();
280 protected String getColumntTitle(int i) {
295 throw new RuntimeException("Unknown column index" + i);
300 protected abstract ColumnLabelProvider getLabelProvider(int i);
301 protected abstract Pair<GraphComparator,String> getChanges(Resource r1, Resource r2) throws DatabaseException;
302 protected abstract UpdateTree getUpdateTree(GraphChanges changes) throws DatabaseException;
303 protected UpdateList getUpdateList(GraphChanges changes) throws DatabaseException {
304 return new UpdateList(changes.getModifications());
307 protected void addFilters(List<ChangeFilter> filters) {
311 public GraphChanges getChanges() {
315 public UpdateTree getUpdateTree() {
319 public UpdateList getUpdateList() {
323 public CheckboxTreeViewer getChangeBrowser() {
324 return changeBrowser;
327 public TableViewer getChangeViewer() {
331 private void showWarning(String text) {
332 errorComposite.setVisible(true);
334 Label label = new Label(errorComposite, SWT.NONE);
335 label.setImage(warning);
336 label = new Label(errorComposite, SWT.NONE);
338 //this.setStatusMessage("Update contains structural changes (new or deleted symbols), please create a new model.");
343 private List<ICheckStateListener> checkStateListeners = new ArrayList<>();
346 public void addCheckStateListener(ICheckStateListener listener) {
347 checkStateListeners.add(listener);
350 public void removeCheckStateListener(ICheckStateListener listener) {
351 checkStateListeners.remove(listener);
354 public void refreshChecked() {
355 Stack<UpdateNode> nodeStack = new Stack<UpdateNode>();
356 nodeStack.push((UpdateNode)updateTree.getRootNode());
357 while (!nodeStack.isEmpty()) {
358 UpdateNode n = nodeStack.pop();
359 if (n.getOp() != null) {
360 UpdateOp op = n.getOp();
361 if (!op.isAdd() && !op.isDelete()) {
362 changeBrowser.setGrayed(n, true);
363 changeBrowser.setChecked(n, true);
365 boolean applied = op.applied();
367 changeBrowser.setChecked(n, true);
368 changeBrowser.setGrayed(n, true);
369 selectedStructure.remove(n);
371 boolean sel = op.selected();
373 selectedStructure.add(n);
376 selectedStructure.remove(n);
378 changeBrowser.setChecked(n, sel);
379 changeBrowser.setGrayed(n, false);
383 changeBrowser.setGrayed(n, true);
384 changeBrowser.setChecked(n, true);
386 for (UpdateNode c : n.getChildren()) {
387 nodeStack.add((UpdateNode)c);
391 changeBrowser.refresh();
392 for (ICheckStateListener l : checkStateListeners) {
393 l.checkStateChanged(new CheckStateChangedEvent(changeBrowser, null, false));
395 changeViewer.refresh();
400 public void load(UpdateEditorInput uei) {
404 Resource r1 = uei.getR1();
405 Resource r2 = uei.getR2();
406 Resource r3 = uei.getR3();
410 Pair<GraphComparator,String> result = getChanges(r1,r2);
411 GraphComparator comparator = result.first;
412 if (result.second != null)
413 showWarning(result.second);
414 comparator.test(getSession());
415 changes = comparator.getChanges();
416 changes = getSession().syncRequest(new FilterChangesRead(changes));
417 updateTree = getUpdateTree(changes);
418 updateList = getUpdateList(changes);
420 Pair<GraphComparator,String> result2 = getChanges(r1,r3);
421 GraphComparator comparator2 = result2.first;
422 if (result2.second != null)
423 showWarning(result2.second);
424 comparator2.test(getSession());
425 changes2 = comparator2.getChanges();
426 changes2 = getSession().syncRequest(new FilterChangesRead(changes2));
427 updateTree2 = getUpdateTree(changes2);
428 updateList2 = getUpdateList(changes2);
430 createDefaultSelections();
432 } catch (DatabaseException e) {
433 Text text = new Text(this, SWT.MULTI);
434 text.setText(e.getMessage());
444 protected void setInputs() {
445 changeViewer.setInput(updateList.getChanges());
446 changeBrowser.setInput(updateTree);
449 private void applyAll() {
450 updateAllButton.setEnabled(false);
451 updateSelectedButton.setEnabled(false);
452 getSession().asyncRequest(new WriteRequest(){
454 public void perform(WriteGraph graph) throws DatabaseException {
455 Layer0Utils.addCommentMetadata(graph, "Apply all model updates");
456 graph.markUndoPoint();
457 for (Pair<Statement, Statement> mod : updateList.getChanges()) {
458 applyLiteralChange(graph, mod);
462 updateTree.getUpdateOps().applyAll(graph);
464 Display.getDefault().asyncExec(new Runnable() {
469 updateAllButton.setEnabled(true);
470 updateSelectedButton.setEnabled(true);
472 changeViewer.refresh();
478 }, new Callback<DatabaseException>() {
480 public void run(DatabaseException parameter) {
481 if (parameter != null)
482 ExceptionUtils.logAndShowError("Cannot update model", parameter);
487 protected void applyLiteralChange(WriteGraph graph, Pair<Statement, Statement> mod) throws DatabaseException {
488 if (mod.second == null) {
489 graph.deny(mod.first);
492 Resource s = changes.getComparable().getLeft(mod.second.getSubject());
493 Resource pred = mod.second.getPredicate();
494 if (graph.hasValue(mod.second.getObject())) {
495 Object value = graph.getValue(mod.second.getObject());
496 graph.claimLiteral(s, pred, value);
504 private void applySelected() {
505 updateAllButton.setEnabled(false);
506 updateSelectedButton.setEnabled(false);
507 getSession().asyncRequest(new WriteRequest(){
509 public void perform(WriteGraph graph) throws DatabaseException {
510 Layer0Utils.addCommentMetadata(graph, "Apply selected model updates");
511 graph.markUndoPoint();
512 for (Pair<Statement, Statement> mod : updateList.getSelected()) {
513 updateList.removeChange(mod);
514 applyLiteralChange(graph, mod);
517 updateTree.getUpdateOps().applySelected(graph);
519 Display.getDefault().asyncExec(new Runnable() {
523 changeViewer.refresh();
524 updateAllButton.setEnabled(true);
525 updateSelectedButton.setEnabled(true);
536 * 1. Changes that are not essential for model update (changes that can be found when the models are axcatly the same)
537 * 2. Runs custom filters for value changes.
542 * @throws DatabaseException
544 protected GraphChanges filterChanges(ReadGraph g, GraphChanges changes) throws DatabaseException
547 List<Pair<Statement,Statement>> modifications = new ArrayList<Pair<Statement,Statement>>();
549 for (Pair<Statement, Statement> mod : changes.getModifications()) {
551 boolean accept = true;
552 for (ChangeFilter filter : filters) {
553 if (!filter.accept(g, mod)) {
559 modifications.add(mod);
561 GraphChanges newChanges = new GraphChanges(changes.getResource1(),changes.getResource2(),changes.getDeletions(), changes.getAdditions(), modifications, changes.getComparable());
565 public interface ChangeFilter {
566 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException;
570 protected void createDefaultSelections() {
571 for (Entry<Resource, UpdateOp> op : updateTree.getUpdateOps().getResourceMap().entrySet()) {
572 UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
574 op.getValue().select(true);
578 for (Entry<Statement, UpdateOp> op : updateTree.getUpdateOps().getStatementMap().entrySet()) {
579 UpdateOp op2 = updateTree2.getUpdateOps().getUpdateOp(op.getKey());
581 op.getValue().select(true);
585 for (Pair<Statement, Statement> pair : updateList.getChanges()) {
586 if (pair.first != null) {
587 boolean found = false;
588 for (Pair<Statement, Statement> pair2 : updateList2.getChanges()) {
589 if (pair.first.equals(pair2.first)) {
595 updateList.addSelected(pair);
598 updateList.addSelected(pair);
607 * Filters floating point value changes (default filter is set filter when the change is less than 1%)
610 protected class FPValueFilter implements ChangeFilter {
612 private double percentage = 0.01;
614 public FPValueFilter() {
618 public FPValueFilter(double percentage) {
619 if (percentage < 0.0 || percentage > 1.0)
620 throw new IllegalArgumentException("Percentage must be between 0.0 and 1.0.");
621 this.percentage = percentage;
625 public boolean accept(ReadGraph g, Pair<Statement, Statement> change) throws DatabaseException {
626 //filter floating point values that have less than 1% difference.
627 if (!g.hasValue(change.first.getObject()) || !g.hasValue(change.second.getObject()))
629 Object v1 = g.getValue(change.first.getObject());
630 Object v2 = g.getValue(change.second.getObject());
632 if (v1 instanceof Double && v2 instanceof Double) {
633 double d1 = (Double)v1;
634 double d2 = (Double)v2;
635 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
637 } else if (v1 instanceof Float && v2 instanceof Float) {
638 float d1 = (Float)v1;
639 float d2 = (Float)v2;
640 if (Math.abs(d1-d2) / Math.max(Math.abs(d1), Math.abs(d2)) < percentage)
649 private class FilterChangesRead implements Read<GraphChanges> {
650 private GraphChanges changes;
651 public FilterChangesRead(GraphChanges changes) {
652 this.changes = changes;
656 public GraphChanges perform(ReadGraph graph) throws DatabaseException {
657 return filterChanges(graph, changes);
662 private class ModificationListContentProvider implements IStructuredContentProvider {
664 @SuppressWarnings("unchecked")
666 public Object[] getElements(Object inputElement) {
667 if (inputElement == null)
669 Collection<Pair<Statement, Statement>> coll = (Collection<Pair<Statement, Statement>>)inputElement;
670 return coll.toArray();
674 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
679 public void dispose() {
684 private class UpdateTreeContentProvider implements ITreeContentProvider {
686 public Object[] getElements(Object inputElement) {
687 if (inputElement instanceof UpdateTree)
688 return new Object[]{((UpdateTree)inputElement).getRootNode()};
689 if (inputElement instanceof UpdateNode) {
690 UpdateNode node = (UpdateNode)inputElement;
691 return node.getChildren().toArray();
693 return new Object[0];
697 public Object getParent(Object element) {
702 public Object[] getChildren(Object parentElement) {
703 UpdateNode node = (UpdateNode)parentElement;
704 return node.getChildren().toArray();
708 public boolean hasChildren(Object element) {
709 UpdateNode node = (UpdateNode)element;
710 return node.getChildren().size() > 0;
714 public void dispose() {
719 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
724 private class SelectionLabelProvider extends ColumnLabelProvider {
726 public SelectionLabelProvider() {
730 public String getText(Object element) {
735 public Image getImage(Object element) {
736 if (updateList == null)
738 if (updateList.isSelected((Pair<Statement, Statement>) element))
745 private class UpdateNodeLabelProvider extends ColumnLabelProvider {
748 public String getText(Object element) {
749 final UpdateNode node = (UpdateNode)element;
750 return node.getLabel();
754 public Image getImage(Object element) {
755 final UpdateNode node = (UpdateNode)element;
757 ImageDescriptor id = getSession().syncRequest(new Read<ImageDescriptor>() {
759 public ImageDescriptor perform(ReadGraph graph) throws DatabaseException {
760 return node.getImage(graph);
763 return manager.createImage(id);
764 } catch (Exception e) {
770 public String getToolTipText(Object element) {
771 final UpdateNode node = (UpdateNode)element;
772 if (node.getOp() != null) {
773 return node.getOp().toString();
780 public int getToolTipDisplayDelayTime(Object object) {
785 public int getToolTipTimeDisplayed(Object object) {
790 public Color getBackground(Object element) {
791 final UpdateNode node = (UpdateNode)element;
792 Status status = node.getStatus();
793 if (status == Status.CONTAINS)
794 return containsColor;
795 if (status == Status.DELETED)
797 if (status == Status.NEW)
803 private class SelectionEditingSupport extends EditingSupport {
806 @SuppressWarnings("rawtypes")
807 public SelectionEditingSupport(ColumnViewer viewer) {
813 protected boolean canEdit(Object element) {
818 protected CellEditor getCellEditor(Object element) {
819 return new CheckboxCellEditor(null, SWT.CHECK);
823 protected Object getValue(Object element) {
824 if (updateList == null)
826 return updateList.isSelected((Pair<Statement, Statement>) element);
829 @SuppressWarnings("unchecked")
831 protected void setValue(Object element, Object value) {
832 if (updateList == null)
834 if (Boolean.TRUE.equals(value))
835 updateList.addSelected((Pair<Statement, Statement>) element);
837 updateList.removeSelected((Pair<Statement, Statement>) element);
839 getViewer().refresh(element);