/* Alloy Analyzer 4 -- Copyright (c) 2007-2008, Derek Rayside
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
* (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package edu.mit.csail.sdg.alloy4viz;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import edu.mit.csail.sdg.alloy4.ConstList;
import edu.mit.csail.sdg.alloy4.Util;
import edu.mit.csail.sdg.alloy4.ConstList.TempList;
import edu.mit.csail.sdg.alloy4graph.DotColor;
import edu.mit.csail.sdg.alloy4graph.DotPalette;
import edu.mit.csail.sdg.alloy4graph.DotShape;
import static edu.mit.csail.sdg.alloy4graph.DotShape.BOX;
import static edu.mit.csail.sdg.alloy4graph.DotShape.DIAMOND;
import static edu.mit.csail.sdg.alloy4graph.DotShape.TRAPEZOID;
import static edu.mit.csail.sdg.alloy4graph.DotShape.HOUSE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.ELLIPSE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.EGG;
import static edu.mit.csail.sdg.alloy4graph.DotShape.HEXAGON;
import static edu.mit.csail.sdg.alloy4graph.DotShape.OCTAGON;
import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_HOUSE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRAPEZOID;
import static edu.mit.csail.sdg.alloy4graph.DotShape.INV_TRIANGLE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.DOUBLE_OCTAGON;
import static edu.mit.csail.sdg.alloy4graph.DotShape.TRIPLE_OCTAGON;
import static edu.mit.csail.sdg.alloy4graph.DotShape.M_CIRCLE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.M_DIAMOND;
import static edu.mit.csail.sdg.alloy4graph.DotShape.M_SQUARE;
import static edu.mit.csail.sdg.alloy4graph.DotShape.PARALLELOGRAM;
import edu.mit.csail.sdg.alloy4graph.DotStyle;
/** This class implements the automatic visualization inference.
*
* <p><b>Thread Safety:</b> Can be called only by the AWT event thread.
*/
final class MagicColor {
/** The VizState object that we're going to configure. */
private final VizState vizState;
/** Constructor. */
private MagicColor(final VizState vizState) { this.vizState = vizState; }
/** Main method to infer settings. */
public static void magic(final VizState vizState) {
vizState.setNodePalette(DotPalette.MARTHA);
final MagicColor st = new MagicColor(vizState);
st.nodeNames();
st.nodeShape();
st.nodeColour();
st.skolemColour();
}
/** SYNTACTIC/VISUAL: Determine colours for nodes.
*
* when do we color things and what is the meaning of color
* <ul>
* <li> symmetry breaking: colors only matter up to recoloring (diff from
* shape!)
* <li> color substitutes for name/label
* </ul>
*/
private void nodeColour() {
final Set<AlloyType> visibleUserTypes = MagicUtil.visibleUserTypes(vizState);
final Set<AlloyType> uniqueColourTypes;
if (visibleUserTypes.size() <= 5) {
// can give every visible user type its own shape
uniqueColourTypes = visibleUserTypes;
} else {
// give every top-level visible user type its own shape
uniqueColourTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState);
}
int index = 0;
for (final AlloyType t : uniqueColourTypes) {
vizState.nodeColor.put(t, (DotColor) DotColor.valuesWithout(DotColor.MAGIC)[index]);
index = (index + 1) % DotColor.valuesWithout(DotColor.MAGIC).length;
}
}
/** SYNTACTIC/VISUAL: Determine colour highlighting for skolem constants. */
private void skolemColour() {
final Set<AlloySet> sets = vizState.getCurrentModel().getSets();
for (final AlloySet s : sets) {
// change the style
vizState.nodeStyle.put(s, DotStyle.BOLD);
// change the label
String label = vizState.label.get(s);
final int lastUnderscore = label.lastIndexOf('_');
if (lastUnderscore >= 0) {
label = label.substring(lastUnderscore+1);
}
vizState.label.put(s, label);
}
}
/** The list of shape families. */
private static final List<ConstList<DotShape>> families;
static {
TempList<ConstList<DotShape>> list = new TempList<ConstList<DotShape>>();
list.add(Util.asList(BOX, TRAPEZOID, HOUSE));
list.add(Util.asList(ELLIPSE, EGG));
list.add(Util.asList(HEXAGON, OCTAGON, DOUBLE_OCTAGON, TRIPLE_OCTAGON));
list.add(Util.asList(INV_TRIANGLE, INV_HOUSE, INV_TRAPEZOID));
list.add(Util.asList(M_DIAMOND, M_SQUARE, M_CIRCLE));
list.add(Util.asList(PARALLELOGRAM, DIAMOND));
families = list.makeConst();
}
/** SYNTACTIC/VISUAL: Determine shapes for nodes.
* <ul>
* <li> trapezoid, hexagon, rectangle, ellipse, circle, square -- no others
* <li> actual shape matters -- do not break symmetry as with color
* <li> ellipse by default
* <li> circle if special extension of ellipse
* <li> rectangle if lots of attributes
* <li> square if special extension of rectangle
* <li> when to use other shapes?
*
* </ul>
*/
private void nodeShape() {
final Set<List<DotShape>> usedShapeFamilies = new LinkedHashSet<List<DotShape>>();
final Set<AlloyType> topLevelTypes = MagicUtil.partiallyVisibleUserTopLevelTypes(vizState);
for (final AlloyType t : topLevelTypes) {
// get the type family
final Set<AlloyType> subTypes = MagicUtil.visibleSubTypes(vizState, t);
final boolean isTvisible = MagicUtil.isActuallyVisible(vizState, t);
final int size = subTypes.size() + (isTvisible ? 1 : 0);
//log("TopLevelType: " + t + " -- " + subTypes + " " + size);
// match it to a shape family
// 1. look for exact match
boolean foundExactMatch = false;
for (final List<DotShape> shapeFamily: families) {
if (size == shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) {
// found a match!
usedShapeFamilies.add(shapeFamily);
assignNodeShape(t, subTypes, isTvisible, shapeFamily);
foundExactMatch = true;
break;
}
}
if (foundExactMatch) continue;
// 2. look for approximate match
List<DotShape> approxShapeFamily = null;
int approxShapeFamilyDistance = Integer.MAX_VALUE;
for (final List<DotShape> shapeFamily: families) {
if (size <= shapeFamily.size() && !usedShapeFamilies.contains(shapeFamily)) {
// found a potential match
final int distance = shapeFamily.size() - size;
if (distance < approxShapeFamilyDistance) {
// it's a closer fit than the last match, keep it for now
approxShapeFamily = shapeFamily;
approxShapeFamilyDistance = distance;
}
}
}
if (approxShapeFamily != null) {
// use the best approximate match that we just found
usedShapeFamilies.add(approxShapeFamily);
assignNodeShape(t, subTypes, isTvisible, approxShapeFamily);
}
// 3. re-use a shape family matched to something else -- just give up for now
}
}
/** Helper for nodeShape(). */
private void assignNodeShape(final AlloyType t, final Set<AlloyType> subTypes, final boolean isTvisible, final List<DotShape> shapeFamily) {
int index = 0;
// shape for t, if visible
if (isTvisible) {
final DotShape shape = shapeFamily.get(index++);
//log("AssignNodeShape " + t + " " + shape);
vizState.shape.put(t, shape);
}
// shapes for visible subtypes
for (final AlloyType subt : subTypes) {
final DotShape shape = shapeFamily.get(index++);
//log("AssignNodeShape " + subt + " " + shape);
vizState.shape.put(subt, shape);
}
}
/** SYNTACTIC/VISUAL: Should the names of nodes be displayed on them?
*
* when should names be used?
* <ul>
* <li> not when only a single sig (e.g. state machine with only one 'node' sig)
* <li> not when only a single relation
* <li> check for single things _after_ hiding things by default
* </ul>
*/
private void nodeNames() {
final Set<AlloyType> visibleUserTypes = MagicUtil.visibleUserTypes(vizState);
// trim names
for (final AlloyType t : visibleUserTypes) {
// trim label before last slash
MagicUtil.trimLabelBeforeLastSlash(vizState, t);
}
// hide names if there's only one node type visible
if (1 == visibleUserTypes.size()) {
vizState.label.put(visibleUserTypes.iterator().next(), "");
}
}
}