/*
* Copyright (C) 2012 Sony Mobile Communications AB
*
* This file is part of ApkAnalyser.
*
* 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 andreflect.gui.chart;
import java.awt.Rectangle;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import analyser.gui.ClassTreeRenderer;
import analyser.gui.FlagIcon;
import analyser.logic.RefClass;
import andreflect.gui.chart.layout.PackageStackLayout;
import com.mxgraph.model.mxCell;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxCellState;
import com.mxgraph.view.mxGraph;
public class CustomGraph extends mxGraph {
/**
* Holds the edge to be used as a template for inserting new edges.
*/
protected Object edgeTemplate;
protected PackageStackLayout layout;
protected HashMap<RefClass, TreeSet<RefClass>> classMap;
/**
* Custom graph that defines the alternate edge style to be used when
* the middle control point of edges is double clicked (flipped).
*/
public CustomGraph() {
super();
setAlternateEdgeStyle("edgeStyle=mxEdgeStyle.ElbowConnector;elbow=vertical");
layout = new PackageStackLayout(this);
classMap = new HashMap<RefClass, TreeSet<RefClass>>();
}
public void doLayoutFirst() {
//first time
doLayoutInner(null, false);
layout.executeLastFill(getDefaultParent());
//second time
doLayoutInner(null, true);
layout.execute(getDefaultParent(), false);
}
public void doLayoutAgain() {
doLayoutInner(null, true);
layout.execute(getDefaultParent(), false);
}
private void doLayoutInner(Object parent, boolean again) {
if (parent == null) {
parent = getDefaultParent();
}
Object[] children = getChildVertices(parent);
for (Object child : children) {
doLayoutInner(child, again);
}
Object parentVal = getModel().getValue(parent);
if (parentVal instanceof PackageComponent) {
layout.execute(parent, again);
}
}
public HashMap<RefClass, TreeSet<RefClass>> getClassMap() {
return classMap;
}
/**
* Sets the edge template to be used to inserting edges.
*/
public void setEdgeTemplate(Object template)
{
edgeTemplate = template;
}
/**
* Prints out some useful information about the cell in the tooltip.
*/
@Override
public String getToolTipForCell(Object cell)
{
String tip = null;
Object value = getModel().getValue(cell);
if (value instanceof ClassComponent) {
ClassComponent classSplit = (ClassComponent) value;
RefClass refClass = classSplit.getRefClass();
//System.out.println(refClass.getName() + " " + classMap.containsKey(refClass) + " " + classMap.get(refClass));
tip = "<html>" + refClass.getName() + " doesn't have any inner class</html>";
if (classMap.containsKey(refClass)
&& classMap.get(refClass) != null
&& classMap.get(refClass).size() != 0) {
TreeSet<RefClass> set = classMap.get(refClass);
tip = "<html>";
for (RefClass refC : set) {
tip += refC.getName() + "<br>";
}
tip += "</html>";
}
}
return tip;
}
/**
* Overrides the method to use the currently selected edge template for
* new edges.
*
* @param graph
* @param parent
* @param id
* @param value
* @param source
* @param target
* @param style
* @return
*/
@Override
public Object createEdge(Object parent, String id, Object value,
Object source, Object target, String style)
{
if (edgeTemplate != null)
{
mxCell edge = (mxCell) cloneCells(new Object[] { edgeTemplate })[0];
edge.setId(id);
return edge;
}
return super.createEdge(parent, id, value, source, target, style);
}
//override to make package as container
@Override
public boolean isSwimlane(Object cell)
{
if (cell != null)
{
if (model.getParent(cell) != model.getRoot())
{
mxCellState state = view.getState(cell);
Map<String, Object> style = (state != null) ? state.getStyle()
: getCellStyle(cell);
if (style != null && !model.isEdge(cell))
{
String shape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
return shape.equals(mxConstants.SHAPE_SWIMLANE)
|| shape.equals(GraphPanel.SHAPE_PACKAGE)
|| shape.equals(GraphPanel.SHAPE_FOLDER);
}
}
}
return false;
}
// ignore model.getChildCount(cell) > 0
@Override
public boolean isCellFoldable(Object cell, boolean collapse)
{
mxCellState state = view.getState(cell);
Map<String, Object> style = (state != null) ? state.getStyle()
: getCellStyle(cell);
return mxUtils.isTrue(style, mxConstants.STYLE_FOLDABLE, true);
}
// calculate the preferred size for each component
@Override
public mxRectangle getPreferredSizeForCell(Object cell)
{
mxRectangle result = null;
if (cell != null)
{
mxCellState state = view.getState(cell);
Map<String, Object> style = (state != null) ? state.getStyle()
: getCellStyle(cell);
if (style != null && !model.isEdge(cell))
{
String styleShape = mxUtils.getString(style, mxConstants.STYLE_SHAPE, "");
if (!(styleShape.equals(GraphPanel.SHAPE_FOLDER)
|| styleShape.equals(GraphPanel.SHAPE_PACKAGE)
|| styleShape.equals(GraphPanel.SHAPE_CLASS)))
{
return super.getPreferredSizeForCell(cell);
}
double scale = 1;
Object val = state.getView().getGraph().getModel().getValue(state.getCell());
String name = null;
if (val instanceof PackageComponent) {
name = ((PackageComponent) val).getName();
} else if (val instanceof ClassComponent) {
name = ((ClassComponent) val).getRefClass().getName();
}
Rectangle fontRect = mxUtils.getSizeForString(name, mxUtils.getFont(state
.getStyle()), scale).getRectangle();
FlagIcon im = ClassTreeRenderer.ICON_PACKAGE;
double s = Math.min(fontRect.height / im.getIconHeight(), 1);
int imagewidth = (int) (im.getIconWidth() * s);
result = new mxRectangle(0, 0, (GraphPanel.CHAR_SPACING + imagewidth + GraphPanel.CHAR_SPACING + fontRect.width + GraphPanel.CHAR_SPACING + GraphPanel.FOLDING_ICON.getIconWidth() + GraphPanel.CHAR_SPACING),
fontRect.height);
}
}
return result;
}
// override the start size for swimlane
@Override
public mxRectangle getStartSize(Object swimlane)
{
mxRectangle result = new mxRectangle();
mxCellState state = view.getState(swimlane, true);
Map<String, Object> style = (state != null) ? state.getStyle()
: getCellStyle(swimlane);
if (style != null)
{
Rectangle fontRect = mxUtils.getSizeForString("com.sonyericsson.abc", mxUtils.getFont(state
.getStyle()), 1).getRectangle();
result.setHeight(fontRect.height);
}
return result;
}
}