/*
Copyright 2011-2016 Google Inc. All Rights Reserved.
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 com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.realizers;
import com.google.common.base.Preconditions;
import com.google.security.zynamics.binnavi.CUtilityFunctions;
import com.google.security.zynamics.zylib.general.ListenerProvider;
import com.google.security.zynamics.zylib.gui.zygraph.nodes.ZyNodeData;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IRealizerUpdater;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.IZyNodeRealizerListener;
import com.google.security.zynamics.zylib.gui.zygraph.realizers.ZyLabelContent;
import com.google.security.zynamics.zylib.yfileswrap.gui.zygraph.nodes.ZyGraphNode;
import y.base.Edge;
import y.base.Node;
import y.geom.YPoint;
import y.view.EdgeRealizer;
import y.view.Graph2D;
import y.view.LineType;
import y.view.Port;
import y.view.ShapeNodeRealizer;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
/**
* Realizer that is used to display graph nodes.
*/
public abstract class ZyNodeRealizer<NodeType extends ZyGraphNode<?>> extends ShapeNodeRealizer
implements IZyNodeRealizer {
/**
* User-specific data that is associated with the realizer.
*/
private ZyNodeData<?> m_userData;
/**
* Node updater class that is used to update the content of the realizer.
*/
private IRealizerUpdater<?> m_updater;
/**
* Listeners that are notified about changes in the node realizer.
*/
private final ListenerProvider<IZyNodeRealizerListener<?>> m_listeners = new ListenerProvider<>();
protected boolean m_isHighLighted = false;
private void notifyLocationChanged(final double x, final double y) {
for (final IZyNodeRealizerListener<?> listener : m_listeners) {
try {
listener.changedLocation(this, x, y);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
protected void notifyHasRegenerated() {
for (final IZyNodeRealizerListener<?> listener : m_listeners) {
try {
listener.regenerated(this);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
protected void scalePortCoordinates(
final Node node, final double wOld, final double wNew, final double hOld, final double hNew) {
final Graph2D graph = (Graph2D) node.getGraph();
final double wScaling = wOld > 0 ? wNew / wOld : 1.0d;
final double hScaling = hOld > 0 ? hNew / hOld : 1.0d;
for (Edge e = node.firstOutEdge(); e != null; e = e.nextOutEdge()) {
final EdgeRealizer er = graph.getRealizer(e);
final Port port = er.getSourcePort();
final double px = port.getOffsetX() * wScaling;
final double py = port.getOffsetY() * hScaling;
port.setOffsets(px, py);
graph.setSourcePointRel(e, new YPoint(px, py));
}
for (Edge e = node.firstInEdge(); e != null; e = e.nextInEdge()) {
final EdgeRealizer er = graph.getRealizer(e);
final Port port = er.getTargetPort();
final double px = port.getOffsetX() * wScaling;
final double py = port.getOffsetY() * hScaling;
port.setOffsets(px, py);
graph.setTargetPointRel(e, new YPoint(px, py));
}
}
/**
* Adds a listener that is notified about changes in the node realizer.
*
* @param listener The listener object to add.
*/
@Override
public void addListener(final IZyNodeRealizerListener<?> listener) {
m_listeners.addListener(listener);
}
/**
* Returns the node content that is displayed by the realizer.
*
* @return The node content that is displayed by the realizer.
*/
@Override
public abstract ZyLabelContent getNodeContent();
@Override
public IRealizerUpdater<?> getUpdater() {
return m_updater;
}
/**
* Returns the user data associated with the realizer.
*
* @return The user data associated with the realizer.
*/
@Override
public ZyNodeData<?> getUserData() {
return m_userData;
}
@Override
public void moveBy(final double x, final double y) {
super.moveBy(x, y);
notifyLocationChanged(getX(), getY());
}
@Override
public void paintSloppy(final Graphics2D gfx) {
if (isSelected() || m_isHighLighted) {
final LineType old = getLineType();
setLineType(LineType.LINE_5);
paintFilledShape(gfx);
paintShapeBorder(gfx);
setLineType(old);
} else {
paintFilledShape(gfx);
paintShapeBorder(gfx);
}
}
/**
* Converts a y coordinate into a line number of the node content.
*
* @param y The y coordinate.
* @return The number of the line shown at the y address or -1 if there is no line at the given
* coordinates.
*/
@Override
public int positionToRow(final double y) {
// TODO: This does not really work because line heights are not constant.
final ZyLabelContent content = getNodeContent();
final Rectangle2D contentBounds = getNodeContent().getBounds();
final double yratio = getHeight() / contentBounds.getHeight();
final int row = (int) ((y / yratio - content.getPaddingTop()) / content.getLineHeight());
return row >= content.getLineCount() ? -1 : row;
}
/**
* Regenerates the content of the realizer.
*/
@Override
public void regenerate() {
final ZyLabelContent content = getNodeContent();
final double widthOld = content.getBounds().getWidth();
final double heightOld = content.getBounds().getHeight();
if (m_updater != null) {
m_updater.generateContent(this, content);
}
final Rectangle2D bounds = content.getBounds();
setSize(bounds.getWidth(), bounds.getHeight());
scalePortCoordinates(getNode(), widthOld, bounds.getWidth(), heightOld, bounds.getHeight());
notifyHasRegenerated();
// TODO(jannewger): check if this method is redundant - it seems that client code often calls
// regeneerate followe by a redraw operation. That would mean that we needlessly redraw two
// times.
repaint();
}
/**
* Removes a listener object from the realizer.
*
* @param listener The listener object to be removed.
*/
@Override
public void removeListener(final IZyNodeRealizerListener<?> listener) {
m_listeners.removeListener(listener);
}
@Override
public double rowToPosition(final int row) {
final ZyLabelContent content = getNodeContent();
return content.getPaddingTop() + row * content.getLineHeight();
}
@Override
public void setCenter(final double x, final double y) {
super.setCenter(x, y);
notifyLocationChanged(getX(), getY());
}
@Override
public void setFillColor(final Color color) {
if (super.getFillColor() != color) {
super.setFillColor(color);
updateContentSelectionColor();
}
}
@Override
public void setLineType(final LineType linetype) {
m_isHighLighted = linetype == LineType.LINE_5 || linetype == LineType.DASHED_5
|| linetype == LineType.DOTTED_5 || linetype == LineType.DASHED_DOTTED_5;
super.setLineType(linetype);
}
@Override
public void setLocation(final double x, final double y) {
super.setLocation(x, y);
notifyLocationChanged(x, y);
}
@Override
public void setSelected(final boolean selected) {
if (super.isSelected() != selected) {
super.setSelected(selected);
updateContentSelectionColor();
for (final IZyNodeRealizerListener<?> listener : m_listeners) {
try {
listener.changedSelection(this);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void setSize(final double x, final double y) {
if (super.getX() != x || super.getY() != y) {
super.setSize(x, y);
for (final IZyNodeRealizerListener<?> listener : m_listeners) {
try {
listener.changedSize(this, x, y);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void setUpdater(final IRealizerUpdater<? extends ZyGraphNode<?>> updater) {
m_updater = updater;
if (updater != null) {
updater.setRealizer(this);
}
}
/**
* Sets the user data associated with the realizer.
*
* @param data The new user data object.
*/
@Override
public void setUserData(final ZyNodeData<?> data) {
Preconditions.checkNotNull(data);
m_userData = data;
}
@Override
public void setVisible(final boolean visible) {
if (super.isVisible() != visible) {
super.setVisible(visible);
updateContentSelectionColor();
for (final IZyNodeRealizerListener<?> listener : m_listeners) {
try {
listener.changedVisibility(this);
} catch (final Exception exception) {
CUtilityFunctions.logException(exception);
}
}
}
}
@Override
public void updateContentSelectionColor() {
// TODO: This is kind of a hack, should be improved
final ZyLabelContent content = getNodeContent();
if (content.isSelectable()) {
content.updateContentSelectionColor(getFillColor(), isSelected());
}
}
}