/*
* 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.layout;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import analyser.gui.ClassTreeRenderer;
import analyser.gui.FlagIcon;
import andreflect.gui.chart.ClassComponent;
import andreflect.gui.chart.GraphPanel;
import andreflect.gui.chart.PackageComponent;
import com.mxgraph.layout.mxGraphLayout;
import com.mxgraph.model.mxGeometry;
import com.mxgraph.model.mxIGraphModel;
import com.mxgraph.util.mxRectangle;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxGraph;
public class PackageStackLayout extends mxGraphLayout
{
/**
* Specifies the spacing between the cells. Default is 0.
*/
protected int spacing;
/**
* Border to be added if fill is true. Default is 0.
*/
protected int border;
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public PackageStackLayout(mxGraph graph)
{
this(graph, true, graph.getGridSize() / 2, graph.getGridSize());
}
/**
* Constructs a new stack layout layout for the specified graph,
* spacing, orientation and offset.
*/
public PackageStackLayout(mxGraph graph, boolean horizontal, int spacing, int border)
{
super(graph);
this.spacing = spacing;
this.border = border;
}
/**
* Hook for subclassers to return the container size.
*/
public mxRectangle getContainerSize()
{
return new mxRectangle();
}
@Override
public void execute(Object parent)
{
execute(parent, true);
}
public void execute(Object parent, boolean again)
{
if (parent != null)
{
mxIGraphModel model = graph.getModel();
mxGeometry pgeo = model.getGeometry(parent);
int wrap = 0;
if (pgeo == null || parent == graph.getDefaultParent())
{
//System.out.println(" no parent ");
mxRectangle tmp = getContainerSize();
pgeo = new mxGeometry(0, 0, tmp.getWidth(), tmp.getHeight());
wrap = 0; //only change line when type is changed
} else {
PackageComponent parentSplit = getPackageSplit(parent);
//System.out.println(parentSplit.getName());
if (again) {
wrap = (int) pgeo.getWidth();
} else if (parentSplit.hasInnerPackage()) {
wrap = calulateWrapWidthAndSwapWithInnerPackage(parent);
} else {
wrap = calulateWrapWidth(parent);
}
}
// Handles swimlane start size
mxRectangle size = graph.getStartSize(parent);
double x0 = border;
double y0 = size.getHeight() + border;
model.beginUpdate();
try
{
double tmp = 0;
double x_max = 0;
mxGeometry last = null;
Object lastCell = null;
PackageComponent lastPackageSplit = null;
int childCount = model.getChildCount(parent);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isVertexIgnored(child) && isVertexMovable(child))
{
mxGeometry geo = model.getGeometry(child);
if (geo != null)
{
geo = (mxGeometry) geo.clone();
if (last != null)
{
/*if ((model.getValue(lastCell) instanceof PackageSplit))
System.out.println(" "+ "["+((PackageSplit)model.getValue(lastCell)).getName()+"]" + " exec last = "+(last.getX()
+ last.getWidth()) + " thiswidth = " + geo.getWidth() + " check = " + (last.getX()
+ last.getWidth() + geo.getWidth() + 2 * spacing + graph.getGridSize()) + " wrap = " +wrap);*/
boolean change = false;
if (again) {
if (lastPackageSplit != null) {
if (lastPackageSplit.isLast()) {
change = true;
}
} else if ((wrap != 0 && last.getX()
+ last.getWidth() + geo.getWidth() + 2
* spacing + graph.getGridSize() > wrap)
|| isTypeChanged(lastCell, child))
{
change = true;
}
}
else if ((wrap != 0 && last.getX()
+ last.getWidth() + geo.getWidth() + 2
* spacing + graph.getGridSize() > wrap)
|| isTypeChanged(lastCell, child))
{
change = true;
if (lastPackageSplit != null) {
lastPackageSplit.setLast();
}
}
if (change) {
last = null;
y0 += tmp + spacing;
tmp = 0;
}
}
tmp = Math.max(tmp, geo.getHeight());
if (last != null)
{
geo.setX(graph.snap(last.getX() + last.getWidth()
+ spacing + graph.getGridSize() / 2));
}
else
{
geo.setX(x0);
}
geo.setY(y0);
x_max = Math.max(x_max, geo.getX() + geo.getWidth());
model.setGeometry(child, geo);
last = geo;
lastCell = child;
lastPackageSplit = getPackageSplit(child);
}
}
}
if (again == false && lastPackageSplit != null) {
lastPackageSplit.setLast();
}
if (pgeo != null
&& !graph.isCellCollapsed(parent))
{
pgeo = (mxGeometry) pgeo.clone();
pgeo.setWidth(Math.max(x_max + border, getNameWidth(parent) + graph.getGridSize()));
if (last != null) {
pgeo.setHeight(last.getY() + tmp + border);
} else {
pgeo.setHeight(y0 + border);
}
//System.out.println(" final width = " + Math.max(x_max + border, getNameWidth(parent) + graph.getGridSize()) + " height = "+ (last.getY() + tmp+ border));
model.setGeometry(parent, pgeo);
}
} finally
{
model.endUpdate();
}
}
}
private boolean isTypeChanged(Object lastCell, Object child) {
final mxIGraphModel model = graph.getModel();
Object lastValue = model.getValue(lastCell);
Object value = model.getValue(child);
if (lastValue instanceof ClassComponent
&& value instanceof PackageComponent) {
return true;
}
if (lastValue instanceof PackageComponent
&& value instanceof PackageComponent
&& ((PackageComponent) lastValue).isMidlet() == true
&& ((PackageComponent) value).isMidlet() == false) {
return true;
}
return false;
}
private class SortPackage {
Object cell;
int orginalIndex;
public SortPackage(Object cell, int orginalIndex) {
this.cell = cell;
this.orginalIndex = orginalIndex;
}
}
private int calulateWrapWidthAndSwapWithInnerPackage(Object parent) {
ArrayList<Object> children = new ArrayList<Object>();
final mxIGraphModel model = graph.getModel();
int childCount = model.getChildCount(parent);
ArrayList<SortPackage> sortPackages = new ArrayList<SortPackage>();
double maxPackageWidth = 0;
int i;
for (i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isVertexIgnored(child) && isVertexMovable(child))
{
children.add(child);
PackageComponent split = getPackageSplit(child);
if (split != null) {
sortPackages.add(new SortPackage(child, i));
maxPackageWidth = Math.max(maxPackageWidth, model.getGeometry(child).getWidth() + border * 2);
}
}
}
Collections.sort(sortPackages, new Comparator<SortPackage>() {
@Override
public int compare(SortPackage o1, SortPackage o2) {
return o1.orginalIndex - o2.orginalIndex;
}
});
int[] indexes = new int[sortPackages.size()];
i = 0;
for (SortPackage sortPackage : sortPackages) {
indexes[i++] = sortPackage.orginalIndex;
}
Collections.sort(sortPackages, new Comparator<SortPackage>() {
@Override
public int compare(SortPackage o1, SortPackage o2) {
return (int) (model.getGeometry(o1.cell).getHeight() - model.getGeometry(o2.cell).getHeight());
}
});
i = 0;
for (SortPackage sortPackage : sortPackages) {
model.add(parent, sortPackage.cell, indexes[i++]);
}
if (getPackageSplit(parent) != null
&& getPackageSplit(parent).isMidlet()) {
return (int) tryLayout(children, maxPackageWidth, maxPackageWidth * 2, 0.2, 6);
} else {
return (int) tryLayout(children, maxPackageWidth, maxPackageWidth * 2, 0.2, 4);
}
}
private int calulateWrapWidth(Object parent) {
mxIGraphModel model = graph.getModel();
int childCount = model.getChildCount(parent);
double totalLen = 0;
double tmp = 0;
ArrayList<Object> children = new ArrayList<Object>();
int i;
for (i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (!isVertexIgnored(child) && isVertexMovable(child))
{
mxGeometry geo = model.getGeometry(child);
totalLen += geo.getWidth();
totalLen += spacing + graph.getGridSize();
children.add(child);
tmp = Math.max(tmp, geo
.getHeight());
}
}
totalLen += border * 2;
double averageLen = totalLen / childCount;
if (getPackageSplit(parent) != null
&& getPackageSplit(parent).isMidlet()) {
return (int) tryLayout(children, averageLen * 3, totalLen, 0.25, 6);
} else {
return (int) tryLayout(children, averageLen * 2, totalLen, 0.5, 4);
}
}
private double tryLayout(ArrayList<Object> children, double base, double max, double step, double targetRatio) {
double times = 1;
double min_area = 0;
double ret = base;
double height;
double width;
mxIGraphModel model = graph.getModel();
do {
double tmp = 0;
double x_max = 0;
mxGeometry last = null;
Object lastCell = null;
height = 0;
width = 0;
double area;
for (Object child : children)
{
mxGeometry geo = model.getGeometry(child);
if (geo != null)
{
geo = (mxGeometry) geo.clone();
if (last != null)
{
/*System.out.println(" try last = "+(last.getX()
+ last.getWidth()) + " thiswidth = " + geo.getWidth() + " check = " + (last.getX()
+ last.getWidth() + geo.getWidth() + 2
* spacing + graph.getGridSize()) + " wrap = " +wrap );*/
if (last.getX()
+ last.getWidth() + geo.getWidth() + 2
* spacing + graph.getGridSize() > base * times
|| isTypeChanged(lastCell, child)) {
last = null;
height += tmp + spacing;
tmp = 0;
}
}
tmp = Math.max(tmp, geo.getHeight());
if (last != null)
{
geo.setX(graph.snap(last.getX() + last.getWidth()
+ spacing + graph.getGridSize() / 2));
}
else
{
geo.setX(border);
}
x_max = Math.max(x_max, geo.getX() + geo.getWidth());
last = geo;
lastCell = child;
}
}
width = x_max + border;
height += tmp + spacing;
//System.out.println (" base = " + base+ " width = "+ width + " height = "+ height + " ratio = "+ width / height + " target = "+ targetRatio + " area = " + width * height + " min_area = "+ min_area);
area = width * height;
if (min_area == 0
|| area <= min_area * 1.10) {
if (area < min_area
|| min_area == 0) {
min_area = area;
}
if (width / height < targetRatio) {
ret = width;
}
}
times = times + step;
} while (base * times < max && width / height < targetRatio);
//System.out.println(" calculate width = "+ ret);
return ret + 1; //simple for math.round
}
private PackageComponent getPackageSplit(Object cell) {
Object val = graph.getModel().getValue(cell);
if (val instanceof PackageComponent) {
return (PackageComponent) val;
}
return null;
}
private double getNameWidth(Object cell) {
if (getPackageSplit(cell) == null) {
return 0;
}
double scale = 1;
String name = getPackageSplit(cell).getName();
Rectangle fontRect = mxUtils.getSizeForString(name, mxUtils.getFont(graph.getCellStyle(cell)), scale).getRectangle();
FlagIcon im = ClassTreeRenderer.ICON_PACKAGE;
double s = Math.min(fontRect.height / im.getIconHeight(), 1);
int imagewidth = (int) (im.getIconWidth() * s);
return GraphPanel.CHAR_SPACING + imagewidth + GraphPanel.CHAR_SPACING + fontRect.width + GraphPanel.CHAR_SPACING + GraphPanel.FOLDING_ICON.getIconWidth() + GraphPanel.CHAR_SPACING;
}
public void executeLastFill(Object parent) {
if (parent != null)
{
mxIGraphModel model = graph.getModel();
int childCount = model.getChildCount(parent);
mxGeometry pgeo = model.getGeometry(parent);
for (int i = 0; i < childCount; i++)
{
Object child = model.getChildAt(parent, i);
if (pgeo != null && parent != graph.getDefaultParent())
{
if (!isVertexIgnored(child) && isVertexMovable(child))
{
mxGeometry geo = model.getGeometry(child);
if (geo != null)
{
PackageComponent split = getPackageSplit(child);
if (split != null
&& split.isLast()) {
//System.out.println(" " + split.getName()+ " old = " + geo.getWidth() + " new = " + (pgeo.getWidth() - geo.getX() - border - graph.getGridSize()) + " pgeo.getX() = " + pgeo.getX() + " pgeo.getWidth() = " + pgeo.getWidth() + " geo.getX() = "+ geo.getX());
geo.setWidth(Math.max(pgeo.getWidth() - geo.getX() - border - graph.getGridSize(), geo.getWidth()));
}
}
}
}
executeLastFill(model.getChildAt(parent, i));
}
}
}
}