]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.modeling.ui/src/org/simantics/modeling/ui/actions/AssignSymbolGroup.java
Guard against NPE in ConnectionRelationLabelDecorationRule
[simantics/platform.git] / bundles / org.simantics.modeling.ui / src / org / simantics / modeling / ui / actions / AssignSymbolGroup.java
1 /*******************************************************************************
2  * Copyright (c) 2012 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.modeling.ui.actions;
13
14 import java.util.ArrayList;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.Collections;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.concurrent.atomic.AtomicReference;
22
23 import org.eclipse.jface.dialogs.Dialog;
24 import org.eclipse.jface.dialogs.IInputValidator;
25 import org.eclipse.jface.dialogs.InputDialog;
26 import org.eclipse.jface.dialogs.MessageDialog;
27 import org.eclipse.jface.viewers.ICheckStateProvider;
28 import org.eclipse.jface.viewers.IStructuredContentProvider;
29 import org.eclipse.jface.viewers.LabelProvider;
30 import org.eclipse.jface.viewers.Viewer;
31 import org.eclipse.swt.widgets.Shell;
32 import org.eclipse.ui.PlatformUI;
33 import org.simantics.Simantics;
34 import org.simantics.db.ReadGraph;
35 import org.simantics.db.Resource;
36 import org.simantics.db.WriteGraph;
37 import org.simantics.db.common.request.PossibleIndexRoot;
38 import org.simantics.db.common.request.UniqueRead;
39 import org.simantics.db.common.request.WriteRequest;
40 import org.simantics.db.common.utils.NameUtils;
41 import org.simantics.db.exception.DatabaseException;
42 import org.simantics.db.layer0.adapter.ActionFactory;
43 import org.simantics.db.layer0.adapter.ActionFactory2;
44 import org.simantics.db.request.Read;
45 import org.simantics.diagram.stubs.DiagramResource;
46 import org.simantics.modeling.AssignSymbolGroupRequest;
47 import org.simantics.modeling.GetSymbolGroups;
48 import org.simantics.modeling.NewSymbolGroupRequest;
49 import org.simantics.utils.strings.AlphanumComparator;
50 import org.simantics.utils.ui.ErrorLogger;
51 import org.simantics.utils.ui.dialogs.ShowMessage;
52
53 /**
54  * @author Hannu Niemistö
55  * @author Tuukka Lehtonen <tuukka.lehtonen@semantum.fi>
56  */
57 public class AssignSymbolGroup implements ActionFactory, ActionFactory2 {
58
59     @Override
60     public Runnable create(Collection<?> targets) {
61         final ArrayList<Resource> resources = new ArrayList<Resource>();
62         for (Object target : targets) {
63             if (!(target instanceof Resource))
64                 return null;
65             resources.add((Resource) target);
66         }
67         return new Runnable() {
68             @Override
69             public void run() {
70                 assignGroups(resources);
71                 
72             }
73         };
74     }
75
76     @Override
77     public Runnable create(Object target) {
78         if(!(target instanceof Resource))
79             return null;
80         final Resource symbol = (Resource)target;
81         return new Runnable() {
82             @Override
83             public void run() {
84                 assignGroups(Collections.singletonList(symbol));
85             }
86         };
87     }
88
89     private static final SymbolGroup[] NO_SYMBOL_GROUPS = new SymbolGroup[0];
90
91     static enum Tristate {
92         NONE, SOME, ALL;
93
94         public static Tristate add(Tristate current, boolean next) {
95             if (current == null)
96                 return next ? ALL : NONE;
97             switch (current) {
98             case ALL: return next ? ALL : SOME; 
99             case SOME: return next ? SOME : SOME;
100             case NONE: return next ? SOME : NONE;
101             default: return NONE;
102             }
103         }
104     }
105
106     private static class SymbolGroup implements Comparable<SymbolGroup> {
107         Resource resource;
108         String name;
109         Tristate originallySelected;
110         Tristate selected;
111
112         public SymbolGroup(Resource resource, String name, Tristate originallySelected, Tristate selected) {
113             super();
114             this.resource = resource;
115             this.name = name;
116             this.originallySelected = originallySelected;
117             this.selected = selected;
118         }
119
120         @Override
121         public int compareTo(SymbolGroup o) {
122             return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(name, o.name);
123         }
124
125         @Override
126         public String toString() {
127             return getClass().getSimpleName() + "[name=" + name
128                     + ", originally selected=" + originallySelected
129                     + ", selected=" + selected + "]";
130         }
131     }
132
133     private static class ContentProviderImpl implements IStructuredContentProvider {    
134         @Override
135         public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
136         }
137
138         @Override
139         public void dispose() {
140         }
141
142         @Override
143         public Object[] getElements(Object inputElement) {
144             return (Object[])inputElement;
145         }
146     };
147
148     private static class LabelProviderImpl extends LabelProvider {
149         @Override
150         public String getText(Object element) {
151             return ((SymbolGroup)element).name;
152         }
153     }
154
155     private static class CheckStateProviderImpl implements ICheckStateProvider {
156         @Override
157         public boolean isChecked(Object element) {
158             return ((SymbolGroup) element).selected != Tristate.NONE;
159         }
160         @Override
161         public boolean isGrayed(Object element) {
162             return ((SymbolGroup) element).selected == Tristate.SOME;
163         }
164     }
165
166     private static Resource getCommonModel(final Collection<Resource> symbols) {
167         try {
168             return Simantics.sync(new UniqueRead<Resource>() {
169                 @Override
170                 public Resource perform(ReadGraph graph) throws DatabaseException {
171                     return getPossibleIndexRoot(graph, symbols);
172                 }
173             });
174         } catch (DatabaseException e) {
175             ErrorLogger.defaultLogError(e);
176             return null;
177         }
178     }
179
180     private static Resource getPossibleIndexRoot(ReadGraph g, Collection<Resource> symbols) throws DatabaseException {
181         Resource model = null;
182         for (Resource symbol : symbols) {
183             Resource m = getIndexRootOf(g, symbol);
184             if (m == null)
185                 return null;
186             if (model == null)
187                 model = m;
188             else if (!model.equals(m))
189                 return null;
190         }
191         return model;
192     }
193
194     private static Resource getIndexRootOf(ReadGraph g, Resource symbol) throws DatabaseException {
195         return g.syncRequest(new PossibleIndexRoot(symbol));
196     }
197
198     private static SymbolGroup[] getSymbolGroups(final Collection<Resource> symbols) {
199         try {
200             return Simantics.getSession().syncRequest(new Read<SymbolGroup[]>() {
201                 @Override
202                 public SymbolGroup[] perform(ReadGraph g) throws DatabaseException {
203                     return getSymbolGroups(g, symbols);
204                 }
205             });
206         } catch(DatabaseException e) {
207             e.printStackTrace();
208             return NO_SYMBOL_GROUPS;
209         }
210     }
211
212     private static SymbolGroup[] getSymbolGroups(ReadGraph g, Collection<Resource> symbols) throws DatabaseException {
213         Resource model = getPossibleIndexRoot(g, symbols);
214         if (model == null)
215             return NO_SYMBOL_GROUPS;
216         // All symbols have same model.
217         // Resolve the symbol group selection states now.
218         ArrayList<SymbolGroup> result = new ArrayList<SymbolGroup>();
219         DiagramResource DIA = DiagramResource.getInstance(g);
220         for (Resource library : GetSymbolGroups.getSymbolGroups(g, model)) {
221             Tristate selected = getLibrarySelectionState(g, library, symbols, DIA);
222             selected = selected != null ? selected : Tristate.NONE;
223             result.add( new SymbolGroup(
224                     library,
225                     NameUtils.getSafeLabel(g, library),
226                     selected,
227                     selected) );
228         }
229         //System.out.println("result: " + EString.implode(result));
230         Collections.sort(result);
231         //System.out.println("sorted result: " + EString.implode(result));
232         return result.toArray(new SymbolGroup[result.size()]);
233     }
234
235     protected static Tristate getLibrarySelectionState(ReadGraph graph, Resource library,
236             Collection<Resource> symbols, DiagramResource DIA) throws DatabaseException {
237         Tristate selected = null;
238         for (Resource symbol : symbols) {
239             selected = Tristate.add(selected, graph.hasStatement(library, DIA.HasSymbol, symbol));
240         }
241         return selected != null ? selected : Tristate.NONE;
242     }
243
244     private static SymbolGroup[] selectedElements(SymbolGroup[] symbolGroups) {
245         int count = 0;
246         for(SymbolGroup g : symbolGroups)
247             if(g.selected != Tristate.NONE)
248                 ++count;
249         SymbolGroup[] result = new SymbolGroup[count];
250         count = 0;
251         for(SymbolGroup g : symbolGroups)
252             if(g.selected != Tristate.NONE)
253                 result[count++] = g;
254         return result;
255     }
256
257     public void assignGroups(final Collection<Resource> symbols) {
258         if (symbols.isEmpty())
259             return;
260
261         final Resource model = getCommonModel(symbols);
262         if (model == null) {
263             ShowMessage.showInformation("Same Model Required", "All the selected symbols must be from within the same model.");
264             return;
265         }
266
267         final AtomicReference<SymbolGroup[]> groups =
268                 new AtomicReference<SymbolGroup[]>( getSymbolGroups(symbols) );
269
270         StringBuilder message = new StringBuilder();
271         message.append("Select symbol groups the selected ");
272         if (symbols.size() > 1)
273             message.append(symbols.size()).append(" symbols are shown in.");
274         else
275             message.append("symbol is shown in.");
276
277         AssignSymbolGroupsDialog dialog = new AssignSymbolGroupsDialog(
278                 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
279                 groups.get(),
280                 new ContentProviderImpl(), 
281                 new LabelProviderImpl(), 
282                 new CheckStateProviderImpl(),
283                 message.toString()) {
284
285             @Override
286             protected void checkStateChanged(Object[] elements, boolean checked) {
287                 for (Object _g : elements) {
288                     SymbolGroup g = (SymbolGroup) _g;
289                     g.selected = checked ? Tristate.ALL : Tristate.NONE;
290                     // Refresh checked states through provider.
291                     listViewer.refresh();
292                 }
293             }
294
295             @Override
296             protected void newAction() {
297                 SymbolGroup newGroup = newSymbolGroup(getShell(), model, (SymbolGroup[])inputElement);
298                 if (newGroup != null) {
299                     // Select the new library by default.
300                     newGroup.selected = Tristate.ALL;
301
302                     SymbolGroup[] newGroups = (SymbolGroup[]) inputElement;
303                     newGroups = Arrays.copyOf(newGroups, newGroups.length+1);
304                     newGroups[newGroups.length-1] = newGroup;
305                     Arrays.sort(newGroups);
306                     listViewer.setInput(newGroups);
307                     inputElement = newGroups;
308                     groups.set(newGroups);
309                 }
310             }
311
312             @Override
313             protected void deleteAction(Object[] array) {
314                 SymbolGroup[] groupsToRemove = Arrays.copyOf(array, array.length, SymbolGroup[].class);
315                 if (removeSymbolGroups(getShell(), groupsToRemove)) {
316                     listViewer.remove(groupsToRemove);
317                     Set<SymbolGroup> removedGroups = new HashSet<SymbolGroup>();
318                     for (SymbolGroup removed : groupsToRemove)
319                         removedGroups.add(removed);
320                     List<SymbolGroup> newGroups = new ArrayList<SymbolGroup>(groups.get().length);
321                     for (SymbolGroup old : groups.get()) {
322                         if (!removedGroups.contains(old))
323                             newGroups.add(old);
324                     }
325                     groups.set( newGroups.toArray(NO_SYMBOL_GROUPS) );
326                 }
327             }
328         };
329         dialog.setTitle("Symbol Group Assignments");
330         dialog.setInitialSelections(selectedElements(groups.get()));
331         if (dialog.open() == Dialog.OK) {
332             final ArrayList<SymbolGroup> added = new ArrayList<SymbolGroup>();
333             final ArrayList<SymbolGroup> removed = new ArrayList<SymbolGroup>();
334             for (SymbolGroup g : groups.get()) {
335                 if (g.selected != g.originallySelected && g.selected == Tristate.ALL)
336                     added.add(g);
337                 if (g.selected != g.originallySelected && g.selected == Tristate.NONE)
338                     removed.add(g);
339             }
340             if (!added.isEmpty() || !removed.isEmpty()) {
341                 ArrayList<Resource> addedSymbolGroups = new ArrayList<Resource>();
342                 ArrayList<Resource> removedSymbolGroups = new ArrayList<Resource>();
343                 for (SymbolGroup group : added)
344                     addedSymbolGroups.add(group.resource);
345                 for (SymbolGroup group : removed)
346                     removedSymbolGroups.add(group.resource);
347                 Simantics.getSession().asyncRequest(new AssignSymbolGroupRequest(addedSymbolGroups, removedSymbolGroups, symbols));
348             }
349         }
350     }
351
352     private static SymbolGroup newSymbolGroup(Shell shell, Resource model, final SymbolGroup[] oldGroups) {
353         InputDialog dialog = new InputDialog(shell,
354                 "New Symbol Group",
355                 "Write the name of the new symbol group.",
356                 "NewSymbolGroup",
357                 new IInputValidator() {
358                     @Override
359                     public String isValid(String newText) {
360                         newText = newText.trim();
361                         if (newText.isEmpty())
362                             return "The name must be non-empty.";
363                         for (SymbolGroup g : oldGroups)
364                             if (newText.equals(g.name))
365                                 return "A symbol group with that name already exists.";
366                         return null;
367                     }
368                 }
369         );
370         if (dialog.open() == Dialog.OK) {
371             String name = dialog.getValue();
372             try {
373                 NewSymbolGroupRequest request = new NewSymbolGroupRequest(name, model);
374                 Resource symbolGroup = Simantics.getSession().syncRequest(request);
375                 if (symbolGroup == null)
376                     return null;
377                 return new SymbolGroup(symbolGroup, name, Tristate.NONE, Tristate.NONE);
378             } catch (DatabaseException e) {
379                 ErrorLogger.defaultLogError(e);
380                 return null;
381             }
382         }
383         return null;
384     }
385
386     private boolean removeSymbolGroups(Shell shell, final SymbolGroup[] groups) {
387         if (groups.length == 0)
388             return false;
389         String message;
390         if (groups.length == 1)
391             message = "Are you sure you want to remove symbol group '" + groups[0].name + "' ?";
392         else
393             message = "Are you sure you want to remove " + groups.length + " symbol groups?";
394         MessageDialog dialog = 
395             new MessageDialog(shell, "Confirm removal", null, message, MessageDialog.QUESTION, new String[] { "OK", "Cancel" }, 0);
396         if (dialog.open() == Dialog.OK) {
397             Simantics.getSession().asyncRequest(new WriteRequest() {
398                 @Override
399                 public void perform(WriteGraph graph) throws DatabaseException {
400                     for (SymbolGroup group : groups)
401                         graph.deny(group.resource);
402                 }
403             });
404             return true;
405         }
406         else
407             return false;
408     }
409
410 }