/* 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.IGroupNode; 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.Node; import y.base.NodeCursor; import y.base.NodeList; import y.view.Graph2D; import y.view.LineType; import y.view.NodeRealizer; import y.view.ShapeNodeRealizer; import y.view.hierarchy.GroupNodeRealizer; import y.view.hierarchy.HierarchyManager; import java.awt.Color; import java.awt.Graphics2D; import java.awt.geom.Rectangle2D; /** * Realizer class for default rectangular text nodes. * * @param <NodeType> */ public class ZyGroupNodeRealizer<NodeType extends ZyGraphNode<?>> extends GroupNodeRealizer implements IZyNodeRealizer { private static final byte SHADOW_SIZE = 8; private static final Color SHADOW_COLOR = Color.GRAY; /** * Content that is displayed in the realizer. */ private final ZyLabelContent m_content; /** * 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<>(); public ZyGroupNodeRealizer(final ZyLabelContent content, final boolean collapsed) { setShapeType(ShapeNodeRealizer.ROUND_RECT); setLineType(LineType.LINE_2); setDropShadowOffsetX(SHADOW_SIZE); setDropShadowOffsetY(SHADOW_SIZE); setDropShadowColor(SHADOW_COLOR); m_content = content; final Rectangle2D bounds = getNodeContent().getBounds(); setSize(bounds.getWidth(), bounds.getHeight()); setGroupClosed(collapsed); } private void addChildren(final HierarchyManager hm, final Node groupNode, final NodeList childNodes) { for (final NodeCursor nc = hm.getChildren(groupNode); nc.ok(); nc.next()) { final Node n = nc.node(); if (hm.isGroupNode(n)) { addChildren(hm, n, childNodes); } else { childNodes.add(n); } } } private void moveContent(final double dx, final double dy) { final Graph2D graph = (Graph2D) m_userData.getNode().getNode().getGraph(); final HierarchyManager hm = graph.getHierarchyManager(); // Find the children contained in the given group node. final NodeList childNodes = new NodeList(); addChildren(hm, getNode(), childNodes); // Move the children. moveNodes(graph, childNodes.nodes(), dx, dy); } private void moveNodes(final Graph2D graph, final NodeCursor nodes, final double dx, final double dy) { for (; nodes.ok(); nodes.next()) { final NodeRealizer nr = graph.getRealizer(nodes.node()); if ((nr.getAutoBoundsFeature() != null) && nr.getAutoBoundsFeature().isAutoBoundsEnabled()) { continue; } // Only move the node, if it doesn't have the auto resize feature // (enabled). nr.moveBy(dx, dy); } } 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); } } } @Override public void addListener(final IZyNodeRealizerListener<? extends ZyGraphNode<?>> 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 ZyLabelContent getNodeContent() { return m_content; } @Override public NodeRealizer getRealizer() { return this; } @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) { moveContent(x, y); super.moveBy(x, y); notifyLocationChanged(getX(), getY()); } @Override public void paintHotSpots(final Graphics2D g) { return; } @Override public void paintNode(final Graphics2D gfx) { super.paintNode(gfx); final IGroupNode<?, ?> rawNode = (IGroupNode<?, ?>) m_userData.getNode().getRawNode(); if (rawNode.isCollapsed()) { getNodeContent().draw(gfx, getX(), getY() + 8); } } @Override public int positionToRow(final double y) { return 0; } @Override public void regenerate() { m_updater.generateContent(this, m_content); final Rectangle2D bounds = m_content.getBounds(); // For some forsaken reason setSize changes // the position of the node. So we have to // store and restore the position between setting // the size. final double x = getX(); final double y = getY(); setSize(bounds.getWidth(), bounds.getHeight()); setX(x); setY(y); for (final IZyNodeRealizerListener<?> listener : m_listeners) { try { listener.regenerated(this); } catch (final Exception exception) { CUtilityFunctions.logException(exception); } } repaint(); } @Override public void removeListener(final IZyNodeRealizerListener<?> listener) { m_listeners.removeListener(listener); } @Override public double rowToPosition(final int line) { return m_content.getPaddingTop() + (line * m_content.getLineHeight()); } @Override public void setCenter(final double x, final double y) { super.setCenter(x, y); notifyLocationChanged(x, y); } @Override public void setCenterX(final double x) { super.setCenterX(x); notifyLocationChanged(x, y); } @Override public void setFillColor(final Color color) { super.setFillColor(color); updateContentSelectionColor(); } @Override public void setGroupClosed(final boolean collapsed) { super.setGroupClosed(collapsed); if (collapsed) { final Rectangle2D bounds = m_content.getBounds(); setSize(bounds.getWidth(), bounds.getHeight()); } } @Override public void setLocation(final double x, final double y) { super.setLocation(x, y); notifyLocationChanged(x, y); } @Override public void setSelected(final boolean 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 setUpdater(final IRealizerUpdater<?> updater) { m_updater = updater; if (updater != null) { updater.setRealizer(this); } } @Override public void setUserData(final ZyNodeData<?> data) { Preconditions.checkNotNull(data); Preconditions.checkArgument((data.getNode().getRawNode() instanceof IGroupNode), "Error: User data does not contain a group node"); m_userData = data; } @Override public void setVisible(final boolean 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 (timkornau): This is kind of a hack, should be improved if (m_content.isSelectable()) { m_content.updateContentSelectionColor(getFillColor(), isSelected()); } } }