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