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
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.modeling.ui.actions;
\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
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
54 * @author Hannu Niemistö
\r
55 * @author Tuukka Lehtonen <tuukka.lehtonen@semantum.fi>
\r
57 public class AssignSymbolGroup implements ActionFactory, ActionFactory2 {
\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
65 resources.add((Resource) target);
\r
67 return new Runnable() {
\r
70 assignGroups(resources);
\r
77 public Runnable create(Object target) {
\r
78 if(!(target instanceof Resource))
\r
80 final Resource symbol = (Resource)target;
\r
81 return new Runnable() {
\r
84 assignGroups(Collections.singletonList(symbol));
\r
89 private static final SymbolGroup[] NO_SYMBOL_GROUPS = new SymbolGroup[0];
\r
91 static enum Tristate {
\r
94 public static Tristate add(Tristate current, boolean next) {
\r
95 if (current == null)
\r
96 return next ? ALL : NONE;
\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
106 private static class SymbolGroup implements Comparable<SymbolGroup> {
\r
109 Tristate originallySelected;
\r
112 public SymbolGroup(Resource resource, String name, Tristate originallySelected, Tristate selected) {
\r
114 this.resource = resource;
\r
116 this.originallySelected = originallySelected;
\r
117 this.selected = selected;
\r
121 public int compareTo(SymbolGroup o) {
\r
122 return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(name, o.name);
\r
126 public String toString() {
\r
127 return getClass().getSimpleName() + "[name=" + name
\r
128 + ", originally selected=" + originallySelected
\r
129 + ", selected=" + selected + "]";
\r
133 private static class ContentProviderImpl implements IStructuredContentProvider {
\r
135 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
\r
139 public void dispose() {
\r
143 public Object[] getElements(Object inputElement) {
\r
144 return (Object[])inputElement;
\r
148 private static class LabelProviderImpl extends LabelProvider {
\r
150 public String getText(Object element) {
\r
151 return ((SymbolGroup)element).name;
\r
155 private static class CheckStateProviderImpl implements ICheckStateProvider {
\r
157 public boolean isChecked(Object element) {
\r
158 return ((SymbolGroup) element).selected != Tristate.NONE;
\r
161 public boolean isGrayed(Object element) {
\r
162 return ((SymbolGroup) element).selected == Tristate.SOME;
\r
166 private static Resource getCommonModel(final Collection<Resource> symbols) {
\r
168 return Simantics.sync(new UniqueRead<Resource>() {
\r
170 public Resource perform(ReadGraph graph) throws DatabaseException {
\r
171 return getPossibleIndexRoot(graph, symbols);
\r
174 } catch (DatabaseException e) {
\r
175 ErrorLogger.defaultLogError(e);
\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
188 else if (!model.equals(m))
\r
194 private static Resource getIndexRootOf(ReadGraph g, Resource symbol) throws DatabaseException {
\r
195 return g.syncRequest(new PossibleIndexRoot(symbol));
\r
198 private static SymbolGroup[] getSymbolGroups(final Collection<Resource> symbols) {
\r
200 return Simantics.getSession().syncRequest(new Read<SymbolGroup[]>() {
\r
202 public SymbolGroup[] perform(ReadGraph g) throws DatabaseException {
\r
203 return getSymbolGroups(g, symbols);
\r
206 } catch(DatabaseException e) {
\r
207 e.printStackTrace();
\r
208 return NO_SYMBOL_GROUPS;
\r
212 private static SymbolGroup[] getSymbolGroups(ReadGraph g, Collection<Resource> symbols) throws DatabaseException {
\r
213 Resource model = getPossibleIndexRoot(g, symbols);
\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
225 NameUtils.getSafeLabel(g, library),
\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
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
241 return selected != null ? selected : Tristate.NONE;
\r
244 private static SymbolGroup[] selectedElements(SymbolGroup[] symbolGroups) {
\r
246 for(SymbolGroup g : symbolGroups)
\r
247 if(g.selected != Tristate.NONE)
\r
249 SymbolGroup[] result = new SymbolGroup[count];
\r
251 for(SymbolGroup g : symbolGroups)
\r
252 if(g.selected != Tristate.NONE)
\r
253 result[count++] = g;
\r
257 public void assignGroups(final Collection<Resource> symbols) {
\r
258 if (symbols.isEmpty())
\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
267 final AtomicReference<SymbolGroup[]> groups =
\r
268 new AtomicReference<SymbolGroup[]>( getSymbolGroups(symbols) );
\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
275 message.append("symbol is shown in.");
\r
277 AssignSymbolGroupsDialog dialog = new AssignSymbolGroupsDialog(
\r
278 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
\r
280 new ContentProviderImpl(),
\r
281 new LabelProviderImpl(),
\r
282 new CheckStateProviderImpl(),
\r
283 message.toString()) {
\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
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
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
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
325 groups.set( newGroups.toArray(NO_SYMBOL_GROUPS) );
\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
337 if (g.selected != g.originallySelected && g.selected == Tristate.NONE)
\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
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
357 new IInputValidator() {
\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
370 if (dialog.open() == Dialog.OK) {
\r
371 String name = dialog.getValue();
\r
373 NewSymbolGroupRequest request = new NewSymbolGroupRequest(name, model);
\r
374 Resource symbolGroup = Simantics.getSession().syncRequest(request);
\r
375 if (symbolGroup == null)
\r
377 return new SymbolGroup(symbolGroup, name, Tristate.NONE, Tristate.NONE);
\r
378 } catch (DatabaseException e) {
\r
379 ErrorLogger.defaultLogError(e);
\r
386 private boolean removeSymbolGroups(Shell shell, final SymbolGroup[] groups) {
\r
387 if (groups.length == 0)
\r
390 if (groups.length == 1)
\r
391 message = "Are you sure you want to remove symbol group '" + groups[0].name + "' ?";
\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
399 public void perform(WriteGraph graph) throws DatabaseException {
\r
400 for (SymbolGroup group : groups)
\r
401 graph.deny(group.resource);
\r