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