/*
* #!
* Ontopia Vizigator
* #-
* Copyright (C) 2001 - 2013 The Ontopia Project
* #-
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* !#
*/
package net.ontopia.topicmaps.viz;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import net.ontopia.topicmaps.core.AssociationIF;
import net.ontopia.topicmaps.core.TopicNameIF;
import net.ontopia.topicmaps.core.TopicIF;
import net.ontopia.topicmaps.core.TopicMapIF;
import net.ontopia.topicmaps.utils.ScopeUtils;
import net.ontopia.utils.OntopiaRuntimeException;
import com.touchgraph.graphlayout.Node;
import com.touchgraph.graphlayout.TGException;
import com.touchgraph.graphlayout.TGPaintListener;
import com.touchgraph.graphlayout.TGPanel;
/**
* INTERNAL: Node class representing n-ary associations as nodes.
*/
public class TMAssociationNode extends TMAbstractNode
implements VizTMObjectIF, VizTMAssociationIF, TGPaintListener {
// We need to hold our own version of this for GUI display purposes in the
// Vizlet
private int roleCount = 0;
private static final String SHORT_NAME = "http://psi.ontopia.net/basename/" +
"#short-name";
protected static String getAssociationText(AssociationIF association,
boolean displScopedAssocNames,
TopicIF scopingTopic) {
String main = "[No name]";
if (association.getType() != null) {
List scope = new ArrayList(2);
TopicMapIF tm = association.getTopicMap();
TopicIF shortName = (tm == null ? null : tm.getTopicBySubjectIdentifier(VizUtils
.makeLocator(SHORT_NAME)));
if (shortName != null) scope.add(shortName);
if (scopingTopic != null) scope.add(scopingTopic);
Collection bnames = association.getType().getTopicNames();
int nsize = bnames.size();
if (nsize > 0) {
// rank names by scope relevancy
List ranked = ScopeUtils.rankByScope(bnames, scope);
main = ((TopicNameIF)ranked.get(0)).getValue();
// keep track of names already seen
Collection visited = new HashSet(ranked.size());
visited.add(main);
// output remaining names
if (nsize > 1 && displScopedAssocNames) {
String names = "";
boolean first = true;
for (int i=1; i < nsize; i++) {
TopicNameIF element = (TopicNameIF) ranked.get(i);
String name = element.getValue();
if (!visited.contains(name)) {
names += first ? " (" : " / ";
names += name;
visited.add(name);
first = false;
}
}
if (!first)
names += ")";
return main + names;
}
}
}
return main;
}
private AssociationIF association;
private int lineWeight = TMAbstractEdge.DEFAULT_LINE_WEIGHT;
private TopicIF scopingTopic;
private boolean shouldDisplayScopedAssociationNames;
public void setEdgeCount(int visibleEdgeCount) {
visibleEdgeCnt = visibleEdgeCount;
}
public int getEdgeCount() {
return visibleEdgeCnt;
}
public void setRoleCount(int count) {
this.roleCount = count;
}
protected void drawMissingEdgesIndicator(Graphics g, TGPanel tgPanel) {
int hiddenEdgeCount = roleCount - visibleEdgeCount();
if (hiddenEdgeCount <= 0) return;
int ix = (int) drawx;
int iy = (int) drawy;
int h = getHeight();
int w = getWidth();
int tagX = ix + (w - 7) / 2 - 2 + w % 2;
int tagY = iy - h / 2 - 2;
char character;
character = (hiddenEdgeCount < 9) ? (char) ('0' + hiddenEdgeCount) : '*';
paintSmallTag(g, tgPanel, tagX, tagY, Color.red, Color.white, character);
}
public TMAssociationNode(AssociationIF assoc, TopicIF aScopingTopic,
TopicMapView topicMapView) {
super(assoc.getObjectId());
this.association = assoc;
setScopingTopic(aScopingTopic);
this.setLabel("");
this.setType(Node.TYPE_CIRCLE);
this.topicMapView = topicMapView;
}
public void addTo(TGPanel tgpanel) {
try {
tgpanel.addNode(this);
} catch (TGException e) {
throw new OntopiaRuntimeException(e);
}
}
public void deleteFrom(TGPanel tgpanel) {
tgpanel.deleteNode(this);
}
public AssociationIF getAssociation() {
return this.association;
}
public int getLineWeight() {
return this.lineWeight * 2;
}
public String getMainText() {
return getAssociationText(association,
shouldDisplayScopedAssociationNames,
scopingTopic);
}
public List getTargetsFrom(Node find) {
ArrayList targets = new ArrayList(this.edgeCount());
for (Iterator iter = this.getEdges(); iter.hasNext();) {
TMRoleEdge element = (TMRoleEdge) iter.next();
Node target = element.getOtherEndpt(this);
if (!target.equals(find))
targets.add(target);
}
return targets;
}
public TopicIF getTopicMapType() {
return association.getType();
}
public int getWidth() {
if (icon == null)
return this.lineWeight * 2;
return icon.getIconWidth();
}
public boolean isAssociation() {
return true;
}
public boolean isEdge() {
return false;
}
public void paint(Graphics g, TGPanel tgPanel) {
setUnderMouse(tgPanel);
if (underMouse) {
topicMapView.setHighlightNode(this, g);
} else if (tgPanel.getMouseOverN() == null && tgPanel.getSelect() == this) {
topicMapView.setHighlightNode(null, g);
}
miniPaint(g, tgPanel);
}
public void miniPaint(Graphics g, TGPanel tgPanel) {
super.paint(g, tgPanel);
this.drawMissingEdgesIndicator(g, tgPanel);
if (icon != null)
icon.paintIcon(tgPanel, g, (int) drawx - icon.getIconWidth() / 2,
(int) drawy - icon.getIconHeight() / 2);
}
public void paintAfterEdges(Graphics g) {
// Currently do nothing
}
public void paintFirst(Graphics g) {
// Currently do nothing
}
/**
* This is our hover help support. This method is called after all
* other painting has been completed, hence ensuring that ToolTips
* (HoverHelp) is always drawn ontop.
*/
public void paintLast(Graphics g) {
if (underMouse)
this.paintToolTip(g);
}
private void paintToolTip(Graphics g) {
this.paintToolTipText(g, this.getMainText(), (int) drawx, (int) drawy);
}
/**
* @param g -
* The graphic context for the drawing operation.
* @param string -
* The String to be rendered.
* @param x -
* The x coordinate where the String should be positioned.
* @param y -
* The y coordinate where the String should be positioned. NOTE: The
* text is <b>centered </b> over the given coordinates.
*/
private void paintToolTipText(Graphics g, String string, int x, int y) {
g.setFont(this.getFont());
FontMetrics fontMetrics = g.getFontMetrics();
int a = fontMetrics.getAscent();
int h = a + fontMetrics.getDescent();
int w = fontMetrics.stringWidth(string);
int xPosition = x - (w / 2);
int yPosition = y - (h / 2);
// Draw the background
Color c = this.getBackColor();
g.setColor(c);
int r = h / 2;
int vPad = h / 8;
int hPad = h / 4;
g.fillRoundRect(xPosition - hPad, yPosition - vPad, w + (2 * hPad), h
+ (2 * vPad), r, r);
// Draw a defined edge to the popup
g.setColor(TMTopicNode.textColourForBackground(c));
g.drawRoundRect(xPosition - hPad, yPosition - vPad, w + (2 * hPad), h
+ (2 * vPad), r, r);
// Draw the text
g.drawString(string, xPosition, yPosition + a);
}
public boolean represents(Object object) {
return association.equals(object);
}
public void setColor(Color color) {
this.setBackColor(color);
}
public void setLineWeight(int lineWeight) {
this.lineWeight = lineWeight;
}
public void setScopingTopic(TopicIF aTopic) {
scopingTopic = aTopic;
}
public void setShape(int shape) {
// Shape is not applicable for Association Nodes
}
public void setShouldDisplayScopedAssociationNames(boolean newValue) {
shouldDisplayScopedAssociationNames = newValue;
}
public RecoveryObjectIF getDesctructor() {
return new DeleteTMAssociationNode(association);
}
public RecoveryObjectIF getRecreator() {
return new CreateTMAssociationNode(association);
}
}