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.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;
56 * @author Hannu Niemistö
57 * @author Tuukka Lehtonen <tuukka.lehtonen@semantum.fi>
59 public class AssignSymbolGroup implements ActionFactory, ActionFactory2 {
62 public Runnable create(Collection<?> targets) {
63 final ArrayList<Resource> resources = new ArrayList<Resource>();
64 for (Object target : targets) {
65 if (!(target instanceof Resource))
67 resources.add((Resource) target);
69 return new Runnable() {
72 assignGroups(resources);
79 public Runnable create(Object target) {
80 if(!(target instanceof Resource))
82 final Resource symbol = (Resource)target;
83 return new Runnable() {
86 assignGroups(Collections.singletonList(symbol));
91 private static final SymbolGroup[] NO_SYMBOL_GROUPS = new SymbolGroup[0];
93 static enum Tristate {
96 public static Tristate add(Tristate current, boolean next) {
98 return next ? ALL : NONE;
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;
108 private static class SymbolGroup implements Comparable<SymbolGroup> {
111 Tristate originallySelected;
114 public SymbolGroup(Resource resource, String name, Tristate originallySelected, Tristate selected) {
116 this.resource = resource;
118 this.originallySelected = originallySelected;
119 this.selected = selected;
123 public int compareTo(SymbolGroup o) {
124 return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(name, o.name);
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$
135 private static class ContentProviderImpl implements IStructuredContentProvider {
137 public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
141 public void dispose() {
145 public Object[] getElements(Object inputElement) {
146 return (Object[])inputElement;
150 private static class LabelProviderImpl extends LabelProvider {
152 public String getText(Object element) {
153 return ((SymbolGroup)element).name;
157 private static class CheckStateProviderImpl implements ICheckStateProvider {
159 public boolean isChecked(Object element) {
160 return ((SymbolGroup) element).selected != Tristate.NONE;
163 public boolean isGrayed(Object element) {
164 return ((SymbolGroup) element).selected == Tristate.SOME;
168 private static Resource getCommonModel(final Collection<Resource> symbols) {
170 return Simantics.sync(new UniqueRead<Resource>() {
172 public Resource perform(ReadGraph graph) throws DatabaseException {
173 return getPossibleIndexRoot(graph, symbols);
176 } catch (DatabaseException e) {
177 ErrorLogger.defaultLogError(e);
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);
190 else if (!model.equals(m))
196 private static Resource getIndexRootOf(ReadGraph g, Resource symbol) throws DatabaseException {
197 return g.syncRequest(new PossibleIndexRoot(symbol));
200 private static SymbolGroup[] getSymbolGroups(final Collection<Resource> symbols) {
202 return Simantics.getSession().syncRequest(new Read<SymbolGroup[]>() {
204 public SymbolGroup[] perform(ReadGraph g) throws DatabaseException {
205 return getSymbolGroups(g, symbols);
208 } catch(DatabaseException e) {
210 return NO_SYMBOL_GROUPS;
214 private static SymbolGroup[] getSymbolGroups(ReadGraph g, Collection<Resource> symbols) throws DatabaseException {
215 Resource model = getPossibleIndexRoot(g, symbols);
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(
227 NameUtils.getSafeLabel(g, library),
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()]);
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));
243 return selected != null ? selected : Tristate.NONE;
246 private static SymbolGroup[] selectedElements(SymbolGroup[] symbolGroups) {
248 for(SymbolGroup g : symbolGroups)
249 if(g.selected != Tristate.NONE)
251 SymbolGroup[] result = new SymbolGroup[count];
253 for(SymbolGroup g : symbolGroups)
254 if(g.selected != Tristate.NONE)
259 public void assignGroups(final Collection<Resource> symbols) {
260 if (symbols.isEmpty())
263 final Resource model = getCommonModel(symbols);
265 ShowMessage.showInformation(Messages.AssignSymbolGroup_SameModelRequired, Messages.AssignSymbolGroup_SameModelRequiredMsg);
269 final AtomicReference<SymbolGroup[]> groups =
270 new AtomicReference<SymbolGroup[]>( getSymbolGroups(symbols) );
272 String message = symbols.size() > 1
273 ? NLS.bind(Messages.AssignSymbolGroup_SelectSymbolGroupsTheSelectedSymbolsAreShownIn, symbols.size())
274 : Messages.AssignSymbolGroup_SelectSymbolGroupsTheSelectedSymbolIsShownIn;
276 AssignSymbolGroupsDialog dialog = new AssignSymbolGroupsDialog(
277 PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
279 new ContentProviderImpl(),
280 new LabelProviderImpl(),
281 new CheckStateProviderImpl(),
282 message.toString()) {
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();
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;
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);
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))
324 groups.set( newGroups.toArray(NO_SYMBOL_GROUPS) );
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)
336 if (g.selected != g.originallySelected && g.selected == Tristate.NONE)
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));
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() {
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;
369 if (dialog.open() == Dialog.OK) {
370 String name = dialog.getValue();
372 NewSymbolGroupRequest request = new NewSymbolGroupRequest(name, model);
373 Resource symbolGroup = Simantics.getSession().syncRequest(request);
374 if (symbolGroup == null)
376 return new SymbolGroup(symbolGroup, name, Tristate.NONE, Tristate.NONE);
377 } catch (DatabaseException e) {
378 ErrorLogger.defaultLogError(e);
385 private boolean removeSymbolGroups(Shell shell, final SymbolGroup[] groups) {
386 if (groups.length == 0)
389 if (groups.length == 1)
390 message = NLS.bind(Messages.AssignSymbolGroup_AreYouSureToRemoveSymbolGroup, groups[0].name );
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() {
398 public void perform(WriteGraph graph) throws DatabaseException {
399 for (SymbolGroup group : groups)
400 graph.deny(group.resource);