/*
* Freeplane - mind map editor
* Copyright (C) 2008 Dimitry Polivaev
*
* This file author is Dimitry Polivaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.view.swing.map;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Point;
import javax.swing.JComponent;
import org.freeplane.features.cloud.CloudModel;
import org.freeplane.features.map.NodeModel;
import org.freeplane.features.mode.ModeController;
import org.freeplane.features.nodelocation.LocationModel;
import org.freeplane.features.nodestyle.NodeStyleController;
import org.freeplane.view.swing.map.cloud.CloudView;
abstract public class NodeViewLayoutAdapter implements INodeViewLayout {
protected static class LayoutData{
final int[] lx;
final int[] ly;
final boolean[] free;
final boolean[] summary;
int left;
int childContentHeight;
int top;
boolean rightDataSet;
boolean leftDataSet;
public LayoutData(int childCount) {
super();
this.lx = new int[childCount];
this.ly = new int[childCount];
this.free = new boolean[childCount];
this.summary = new boolean[childCount];
this.left = 0;
this.childContentHeight = 0;
this.top = 0;
rightDataSet = false;
leftDataSet = false;
}
}
private static Dimension minDimension;
private int childCount;
private JComponent content;
protected Point location = new Point();
private NodeModel model;
private int spaceAround;
private int vGap;
private NodeView view;
private int contentWidth;
private int contentHeight;
private int cloudHeight;
public void addLayoutComponent(final String arg0, final Component arg1) {
}
/**
* @return Returns the childCount.
*/
protected int getChildCount() {
return childCount;
}
/**
* @return Returns the content.
*/
protected JComponent getContent() {
return content;
}
/**
* @return Returns the model.
*/
protected NodeModel getModel() {
return model;
}
/**
* @return Returns the spaceAround.
*/
int getSpaceAround() {
return spaceAround;
}
/**
* @return Returns the vGap.
*/
int getVGap() {
return vGap;
}
/**
* @return Returns the view.
*/
protected NodeView getView() {
return view;
}
abstract protected void layout();
public void layoutContainer(final Container c) {
if(setUp(c)){
layout();
}
shutDown();
}
/*
* (non-Javadoc)
* @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
*/
public Dimension minimumLayoutSize(final Container arg0) {
if (NodeViewLayoutAdapter.minDimension == null) {
NodeViewLayoutAdapter.minDimension = new Dimension(0, 0);
}
return NodeViewLayoutAdapter.minDimension;
}
/*
* (non-Javadoc)
* @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
*/
public Dimension preferredLayoutSize(final Container c) {
if (!c.isValid()) {
c.validate();
}
return c.getSize();
}
/*
* (non-Javadoc)
* @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
*/
public void removeLayoutComponent(final Component arg0) {
}
protected boolean setUp(final Container c) {
final NodeView localView = (NodeView) c;
JComponent content = localView.getContent();
if(content == null)
return false;
final int localChildCount = localView.getComponentCount() - 1;
for (int i = 0; i < localChildCount; i++) {
final Component component = localView.getComponent(i);
((NodeView) component).validateTree();
}
this.content = content;
view = localView;
model = localView.getModel();
childCount = localChildCount;
if (getModel().isVisible()) {
setVGap(getView().getVGap());
}
else {
setVGap(getView().getVisibleParentView().getVGap());
}
spaceAround = view.getSpaceAround();
if (view.isContentVisible()) {
final Dimension contentSize = calculateContentSize(view);
contentWidth = contentSize.width;
contentHeight = contentSize.height;
cloudHeight = getAdditionalCloudHeigth(view);
}
else {
contentHeight = 0;
contentWidth = 0;
cloudHeight = 0;
}
return true;
}
protected Dimension calculateContentSize(final NodeView view) {
final JComponent content = view.getContent();
final ModeController modeController = view.getMap().getModeController();
final NodeStyleController nsc = NodeStyleController.getController(modeController);
Dimension contentSize;
if (content instanceof ZoomableLabel){
int maxNodeWidth = nsc.getMaxWidth(view.getModel());
contentSize= ((ZoomableLabel)content).getPreferredSize(maxNodeWidth);
}
else{
contentSize= content.getPreferredSize();
}
int minNodeWidth = nsc.getMinWidth(view.getModel());
int contentWidth = Math.max(view.getZoomed(minNodeWidth),contentSize.width);
int contentHeight = contentSize.height;
final Dimension contentProfSize = new Dimension(contentWidth, contentHeight);
return contentProfSize;
}
protected void shutDown() {
view = null;
model = null;
content = null;
childCount = 0;
setVGap(0);
spaceAround = 0;
}
public void setVGap(final int vGap) {
this.vGap = vGap;
}
/**
* Calculates the tree height increment because of the clouds.
*/
public int getAdditionalCloudHeigth(final NodeView node) {
if (!node.isContentVisible()) {
return 0;
}
final CloudModel cloud = node.getCloudModel();
if (cloud != null) {
return CloudView.getAdditionalHeigth(cloud, node);
}
else {
return 0;
}
}
protected void calcLayout(final boolean isLeft, final LayoutData data) {
int highestSummaryLevel = 1;
int level = 1;
for (int i = 0; i < getChildCount(); i++) {
final NodeView child = (NodeView) getView().getComponent(i);
if (child.isLeft() != isLeft) {
continue;
}
if(child.isSummary()){
level++;
highestSummaryLevel = Math.max(highestSummaryLevel, level);
}
else{
level = 1;
}
}
int left = 0;
int y = 0;
int childContentHeightSum = 0;
int visibleChildCounter = 0;
boolean useSummaryAsItem = true;
int top = 0;
final int[] groupStart = new int[highestSummaryLevel];
final int[] groupStartContentHeightSum = new int[highestSummaryLevel];
final int[] groupStartY = new int[highestSummaryLevel];
final int[] groupEndY = new int[highestSummaryLevel];
final int summaryBaseX[] = new int[highestSummaryLevel];
level = highestSummaryLevel;
for (int i = 0; i < getChildCount(); i++) {
final NodeView child = (NodeView) getView().getComponent(i);
if (child.isLeft() != isLeft) {
continue;
}
final boolean isSummary = child.isSummary();
final boolean isItem = !isSummary || useSummaryAsItem;
final int oldLevel = level;
if(isItem){
if(level > 0)
useSummaryAsItem = true;
level = 0;
}
else{
level++;
}
final int childCloudHeigth = getAdditionalCloudHeigth(child);
final int childContentHeight = child.getContent().getHeight() + childCloudHeigth;
final int childShiftY = child.isContentVisible() ? child.getShift() : 0;
final int childContentShift = child.getContent().getY() -childCloudHeigth/2 - getSpaceAround();
int childHGap;
if(child.isContentVisible())
childHGap = child.getHGap();
else if(child.isSummary())
childHGap = child.getZoomed(LocationModel.HGAP);
else
childHGap = 0;
final int childHeight = child.getHeight() - 2 * getSpaceAround();
boolean isFreeNode = child.isFree();
data.free[i] = isFreeNode;
data.summary[i] = ! isItem;
if(isItem) {
if (isFreeNode){
data.ly[i] = childShiftY - childContentShift-childCloudHeigth/2 - getSpaceAround();
}
else{
if (childShiftY < 0 || visibleChildCounter == 0) {
top += childShiftY;
}
top -= childContentShift;
top += child.getTopOverlap();
y -= child.getTopOverlap();
if (childShiftY < 0) {
data.ly[i] = y;
y -= childShiftY;
}
else {
if(visibleChildCounter > 0)
y += childShiftY;
data.ly[i] = y;
}
if (childHeight != 0) {
y += childHeight + getVGap();
y -= child.getBottomOverlap();
}
childContentHeightSum += childContentHeight;
if(oldLevel > 0){
summaryBaseX[0] = 0;
for(int j = 0; j < oldLevel; j++){
groupStart[j] = i;
groupStartY[j] = Integer.MAX_VALUE;
groupEndY[j] = Integer.MIN_VALUE;
groupStartContentHeightSum[j] = childContentHeightSum;
}
}
else if(child.isFirstGroupNode()){
groupStartContentHeightSum[0] = childContentHeightSum;
summaryBaseX[0] = 0;
groupStart[0] = i;
}
if (childHeight != 0) {
if (visibleChildCounter > 0)
childContentHeightSum += getVGap();
}
}
if (childHeight != 0) {
visibleChildCounter++;
useSummaryAsItem = false;
}
}
else{
final int itemLevel = level - 1;
if(child.isFirstGroupNode()){
groupStartContentHeightSum[level] = groupStartContentHeightSum[itemLevel];
summaryBaseX[level] = 0;
groupStart[level] = groupStart[itemLevel];
}
int summaryY = (groupStartY[itemLevel] + groupEndY[itemLevel] ) / 2 - childContentHeight / 2 + childShiftY - (child.getContent().getY() - childCloudHeigth/2 - getSpaceAround());
data.ly[i] = summaryY;
if(!isFreeNode ){
final int deltaY = summaryY - groupStartY[itemLevel] + child.getTopOverlap();
if(deltaY < 0){
top += deltaY;
y -= deltaY;
summaryY -= deltaY;
for(int j = groupStart[itemLevel]; j <= i; j++){
NodeView groupItem = (NodeView) getView().getComponent(j);
if(groupItem.isLeft() == isLeft && (data.summary[j] || !data.free[j]))
data.ly[j]-=deltaY;
}
}
if (childHeight != 0) {
summaryY += childHeight + getVGap() - child.getBottomOverlap();
}
y = Math.max(y, summaryY);
final int summaryContentHeight = groupStartContentHeightSum[itemLevel] + childContentHeight;
if(childContentHeightSum < summaryContentHeight){
childContentHeightSum = summaryContentHeight;
}
}
}
if(! isItem || ! isFreeNode){
if(child.isFirstGroupNode()){
groupStartY[level] = data.ly[i] + child.getTopOverlap();
groupEndY[level] = data.ly[i] + childHeight - child.getBottomOverlap();
}
else{
groupStartY[level] = Math.min(groupStartY[level],data.ly[i] + child.getTopOverlap());
groupEndY[level] = Math.max(data.ly[i] + childHeight - child.getBottomOverlap(), groupEndY[level]);
}
}
final int x;
final int baseX;
if(level > 0)
baseX = summaryBaseX[level - 1];
else{
if(child.isLeft() != (isItem && isFreeNode)){
baseX = 0;
}
else{
baseX = contentWidth;
}
}
if(child.isLeft()){
x = baseX - childHGap - child.getContent().getX() - child.getContent().getWidth();
summaryBaseX[level] = Math.min(summaryBaseX[level], x + getSpaceAround());
}
else{
x = baseX + childHGap - child.getContent().getX();
summaryBaseX[level] = Math.max(summaryBaseX[level], x + child.getWidth() - getSpaceAround());
}
left = Math.min(left, x);
data.lx[i] = x;
}
top += (contentHeight - childContentHeightSum) / 2;
setData(data, isLeft, left, childContentHeightSum, top);
}
private void setData(final LayoutData data, boolean isLeft, int left, int childContentHeight, int top) {
if(!isLeft && data.leftDataSet || isLeft && data.rightDataSet){
data.left = Math.min(data.left, left);
data.childContentHeight = Math.max(data.childContentHeight, childContentHeight);
int deltaTop = top - data.top;
final boolean changeLeft;
if(deltaTop < 0){
data.top = top;
changeLeft = !isLeft;
deltaTop = - deltaTop;
}
else{
changeLeft = isLeft;
}
for(int i = 0; i < getChildCount(); i++){
NodeView child = (NodeView) getView().getComponent(i);
if(child.isLeft() == changeLeft && (data.summary[i] || !data.free[i])){
data.ly[i] += deltaTop;
}
}
}
else{
data.left = left;
data.childContentHeight = childContentHeight;
data.top = top;
}
if(isLeft)
data.leftDataSet = true;
else
data.rightDataSet = true;
}
protected void placeChildren(final LayoutData data) {
final int contentX = Math.max(getSpaceAround(), -data.left);
int contentY= getSpaceAround() + cloudHeight/2 - Math.min(0, data.top);
if (getView().isContentVisible()) {
getContent().setVisible(true);
}
else {
getContent().setVisible(false);
}
int baseY = contentY - getSpaceAround() + data.top;
int minY = 0;
for (int i = 0; i < getChildCount(); i++) {
if(!data.summary[i] && data.free[i]){
minY = Math.min(minY, contentY + data.ly[i]);
}
else
minY = Math.min(minY, baseY + data.ly[i]);
}
if(minY < 0){
contentY -= minY;
baseY -= minY;
}
int width = contentX + contentWidth + getSpaceAround();
int height = contentY + contentHeight+ cloudHeight/2 + getSpaceAround();
getContent().setBounds(contentX, contentY, contentWidth, contentHeight);
int topOverlap = -minY;
int heigthWithoutOverlap = height;
for (int i = 0; i < getChildCount(); i++) {
NodeView child = (NodeView) getView().getComponent(i);
final int y;
if(!data.summary[i] && data.free[i]){
y = contentY + data.ly[i];
}
else{
y = baseY + data.ly[i];
if(! data.free[i])
heigthWithoutOverlap = Math.max(heigthWithoutOverlap, y + child.getHeight()+ cloudHeight/2 - child.getBottomOverlap());
}
final int x = contentX + data.lx[i];
child.setLocation(x, y);
width = Math.max(width, child.getX() + child.getWidth());
height = Math.max(height, y + child.getHeight()+ cloudHeight/2);
}
view.setSize(width, height);
view.setTopOverlap(topOverlap);
view.setBottomOverlap(height - heigthWithoutOverlap);
}
}