/******************************************************************************* * Copyright (c) 2012 Association for Decentralized Information Management * in Industry THTH ry. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.modeling.ui.actions; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jface.dialogs.Dialog; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.ICheckStateProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.LabelProvider; import org.eclipse.jface.viewers.Viewer; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.PlatformUI; import org.simantics.Simantics; import org.simantics.db.ReadGraph; import org.simantics.db.Resource; import org.simantics.db.WriteGraph; import org.simantics.db.common.request.PossibleIndexRoot; import org.simantics.db.common.request.UniqueRead; import org.simantics.db.common.request.WriteRequest; import org.simantics.db.common.utils.NameUtils; import org.simantics.db.exception.DatabaseException; import org.simantics.db.layer0.adapter.ActionFactory; import org.simantics.db.layer0.adapter.ActionFactory2; import org.simantics.db.request.Read; import org.simantics.diagram.stubs.DiagramResource; import org.simantics.modeling.AssignSymbolGroupRequest; import org.simantics.modeling.GetSymbolGroups; import org.simantics.modeling.NewSymbolGroupRequest; import org.simantics.utils.strings.AlphanumComparator; import org.simantics.utils.ui.ErrorLogger; import org.simantics.utils.ui.dialogs.ShowMessage; /** * @author Hannu Niemistö * @author Tuukka Lehtonen */ public class AssignSymbolGroup implements ActionFactory, ActionFactory2 { @Override public Runnable create(Collection targets) { final ArrayList resources = new ArrayList(); for (Object target : targets) { if (!(target instanceof Resource)) return null; resources.add((Resource) target); } return new Runnable() { @Override public void run() { assignGroups(resources); } }; } @Override public Runnable create(Object target) { if(!(target instanceof Resource)) return null; final Resource symbol = (Resource)target; return new Runnable() { @Override public void run() { assignGroups(Collections.singletonList(symbol)); } }; } private static final SymbolGroup[] NO_SYMBOL_GROUPS = new SymbolGroup[0]; static enum Tristate { NONE, SOME, ALL; public static Tristate add(Tristate current, boolean next) { if (current == null) return next ? ALL : NONE; switch (current) { case ALL: return next ? ALL : SOME; case SOME: return next ? SOME : SOME; case NONE: return next ? SOME : NONE; default: return NONE; } } } private static class SymbolGroup implements Comparable { Resource resource; String name; Tristate originallySelected; Tristate selected; public SymbolGroup(Resource resource, String name, Tristate originallySelected, Tristate selected) { super(); this.resource = resource; this.name = name; this.originallySelected = originallySelected; this.selected = selected; } @Override public int compareTo(SymbolGroup o) { return AlphanumComparator.CASE_INSENSITIVE_COMPARATOR.compare(name, o.name); } @Override public String toString() { return getClass().getSimpleName() + "[name=" + name //$NON-NLS-1$ + ", originally selected=" + originallySelected //$NON-NLS-1$ + ", selected=" + selected + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } } private static class ContentProviderImpl implements IStructuredContentProvider { @Override public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } @Override public void dispose() { } @Override public Object[] getElements(Object inputElement) { return (Object[])inputElement; } }; private static class LabelProviderImpl extends LabelProvider { @Override public String getText(Object element) { return ((SymbolGroup)element).name; } } private static class CheckStateProviderImpl implements ICheckStateProvider { @Override public boolean isChecked(Object element) { return ((SymbolGroup) element).selected != Tristate.NONE; } @Override public boolean isGrayed(Object element) { return ((SymbolGroup) element).selected == Tristate.SOME; } } private static Resource getCommonModel(final Collection symbols) { try { return Simantics.sync(new UniqueRead() { @Override public Resource perform(ReadGraph graph) throws DatabaseException { return getPossibleIndexRoot(graph, symbols); } }); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return null; } } private static Resource getPossibleIndexRoot(ReadGraph g, Collection symbols) throws DatabaseException { Resource model = null; for (Resource symbol : symbols) { Resource m = getIndexRootOf(g, symbol); if (m == null) return null; if (model == null) model = m; else if (!model.equals(m)) return null; } return model; } private static Resource getIndexRootOf(ReadGraph g, Resource symbol) throws DatabaseException { return g.syncRequest(new PossibleIndexRoot(symbol)); } private static SymbolGroup[] getSymbolGroups(final Collection symbols) { try { return Simantics.getSession().syncRequest(new Read() { @Override public SymbolGroup[] perform(ReadGraph g) throws DatabaseException { return getSymbolGroups(g, symbols); } }); } catch(DatabaseException e) { e.printStackTrace(); return NO_SYMBOL_GROUPS; } } private static SymbolGroup[] getSymbolGroups(ReadGraph g, Collection symbols) throws DatabaseException { Resource model = getPossibleIndexRoot(g, symbols); if (model == null) return NO_SYMBOL_GROUPS; // All symbols have same model. // Resolve the symbol group selection states now. ArrayList result = new ArrayList(); DiagramResource DIA = DiagramResource.getInstance(g); for (Resource library : GetSymbolGroups.getSymbolGroups(g, model)) { Tristate selected = getLibrarySelectionState(g, library, symbols, DIA); selected = selected != null ? selected : Tristate.NONE; result.add( new SymbolGroup( library, NameUtils.getSafeLabel(g, library), selected, selected) ); } //System.out.println("result: " + EString.implode(result)); Collections.sort(result); //System.out.println("sorted result: " + EString.implode(result)); return result.toArray(new SymbolGroup[result.size()]); } protected static Tristate getLibrarySelectionState(ReadGraph graph, Resource library, Collection symbols, DiagramResource DIA) throws DatabaseException { Tristate selected = null; for (Resource symbol : symbols) { selected = Tristate.add(selected, graph.hasStatement(library, DIA.HasSymbol, symbol)); } return selected != null ? selected : Tristate.NONE; } private static SymbolGroup[] selectedElements(SymbolGroup[] symbolGroups) { int count = 0; for(SymbolGroup g : symbolGroups) if(g.selected != Tristate.NONE) ++count; SymbolGroup[] result = new SymbolGroup[count]; count = 0; for(SymbolGroup g : symbolGroups) if(g.selected != Tristate.NONE) result[count++] = g; return result; } public void assignGroups(final Collection symbols) { if (symbols.isEmpty()) return; final Resource model = getCommonModel(symbols); if (model == null) { ShowMessage.showInformation(Messages.AssignSymbolGroup_SameModelRequired, Messages.AssignSymbolGroup_SameModelRequiredMsg); return; } final AtomicReference groups = new AtomicReference( getSymbolGroups(symbols) ); String message = symbols.size() > 1 ? NLS.bind(Messages.AssignSymbolGroup_SelectSymbolGroupsTheSelectedSymbolsAreShownIn, symbols.size()) : Messages.AssignSymbolGroup_SelectSymbolGroupsTheSelectedSymbolIsShownIn; AssignSymbolGroupsDialog dialog = new AssignSymbolGroupsDialog( PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), groups.get(), new ContentProviderImpl(), new LabelProviderImpl(), new CheckStateProviderImpl(), message.toString()) { @Override protected void checkStateChanged(Object[] elements, boolean checked) { for (Object _g : elements) { SymbolGroup g = (SymbolGroup) _g; g.selected = checked ? Tristate.ALL : Tristate.NONE; // Refresh checked states through provider. listViewer.refresh(); } } @Override protected void newAction() { SymbolGroup newGroup = newSymbolGroup(getShell(), model, (SymbolGroup[])inputElement); if (newGroup != null) { // Select the new library by default. newGroup.selected = Tristate.ALL; SymbolGroup[] newGroups = (SymbolGroup[]) inputElement; newGroups = Arrays.copyOf(newGroups, newGroups.length+1); newGroups[newGroups.length-1] = newGroup; Arrays.sort(newGroups); listViewer.setInput(newGroups); inputElement = newGroups; groups.set(newGroups); } } @Override protected void deleteAction(Object[] array) { SymbolGroup[] groupsToRemove = Arrays.copyOf(array, array.length, SymbolGroup[].class); if (removeSymbolGroups(getShell(), groupsToRemove)) { listViewer.remove(groupsToRemove); Set removedGroups = new HashSet(); for (SymbolGroup removed : groupsToRemove) removedGroups.add(removed); List newGroups = new ArrayList(groups.get().length); for (SymbolGroup old : groups.get()) { if (!removedGroups.contains(old)) newGroups.add(old); } groups.set( newGroups.toArray(NO_SYMBOL_GROUPS) ); } } }; dialog.setTitle(Messages.AssignSymbolGroup_SymbolGroupAssignments); dialog.setInitialSelections(selectedElements(groups.get())); if (dialog.open() == Dialog.OK) { final ArrayList added = new ArrayList(); final ArrayList removed = new ArrayList(); for (SymbolGroup g : groups.get()) { if (g.selected != g.originallySelected && g.selected == Tristate.ALL) added.add(g); if (g.selected != g.originallySelected && g.selected == Tristate.NONE) removed.add(g); } if (!added.isEmpty() || !removed.isEmpty()) { ArrayList addedSymbolGroups = new ArrayList(); ArrayList removedSymbolGroups = new ArrayList(); for (SymbolGroup group : added) addedSymbolGroups.add(group.resource); for (SymbolGroup group : removed) removedSymbolGroups.add(group.resource); Simantics.getSession().asyncRequest(new AssignSymbolGroupRequest(addedSymbolGroups, removedSymbolGroups, symbols)); } } } private static SymbolGroup newSymbolGroup(Shell shell, Resource model, final SymbolGroup[] oldGroups) { InputDialog dialog = new InputDialog(shell, Messages.AssignSymbolGroup_NewSymbolGroup, Messages.AssignSymbolGroup_WriteSymbolGroupName, "NewSymbolGroup", //$NON-NLS-1$ new IInputValidator() { @Override public String isValid(String newText) { newText = newText.trim(); if (newText.isEmpty()) return Messages.AssignSymbolGroup_NameMustNotBeEmpty; for (SymbolGroup g : oldGroups) if (newText.equals(g.name)) return Messages.AssignSymbolGroup_GroupSymbolAlreadyExists; return null; } } ); if (dialog.open() == Dialog.OK) { String name = dialog.getValue(); try { NewSymbolGroupRequest request = new NewSymbolGroupRequest(name, model); Resource symbolGroup = Simantics.getSession().syncRequest(request); if (symbolGroup == null) return null; return new SymbolGroup(symbolGroup, name, Tristate.NONE, Tristate.NONE); } catch (DatabaseException e) { ErrorLogger.defaultLogError(e); return null; } } return null; } private boolean removeSymbolGroups(Shell shell, final SymbolGroup[] groups) { if (groups.length == 0) return false; String message; if (groups.length == 1) message = NLS.bind(Messages.AssignSymbolGroup_AreYouSureToRemoveSymbolGroup, groups[0].name ); else message = NLS.bind(Messages.AssignSymbolGroup_AreYouSureToRemoveSymbolGroup1, groups.length ); MessageDialog dialog = new MessageDialog(shell, Messages.AssignSymbolGroup_ConfirmRemoval, null, message, MessageDialog.QUESTION, new String[] { IDialogConstants.OK_LABEL , IDialogConstants.CANCEL_LABEL }, 0); if (dialog.open() == Dialog.OK) { Simantics.getSession().asyncRequest(new WriteRequest() { @Override public void perform(WriteGraph graph) throws DatabaseException { for (SymbolGroup group : groups) graph.deny(group.resource); } }); return true; } else return false; } }