
com.igormaznitsa.mindmap.swing.panel.ui.AbstractElement Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mind-map-swing-panel Show documentation
Show all versions of mind-map-swing-panel Show documentation
Swing based panel to show and interact with mind map
The newest version!
/*
* Copyright (C) 2015-2022 Igor A. 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 static com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute.ATTR_BORDER_COLOR;
import static com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute.ATTR_FILL_COLOR;
import static com.igormaznitsa.mindmap.swing.panel.StandardTopicAttribute.ATTR_TEXT_COLOR;
import static java.util.Objects.requireNonNull;
import com.igormaznitsa.mindmap.model.StandardTopicAttributes;
import com.igormaznitsa.mindmap.model.Topic;
import com.igormaznitsa.mindmap.swing.panel.MindMapPanelConfig;
import com.igormaznitsa.mindmap.swing.panel.ui.gfx.MMGraphics;
import com.igormaznitsa.mindmap.swing.panel.utils.Utils;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.Dimension2D;
import java.awt.geom.Rectangle2D;
import javax.swing.text.JTextComponent;
public abstract class AbstractElement {
protected final Topic model;
protected final TextBlock textBlock;
protected final IconBlock extrasIconBlock;
protected final VisualAttributeImageBlock visualAttributeImageBlock;
protected final Rectangle2D bounds = new Rectangle2D.Double();
protected final Dimension2D blockSize = new Dimension();
protected Color fillColor;
protected Color textColor;
protected Color borderColor;
protected AbstractElement(final AbstractElement orig) {
this.model = orig.model;
this.textBlock = new TextBlock(orig.textBlock);
this.extrasIconBlock = new IconBlock(orig.extrasIconBlock);
this.visualAttributeImageBlock = new VisualAttributeImageBlock(orig.visualAttributeImageBlock);
this.bounds.setRect(orig.bounds);
this.blockSize.setSize(orig.blockSize);
this.fillColor = orig.fillColor;
this.textColor = orig.textColor;
this.borderColor = orig.borderColor;
}
public AbstractElement(final Topic model) {
this.model = model;
this.textBlock =
new TextBlock(this.model.getText(), TextAlign.findForName(model.getAttribute(
StandardTopicAttributes.MMD_TOPIC_ATTRIBUTE_TITLE_ALIGN)));
this.textBlock.setTextAlign(TextAlign.findForName(
model.getAttribute(StandardTopicAttributes.MMD_TOPIC_ATTRIBUTE_TITLE_ALIGN)));
this.extrasIconBlock = new IconBlock(model);
this.visualAttributeImageBlock = new VisualAttributeImageBlock(model);
updateColorAttributeFromModel();
}
public String getText() {
return this.model.getText();
}
public void setText(final String text) {
this.model.setText(text);
this.textBlock.updateText(text);
}
public final void updateColorAttributeFromModel() {
this.borderColor =
Utils.html2color(this.model.getAttribute(ATTR_BORDER_COLOR.getText()), false);
this.textColor = Utils.html2color(this.model.getAttribute(ATTR_TEXT_COLOR.getText()), false);
this.fillColor = Utils.html2color(this.model.getAttribute(ATTR_FILL_COLOR.getText()), false);
}
public AbstractElement getParent() {
final Topic parent = this.model.getParent();
return parent == null ? null : (AbstractElement) parent.getPayload();
}
public Topic getModel() {
return this.model;
}
public TextAlign getTextAlign() {
return this.textBlock.getTextAlign();
}
public void setTextAlign(final TextAlign textAlign) {
this.textBlock.setTextAlign(textAlign);
this.model.putAttribute(StandardTopicAttributes.MMD_TOPIC_ATTRIBUTE_TITLE_ALIGN,
this.textBlock.getTextAlign().name());
}
public void updateElementBounds(final MMGraphics gfx, final MindMapPanelConfig cfg) {
this.visualAttributeImageBlock.updateSize(gfx, cfg);
this.textBlock.updateSize(gfx, cfg);
this.extrasIconBlock.updateSize(gfx, cfg);
final double scaledHorzBlockGap = cfg.getScale() * cfg.getHorizontalBlockGap();
double width = 0.0d;
if (this.visualAttributeImageBlock.mayHaveContent()) {
width += this.visualAttributeImageBlock.getBounds().getWidth() + scaledHorzBlockGap;
}
width += this.textBlock.getBounds().getWidth();
if (this.extrasIconBlock.hasContent()) {
width += this.extrasIconBlock.getBounds().getWidth() + scaledHorzBlockGap;
}
this.bounds.setRect(
0d,
0d,
width,
Math.max(
this.visualAttributeImageBlock.getBounds().getHeight(),
Math.max(
this.textBlock.getBounds().getHeight(), this.extrasIconBlock.getBounds().getHeight()
)
)
);
}
public void updateBlockSize(final MindMapPanelConfig cfg) {
this.calcBlockSize(cfg, this.blockSize, false);
}
public Dimension2D getBlockSize() {
return this.blockSize;
}
public void moveTo(final double x, final double y) {
this.bounds.setFrame(x, y, this.bounds.getWidth(), this.bounds.getHeight());
}
public void moveWholeTreeBranchCoordinates(final double deltaX, final double deltaY) {
moveTo(this.bounds.getX() + deltaX, this.bounds.getY() + deltaY);
for (final Topic t : this.model.getChildren()) {
final AbstractElement el = (AbstractElement) t.getPayload();
if (el != null) {
el.moveWholeTreeBranchCoordinates(deltaX, deltaY);
}
}
}
public Rectangle2D getBounds() {
return this.bounds;
}
public final void doPaint(final MMGraphics g, final MindMapPanelConfig cfg,
final boolean drawCollapsator) {
final MMGraphics gfx = g.copy();
try {
if (this.hasChildren() && !isCollapsed()) {
doPaintConnectors(g, isLeftDirection(), cfg);
}
final Rectangle clip = g.getClipBounds();
if (clip == null) {
gfx.translate(this.bounds.getX(), this.bounds.getY());
drawComponent(gfx, cfg, drawCollapsator);
} else if (clip.intersects(this.bounds)) {
gfx.translate(this.bounds.getX(), this.bounds.getY());
drawComponent(gfx, cfg, drawCollapsator);
}
} finally {
gfx.dispose();
}
}
public void doPaintConnectors(final MMGraphics g, final boolean leftDirection,
final MindMapPanelConfig cfg) {
for (final Topic t : this.model.getChildren()) {
drawConnector(g, this.bounds, (requireNonNull((AbstractElement) t.getPayload())).getBounds(),
leftDirection, cfg);
}
}
public boolean hasChildren() {
return !this.model.isEmpty();
}
public JTextComponent fillByTextAndFont(final JTextComponent compo) {
this.textBlock.fillByTextAndFont(compo);
return compo;
}
public abstract void drawComponent(MMGraphics g, MindMapPanelConfig cfg, boolean drawCollapsator);
public abstract void drawConnector(MMGraphics g, Rectangle2D source, Rectangle2D destination,
boolean leftDirection, MindMapPanelConfig cfg);
public abstract boolean isMoveable();
public abstract boolean isCollapsed();
public void alignElementAndChildren(final MindMapPanelConfig cfg, final boolean leftSide,
final double centerX, final double centerY) {
final double textMargin = cfg.getScale() * cfg.getTextMargins();
final double centralBlockLineY = textMargin +
Math.max(this.visualAttributeImageBlock.getBounds().getHeight(),
Math.max(this.textBlock.getBounds().getHeight(),
this.extrasIconBlock.getBounds().getHeight())) / 2;
final double scaledHorzBlockGap = cfg.getScale() * cfg.getHorizontalBlockGap();
double offset = textMargin;
if (this.visualAttributeImageBlock.mayHaveContent()) {
this.visualAttributeImageBlock.setCoordOffset(offset,
centralBlockLineY - this.visualAttributeImageBlock.getBounds().getHeight() / 2);
offset += this.visualAttributeImageBlock.getBounds().getWidth() + scaledHorzBlockGap;
}
this.textBlock.setCoordOffset(offset,
centralBlockLineY - this.textBlock.getBounds().getHeight() / 2);
offset += this.textBlock.getBounds().getWidth() + scaledHorzBlockGap;
if (this.extrasIconBlock.hasContent()) {
this.extrasIconBlock.setCoordOffset(offset,
centralBlockLineY - this.extrasIconBlock.getBounds().getHeight() / 2);
}
}
public abstract Dimension2D calcBlockSize(MindMapPanelConfig cfg, Dimension2D size,
boolean childrenOnly);
public abstract boolean hasDirection();
public ElementPart findPartForPoint(final Point point) {
ElementPart result = ElementPart.NONE;
if (this.bounds.contains(point)) {
final double offX = point.getX() - this.bounds.getX();
final double offY = point.getY() - this.bounds.getY();
result = ElementPart.AREA;
if (this.visualAttributeImageBlock.getBounds().contains(offX, offY)) {
result = ElementPart.VISUAL_ATTRIBUTES;
} else {
if (this.textBlock.getBounds().contains(offX, offY)) {
result = ElementPart.TEXT;
} else if (this.extrasIconBlock.getBounds().contains(offX, offY)) {
result = ElementPart.ICONS;
}
}
}
return result;
}
public Topic findTopicBeforePoint(final MindMapPanelConfig cfg, 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;
for (final Topic t : this.model.getChildren()) {
final AbstractElement el = requireNonNull((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 (this.model.isLastChild(t)) {
result = t;
break;
}
prev = t;
}
}
}
return result;
}
protected double calcBlockY() {
return this.bounds.getY() - (this.blockSize.getHeight() - this.bounds.getHeight()) / 2;
}
protected double calcBlockX() {
return this.bounds.getX() -
(this.isLeftDirection() ? this.blockSize.getWidth() - this.bounds.getWidth() : 0.0d);
}
public Point getCenter() {
return new Point((int) this.bounds.getCenterX(), (int) this.bounds.getCenterY());
}
public AbstractElement findNearestOpenedTopicToPoint(final AbstractElement elementToIgnore,
final Point point) {
return findNearestTopic(elementToIgnore, Double.MAX_VALUE, point);
}
private AbstractElement findNearestTopic(final AbstractElement elementToIgnore,
double maxDistance, final Point point) {
AbstractElement result = null;
if (elementToIgnore != this) {
final double dist = calcAverageDistanceToPoint(point);
if (dist < maxDistance) {
maxDistance = dist;
result = this;
}
}
if (!this.isCollapsed()) {
for (final Topic t : this.model.getChildren()) {
final AbstractElement element =
t.getPayload() == null ? null : (AbstractElement) t.getPayload();
if (element != null) {
final AbstractElement nearestChild =
element.findNearestTopic(elementToIgnore, maxDistance, point);
if (nearestChild != null) {
maxDistance = nearestChild.calcAverageDistanceToPoint(point);
result = nearestChild;
}
}
}
}
return result;
}
public double calcAverageDistanceToPoint(final Point point) {
final double d1 = point.distance(this.bounds.getX(), this.bounds.getY());
final double d2 = point.distance(this.bounds.getMaxX(), this.bounds.getY());
final double d3 = point.distance(this.bounds.getX(), this.bounds.getMaxY());
final double d4 = point.distance(this.bounds.getMaxX(), this.bounds.getMaxY());
return (d1 + d2 + d3 + d4) / (this.bounds.contains(point) ? 8.0d : 4.0d);
}
public AbstractElement findTopicBlockForPoint(final Point point) {
AbstractElement result = null;
if (point != null) {
final double px = point.getX();
final double py = point.getY();
if (px >= calcBlockX() && py >= calcBlockY() &&
px < this.bounds.getX() + this.blockSize.getWidth() &&
py < this.bounds.getY() + this.blockSize.getHeight()) {
if (this.isCollapsed()) {
result = this;
} else {
AbstractElement foundChild = null;
for (final Topic t : this.model.getChildren()) {
final AbstractElement theElement = (AbstractElement) t.getPayload();
if (theElement != null) {
foundChild = theElement.findTopicBlockForPoint(point);
if (foundChild != null) {
break;
}
}
}
result = foundChild == null ? this : foundChild;
}
}
}
return result;
}
public AbstractElement findForPoint(final Point point) {
AbstractElement result = null;
if (point != null) {
if (this.bounds.contains(point)) {
result = this;
} else {
for (final Topic t : this.model.getChildren()) {
final AbstractElement w = (AbstractElement) t.getPayload();
result = w == null ? null : w.findForPoint(point);
if (result != null) {
break;
}
}
}
}
return result;
}
public boolean isLeftDirection() {
return false;
}
public TextBlock getTextBlock() {
return this.textBlock;
}
public IconBlock getIconBlock() {
return this.extrasIconBlock;
}
public VisualAttributeImageBlock getVisualAttributeImageBlock() {
return this.visualAttributeImageBlock;
}
public boolean collapseOrExpandAllChildren(final boolean collapse) {
boolean result = false;
if (this instanceof AbstractCollapsableElement) {
final AbstractCollapsableElement el = (AbstractCollapsableElement) this;
if (collapse) {
if (!el.isCollapsed()) {
el.setCollapse(true);
result = true;
}
} else if (el.isCollapsed()) {
el.setCollapse(false);
result = true;
}
}
if (this.hasChildren()) {
for (final Topic t : this.model.getChildren()) {
final AbstractElement e = (AbstractElement) t.getPayload();
if (e != null) {
result |= e.collapseOrExpandAllChildren(collapse);
}
}
}
return result;
}
public abstract Color getBackgroundColor(MindMapPanelConfig config);
public abstract Color getTextColor(MindMapPanelConfig config);
public Color getBorderColor(final MindMapPanelConfig config) {
return this.borderColor == null ? config.getElementBorderColor() : this.borderColor;
}
public abstract AbstractElement makeCopy();
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy