/*
* Copyright 2015 Igor Maznitsa.
*
* 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.igormaznitsa.mindmap.swing.panel.ui;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanelConfig;
import com.igormaznitsa.mindmap.model.Topic;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Shape;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.igormaznitsa.mindmap.swing.panel.ui.gfx.StrokeType;
import com.igormaznitsa.mindmap.swing.panel.ui.gfx.MMGraphics;
import static com.igormaznitsa.meta.common.utils.Assertions.assertNotNull;
public final class ElementRoot extends AbstractElement {
private final Dimension2D leftBlockSize = new Dimension();
private final Dimension2D rightBlockSize = new Dimension();
public ElementRoot(@Nonnull final Topic topic) {
super(topic);
}
protected ElementRoot(@Nonnull final ElementRoot element) {
super(element);
this.leftBlockSize.setSize(element.leftBlockSize);
this.rightBlockSize.setSize(element.rightBlockSize);
}
@Override
@Nonnull
public AbstractElement makeCopy() {
return new ElementRoot(this);
}
@Override
public boolean isMoveable() {
return false;
}
@Override
public boolean isCollapsed() {
return false;
}
@Nonnull
private Shape makeShape(@Nonnull final MindMapPanelConfig cfg, final float x, final float y) {
final float round = cfg.safeScaleFloatValue(10.0f, 0.1f);
return new RoundRectangle2D.Float(x, y, (float) this.bounds.getWidth(), (float) this.bounds.getHeight(), round, round);
}
@Override
public void drawComponent(@Nonnull final MMGraphics g, @Nonnull final MindMapPanelConfig cfg, final boolean drawCollapsator) {
g.setStroke(cfg.safeScaleFloatValue(cfg.getElementBorderWidth(),0.1f),StrokeType.SOLID);
final Shape shape = makeShape(cfg, 0f, 0f);
if (cfg.isDropShadow()) {
final float offset = cfg.safeScaleFloatValue(cfg.getShadowOffset(), 0.0f);
g.draw(makeShape(cfg, offset, offset),null,cfg.getShadowColor());
}
g.draw(shape, this.getBorderColor(cfg), this.getBackgroundColor(cfg));
if (this.visualAttributeImageBlock.mayHaveContent()) {
this.visualAttributeImageBlock.paint(g, cfg);
}
this.textBlock.paint(g,this.getTextColor(cfg));
if (this.extrasIconBlock.hasContent()) {
this.extrasIconBlock.paint(g);
}
}
@Override
public void drawConnector(@Nonnull final MMGraphics g, @Nonnull final Rectangle2D source, @Nonnull final Rectangle2D destination, final boolean leftDirection, @Nonnull final MindMapPanelConfig cfg) {
g.setStroke(cfg.safeScaleFloatValue(cfg.getConnectorWidth(),0.1f),StrokeType.SOLID);
final double startX;
if (destination.getCenterX() < source.getCenterX()) {
// left
startX = source.getCenterX() - source.getWidth() / 4;
}
else {
// right
startX = source.getCenterX() + source.getWidth() / 4;
}
g.drawCurve(startX, source.getCenterY(), destination.getCenterX(), destination.getCenterY(), cfg.getConnectorColor());
}
private double calcTotalChildrenHeight(final double vertInset, final boolean left) {
double result = 0.0d;
boolean nonfirst = false;
for (final Topic t : this.model.getChildren()) {
final AbstractCollapsableElement w = assertNotNull((AbstractCollapsableElement) t.getPayload());
final boolean lft = w.isLeftDirection();
if ((left && lft) || (!left && !lft)) {
if (nonfirst) {
result += vertInset;
}
else {
nonfirst = true;
}
result += w.getBlockSize().getHeight();
}
}
return result;
}
@Override
public void alignElementAndChildren(@Nonnull final MindMapPanelConfig cfg, final boolean leftSide, final double cx, final double cy) {
super.alignElementAndChildren(cfg, leftSide, cx, cy);
final double dx = cx;
final double dy = cy;
this.moveTo(dx, dy);
final double insetVert = cfg.getFirstLevelVerticalInset() * cfg.getScale();
final double insetHorz = cfg.getFirstLevelHorizontalInset() * cfg.getScale();
final double leftHeight = calcTotalChildrenHeight(insetVert, true);
final double rightHeight = calcTotalChildrenHeight(insetVert, false);
if (leftHeight > 0.0d) {
final double ddx = dx - insetHorz;
double ddy = dy - (leftHeight - this.bounds.getHeight()) / 2;
for (final Topic t : this.model.getChildren()) {
final AbstractCollapsableElement c = assertNotNull((AbstractCollapsableElement) t.getPayload());
if (c.isLeftDirection()) {
c.alignElementAndChildren(cfg, true, ddx - c.getBlockSize().getWidth(), ddy);
ddy += c.getBlockSize().getHeight() + insetVert;
}
}
}
if (rightHeight > 0.0d) {
final double ddx = dx + this.bounds.getWidth() + insetHorz;
double ddy = dy - (rightHeight - this.bounds.getHeight()) / 2;
for (final Topic t : this.model.getChildren()) {
final AbstractCollapsableElement c = assertNotNull((AbstractCollapsableElement) t.getPayload());
if (!c.isLeftDirection()) {
c.alignElementAndChildren(cfg, false, ddx, ddy);
ddy += c.getBlockSize().getHeight() + insetVert;
}
}
}
}
@Override
public void updateElementBounds(@Nonnull final MMGraphics gfx, @Nonnull final MindMapPanelConfig cfg) {
super.updateElementBounds(gfx, cfg);
final double marginOffset = ((cfg.getTextMargins()+cfg.getElementBorderWidth()) * 2.0d) * cfg.getScale();
this.bounds.setRect(this.bounds.getX(), this.bounds.getY(), this.bounds.getWidth() + marginOffset, this.bounds.getHeight() + marginOffset);
}
@Nonnull
public Dimension2D getLeftBlockSize() {
return this.leftBlockSize;
}
@Nonnull
public Dimension2D getRightBlockSize() {
return this.rightBlockSize;
}
@Override
@Nonnull
public Dimension2D calcBlockSize(@Nonnull final MindMapPanelConfig cfg, @Nonnull final Dimension2D size, final boolean childrenOnly) {
final double insetV = cfg.getScale() * cfg.getFirstLevelVerticalInset();
final double insetH = cfg.getScale() * cfg.getFirstLevelHorizontalInset();
final Dimension2D result = size;
double leftWidth = 0.0d;
double leftHeight = 0.0d;
double rightWidth = 0.0d;
double rightHeight = 0.0d;
boolean nonfirstOnLeft = false;
boolean nonfirstOnRight = false;
for (final Topic t : this.model.getChildren()) {
final ElementLevelFirst w = assertNotNull((ElementLevelFirst) t.getPayload());
w.calcBlockSize(cfg, result, false);
if (w.isLeftDirection()) {
leftWidth = Math.max(leftWidth, result.getWidth());
leftHeight += result.getHeight();
if (nonfirstOnLeft) {
leftHeight += insetV;
}
else {
nonfirstOnLeft = true;
}
}
else {
rightWidth = Math.max(rightWidth, result.getWidth());
rightHeight += result.getHeight();
if (nonfirstOnRight) {
rightHeight += insetV;
}
else {
nonfirstOnRight = true;
}
}
}
if (!childrenOnly) {
leftWidth += nonfirstOnLeft ? insetH : 0.0d;
rightWidth += nonfirstOnRight ? insetH : 0.0d;
}
this.leftBlockSize.setSize(leftWidth, leftHeight);
this.rightBlockSize.setSize(rightWidth, rightHeight);
if (childrenOnly) {
result.setSize(leftWidth + rightWidth, Math.max(leftHeight, rightHeight));
}
else {
result.setSize(leftWidth + rightWidth + this.bounds.getWidth(), Math.max(this.bounds.getHeight(), Math.max(leftHeight, rightHeight)));
}
return result;
}
@Override
public boolean hasDirection() {
return true;
}
@Override
@Nullable
public Topic findTopicBeforePoint(@Nonnull final MindMapPanelConfig cfg, @Nonnull final Point point) {
Topic result = null;
if (this.hasChildren()) {
if (this.isCollapsed()) {
return this.getModel().getLast();
}
else {
double py = point.getY();
final double vertInset = cfg.getOtherLevelVerticalInset() * cfg.getScale();
Topic prev = null;
final List<Topic> childForDirection = new ArrayList<Topic>();
if (point.getX() < this.bounds.getCenterX()) {
for (final Topic t : this.model.getChildren()) {
if ((assertNotNull((AbstractElement) t.getPayload())).isLeftDirection()) {
childForDirection.add(t);
}
}
}
else {
for (final Topic t : this.model.getChildren()) {
if (!(assertNotNull((AbstractElement) t.getPayload())).isLeftDirection()) {
childForDirection.add(t);
}
}
}
final Topic lastOne = childForDirection.isEmpty() ? null : childForDirection.get(childForDirection.size() - 1);
for (final Topic t : childForDirection) {
final AbstractElement el = assertNotNull((AbstractElement) t.getPayload());
final double childStartBlockY = el.calcBlockY();
final double childEndBlockY = childStartBlockY + el.getBlockSize().getHeight() + vertInset;
if (py < childEndBlockY) {
result = py < el.getBounds().getCenterY() ? prev : t;
break;
}
else {
if (t == lastOne) {
result = t;
break;
}
}
prev = t;
}
}
}
return result;
}
@Override
@Nonnull
public Color getBackgroundColor(@Nonnull final MindMapPanelConfig config) {
final Color dflt = this.fillColor == null ? config.getRootBackgroundColor() : this.fillColor;
return dflt;
}
@Override
@Nonnull
public Color getTextColor(@Nonnull final MindMapPanelConfig config) {
final Color dflt = this.textColor == null ? config.getRootTextColor() : this.textColor;
return dflt;
}
}