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