package com.baselet.diagram;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JComponent;
import javax.swing.JLayeredPane;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.RepaintManager;
import javax.swing.ScrollPaneConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.baselet.control.basics.geom.Rectangle;
import com.baselet.control.config.Config;
import com.baselet.control.config.SharedConfig;
import com.baselet.control.constants.Constants;
import com.baselet.control.enums.Program;
import com.baselet.control.enums.RuntimeType;
import com.baselet.control.util.Utils;
import com.baselet.element.interfaces.GridElement;
import com.baselet.element.old.element.Relation;
import com.baselet.gui.filedrop.FileDrop;
import com.baselet.gui.filedrop.FileDropListener;
import com.baselet.gui.listener.ScrollbarListener;
@SuppressWarnings("serial")
public class DrawPanel extends JLayeredPane implements Printable {
private static final Logger log = LoggerFactory.getLogger(DrawPanel.class);
private final Point origin;
private JScrollPane _scr;
private final SelectorOld selector;
private final DiagramHandler handler;
private final List<GridElement> gridElements = new ArrayList<GridElement>();
public DrawPanel(DiagramHandler handler, boolean initStartupTextAndFiledrop) {
this.handler = handler;
// AB: Origin is used to track diagram movement in Cut Command
origin = new Point();
setLayout(null);
setBackground(Color.WHITE);
setOpaque(true);
selector = new SelectorOld(this);
JScrollPane p = new JScrollPane() {
@Override
public void setEnabled(boolean en) {
super.setEnabled(en);
getViewport().getView().setEnabled(en);
}
};
p.getHorizontalScrollBar().setUnitIncrement(50); // Using mousewheel on bar or click on arrow
p.getHorizontalScrollBar().setSize(0, 15);
p.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
p.getVerticalScrollBar().setUnitIncrement(50);
p.getVerticalScrollBar().setSize(15, 0);
p.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
ScrollbarListener sbL = new ScrollbarListener(this);
p.getHorizontalScrollBar().addMouseListener(sbL);
p.getVerticalScrollBar().addMouseListener(sbL);
p.setBorder(null);
setScrollPanel(p);
if (Program.getInstance().getRuntimeType() != RuntimeType.BATCH) {
// Wait until drawpanel is valid (eg: after loading a diagramm) and then update panel and scrollbars
// Otherwise palettes which are larger than the viewable area would sometimes not have visible scrollbars until the first click into the palette
new Timer("updatePanelAndScrollbars", true).schedule(new TimerTask() {
@Override
public void run() {
if (isValid()) {
updatePanelAndScrollbars();
cancel();
}
}
}, 25, 25);
if (initStartupTextAndFiledrop) {
StartUpHelpText startupHelpText = new StartUpHelpText(this);
add(startupHelpText);
@SuppressWarnings("unused")
FileDrop fd = new FileDrop(startupHelpText, new FileDropListener()); // only init if this is not a BATCH call. Also fixes Issue 81
}
}
this.repaint(); // repaint the drawpanel to be sure everything is visible (startuphelp etc)
}
@Override
public void setEnabled(boolean en) {
super.setEnabled(en);
handler.setEnabled(en);
for (Component c : getComponents()) {
c.setEnabled(en);
}
if (en) {
setBackground(new Color(255, 255, 255));
}
else {
setBackground(new Color(235, 235, 235));
}
}
public DiagramHandler getHandler() {
return handler;
}
private void setScrollPanel(JScrollPane scr) {
_scr = scr;
scr.setViewportView(this);
}
public JScrollPane getScrollPane() {
return _scr;
}
/**
* Returns the smalles possible rectangle which contains all entities and a border space around it
*
* @param borderSpace
* the borderspace around the rectangle
* @param entities
* the entities which should be included
* @return Rectangle which contains all entities with border space
*/
public static Rectangle getContentBounds(int borderSpace, Collection<GridElement> entities) {
if (entities.size() == 0) {
return new Rectangle(0, 0, 0, 0);
}
int minx = Integer.MAX_VALUE;
int miny = Integer.MAX_VALUE;
int maxx = 0;
int maxy = 0;
for (GridElement e : entities) {
minx = Math.min(minx, e.getRectangle().x - borderSpace);
miny = Math.min(miny, e.getRectangle().y - borderSpace);
maxx = Math.max(maxx, e.getRectangle().x + e.getRectangle().width + borderSpace);
maxy = Math.max(maxy, e.getRectangle().y + e.getRectangle().height + borderSpace);
}
return new Rectangle(minx, miny, maxx - minx, maxy - miny);
}
@Override
public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
if (pageIndex > 0) {
return NO_SUCH_PAGE;
}
else {
Graphics2D g2d = (Graphics2D) g;
RepaintManager currentManager = RepaintManager.currentManager(this);
currentManager.setDoubleBufferingEnabled(false);
Rectangle bounds = getContentBounds(Config.getInstance().getPrintPadding(), getGridElements());
g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
AffineTransform t = g2d.getTransform();
double scale = Math.min(pageFormat.getImageableWidth() / bounds.width,
pageFormat.getImageableHeight() / bounds.height);
if (scale < 1) {
t.scale(scale, scale);
g2d.setTransform(t);
}
g2d.translate(-bounds.x, -bounds.y);
paint(g2d);
currentManager = RepaintManager.currentManager(this);
currentManager.setDoubleBufferingEnabled(true);
return PAGE_EXISTS;
}
}
public List<GridElement> getGridElements() {
return gridElements;
}
public List<Relation> getOldRelations() {
return getHelper(Relation.class);
}
public List<com.baselet.element.relation.Relation> getStickables(Collection<GridElement> excludeList) {
if (!SharedConfig.getInstance().isStickingEnabled() || handler instanceof PaletteHandler) {
return Collections.<com.baselet.element.relation.Relation> emptyList();
}
List<com.baselet.element.relation.Relation> returnList = getHelper(com.baselet.element.relation.Relation.class);
returnList.removeAll(excludeList);
return returnList;
}
@SuppressWarnings("unchecked")
private <T extends GridElement> List<T> getHelper(Class<T> filtered) {
List<T> gridElementsToReturn = new ArrayList<T>();
for (GridElement e : getGridElements()) {
if (e.getClass().equals(filtered)) {
gridElementsToReturn.add((T) e);
}
}
return gridElementsToReturn;
}
public SelectorOld getSelector() {
return selector;
}
/**
* This method must be called after every "significant change" on the drawpanel.
* This doesn't include every increment of dragging an grid element but it should be called after
* the grid elements new location is set (= after the mousebutton is released)
* It should be called only once after many grid elements have changed and not for each element!
* This makes it very hard to call this method by using listeners, therefore it's called explicitly in specific cases.
*/
public void updatePanelAndScrollbars() {
insertUpperLeftWhitespaceIfNeeded();
removeUnnecessaryWhitespaceAroundDiagram();
}
/**
* If entities are out of the visible drawpanel border on the upper left
* corner this method enlarges the drawpanel and displays scrollbars
*/
private void insertUpperLeftWhitespaceIfNeeded() {
Rectangle diaWithoutWhite = getContentBounds(0, getGridElements());
// We must adjust the components and the view by a certain factor
int adjustWidth = 0;
if (diaWithoutWhite.getX() < 0) {
adjustWidth = diaWithoutWhite.getX();
}
int adjustHeight = 0;
if (diaWithoutWhite.getY() < 0) {
adjustHeight = diaWithoutWhite.getY();
}
moveOrigin(adjustWidth, adjustHeight);
// If any adjustment is needed we move the components and increase the view position
if (adjustWidth != 0 || adjustHeight != 0) {
for (int i = 0; i < getComponents().length; i++) {
Component c = getComponent(i);
c.setLocation(handler.realignToGrid(false, c.getX() - adjustWidth), handler.realignToGrid(false, c.getY() - adjustHeight));
}
}
if (adjustWidth < 0) {
setHorizontalScrollbarVisibility(true);
}
if (adjustHeight < 0) {
setVerticalScrollbarVisibility(true);
}
int width = (int) (_scr.getHorizontalScrollBar().getValue() + getViewableDiagrampanelSize().getWidth() - adjustWidth);
int height = (int) (_scr.getVerticalScrollBar().getValue() + getViewableDiagrampanelSize().getHeight() - adjustHeight);
setPreferredSize(new Dimension(width, height));
changeViewPosition(-adjustWidth, -adjustHeight);
}
/**
* Changes the viewposition of the drawpanel and recalculates the optimal drawpanelsize
*/
public void changeViewPosition(int incx, int incy) {
Point viewp = _scr.getViewport().getViewPosition();
_scr.getViewport().setViewSize(getPreferredSize());
_scr.getViewport().setViewPosition(new Point(viewp.x + incx, viewp.y + incy));
}
/**
* If there is a scrollbar visible and a unnecessary whitespace on any border of the diagram
* which is not visible (but possibly scrollable by scrollbars) we remove this whitespace
*/
private void removeUnnecessaryWhitespaceAroundDiagram() {
Rectangle diaWithoutWhite = getContentBounds(0, getGridElements());
Dimension viewSize = getViewableDiagrampanelSize();
int horSbPos = _scr.getHorizontalScrollBar().getValue();
int verSbPos = _scr.getVerticalScrollBar().getValue();
horSbPos = handler.realignToGrid(false, horSbPos);
verSbPos = handler.realignToGrid(false, verSbPos);
int newX = 0;
if (_scr.getHorizontalScrollBar().isShowing()) {
if (horSbPos > diaWithoutWhite.getX()) {
newX = diaWithoutWhite.getX();
}
else {
newX = horSbPos;
}
}
int newY = 0;
if (_scr.getVerticalScrollBar().isShowing()) {
if (verSbPos > diaWithoutWhite.getY()) {
newY = diaWithoutWhite.getY();
}
else {
newY = verSbPos;
}
}
int newWidth = (int) (horSbPos + viewSize.getWidth());
// If the diagram exceeds the right viewable border the width must be adjusted
if (diaWithoutWhite.getX() + diaWithoutWhite.getWidth() > horSbPos + viewSize.getWidth()) {
newWidth = diaWithoutWhite.getX() + diaWithoutWhite.getWidth();
}
int newHeight = (int) (verSbPos + viewSize.getHeight());
// If the diagram exceeds the lower viewable border the width must be adjusted
if (diaWithoutWhite.getY() + diaWithoutWhite.getHeight() > verSbPos + viewSize.getHeight()) {
newHeight = diaWithoutWhite.getY() + diaWithoutWhite.getHeight();
}
moveOrigin(newX, newY);
for (GridElement ge : getGridElements()) {
ge.setLocation(handler.realignToGrid(false, ge.getRectangle().x - newX), handler.realignToGrid(false, ge.getRectangle().y - newY));
}
changeViewPosition(-newX, -newY);
setPreferredSize(new Dimension(newWidth - newX, newHeight - newY));
checkIfScrollbarsAreNecessary();
}
/**
* Returns the visible size of the drawpanel
*/
public Dimension getViewableDiagrampanelSize() {
return getVisibleRect().getSize();
}
private void checkIfScrollbarsAreNecessary() {
/**
* Afterwards recheck if scrollbars are necessary
* This is needed to avoid appearing scrollbars if the diagramm is on the bottom right
*/
Rectangle diaWithoutWhite = getContentBounds(0, getGridElements());
Dimension viewSize = getViewableDiagrampanelSize();
boolean vertWasVisible = isVerticalScrollbarVisible();
boolean horWasVisible = isHorizontalScrollbarVisible();
// Only if the scrollbar is visible we must respect its size to calculate the visibility of the scrollbar
int verSbWidth = 0;
int horSbHeight = 0;
if (vertWasVisible) {
verSbWidth = _scr.getVerticalScrollBar().getWidth();
}
if (horWasVisible) {
horSbHeight = _scr.getHorizontalScrollBar().getHeight();
}
// If the horizontal scrollbar is on the most left point && the the right end of the diagram without whitespace <= the viewable width incl. the width of the vertical scrollbar we hide the horizontal scrollbar
if (_scr.getHorizontalScrollBar().getValue() < handler.getGridSize() && diaWithoutWhite.getX() + diaWithoutWhite.getWidth() <= viewSize.getWidth() + verSbWidth) {
setHorizontalScrollbarVisibility(false);
}
else if (_scr.getHorizontalScrollBar().getValue() < handler.getGridSize() && getViewableDiagrampanelSize().width + _scr.getHorizontalScrollBar().getValue() == diaWithoutWhite.getX() + diaWithoutWhite.getWidth()) {
setHorizontalScrollbarVisibility(false);
}
else {
setHorizontalScrollbarVisibility(true);
}
if (_scr.getVerticalScrollBar().getValue() < handler.getGridSize() && diaWithoutWhite.getY() + diaWithoutWhite.getHeight() <= viewSize.getHeight() + horSbHeight) {
setVerticalScrollbarVisibility(false);
}
else if (_scr.getVerticalScrollBar().getValue() < handler.getGridSize() && getViewableDiagrampanelSize().height + _scr.getVerticalScrollBar().getValue() == diaWithoutWhite.getY() + diaWithoutWhite.getHeight()) {
setVerticalScrollbarVisibility(false);
}
else {
setVerticalScrollbarVisibility(true);
}
// REMOVED TO FIX JUMPING PALETTE ENTRIES AT COPYING/CUTTING
// adjust x and y to avoid jumping diagram if both scrollbars were visible and one of them disappears (only in the upper left corner)
int adx = 0;
int ady = 0;
if (_scr.getHorizontalScrollBar().getValue() != 0 && vertWasVisible && !isVerticalScrollbarVisible()) {
adx = handler.realignToGrid(false, horSbHeight);
}
if (_scr.getVerticalScrollBar().getValue() != 0 && horWasVisible && !isHorizontalScrollbarVisible()) {
ady = handler.realignToGrid(false, verSbWidth);
}
if (adx != 0 || ady != 0) {
setPreferredSize(new Dimension((int) (getPreferredSize().getWidth() + adx), (int) getPreferredSize().getHeight() + ady));
changeViewPosition(adx, ady);
}
}
private void setHorizontalScrollbarVisibility(boolean visible) {
if (visible) {
_scr.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
}
else {
_scr.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
}
}
private void setVerticalScrollbarVisibility(boolean visible) {
if (visible) {
_scr.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
}
else {
_scr.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
}
}
private boolean isHorizontalScrollbarVisible() {
return _scr.getHorizontalScrollBarPolicy() == ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS;
}
private boolean isVerticalScrollbarVisible() {
return _scr.getVerticalScrollBarPolicy() == ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS;
}
private void drawGrid(Graphics2D g2d) {
g2d.setColor(Constants.GRID_COLOR);
int gridSize = handler.getGridSize();
if (gridSize == 1) {
return; // Gridsize 1 would only make the whole screen grey
}
int width = 2000 + (int) getPreferredSize().getWidth();
int height = 1000 + (int) getPreferredSize().getHeight();
for (int i = gridSize; i < width; i += gridSize) {
g2d.drawLine(i, 0, i, height);
}
for (int i = gridSize; i < height; i += gridSize) {
g2d.drawLine(0, i, width, i);
}
}
// private void drawDevHelpLines(Graphics2D g2d) {
// g2d.setStroke(Utils.getStroke(LineType.DASHED, 1));
//
// g2d.setColor(Color.BLUE);
// int w = handler.getDrawPanel().getScrollPane().getViewport().getViewPosition().x;
// int h = handler.getDrawPanel().getScrollPane().getViewport().getViewPosition().y;
// g2d.drawRect(w, h, w + 2, h + 2);
//
// g2d.setColor(Color.GRAY);
// Dimension dim = getViewableDiagrampanelSize();
// g2d.drawRect(0, 0, (int) dim.getWidth(), (int) dim.getHeight());
//
// g2d.setColor(Color.RED);
// Dimension dim2 = getPreferredSize();
// g2d.drawRect(0, 0, (int) dim2.getWidth(), (int) dim2.getHeight());
//
// g2d.setStroke(Utils.getStroke(LineType.SOLID, 1));
// }
@Override
protected void paintChildren(Graphics g) {
// check if layers have changed and update them
for (GridElement ge : gridElements) {
if (!ge.getLayer().equals(getLayer((JComponent) ge.getComponent()))) {
setLayer((JComponent) ge.getComponent(), ge.getLayer());
}
}
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHints(Utils.getUxRenderingQualityHigh(true));
if (Config.getInstance().isShow_grid()) {
drawGrid(g2d);
}
super.paintComponents(g);
}
/**
* AB: Returns a copy of the actual origin zoomed to 100%.
* Origin marks a point that tracks changes of the diagram panel size and can
* be used to regenerate the original position of entities at the time they have been cut/copied,
* etc...
*
* @return a point that marks the diagram origin at a zoom of 100%.
*/
public Point getOriginAtDefaultZoom() {
Point originCopy = new Point(origin);
originCopy.setLocation(
origin.x * Constants.DEFAULTGRIDSIZE / handler.getGridSize(),
origin.y * Constants.DEFAULTGRIDSIZE / handler.getGridSize());
return originCopy;
}
/**
* AB: Returns a copy of the actual origin.
* Origin marks a point that tracks changes of the diagram panel size and can
* be used to regenerate the original position of entities at the time they have been cut/copied,
* etc...
*
* @return a point that marks the diagram origin.
*/
public Point getOrigin() {
log.trace("Diagram origin: " + origin);
return new Point(origin);
}
/**
* AB: Moves the origin around the given delta x and delta y
* This method is mainly used by updatePanelAndScrollBars() to keep track of the panels size changes.
*/
public void moveOrigin(int dx, int dy) {
log.trace("Move origin to: " + origin);
origin.translate(handler.realignToGrid(false, dx), handler.realignToGrid(false, dy));
}
/**
* AB: Zoom the origin from the old grid size to the new grid size
* this method is mainly used by the DiagramHandler classes setGridAndZoom method.
*
* @see DiagramHandler#setGridAndZoom(int)
* @param oldGridSize
* the old grid size
* @param newGridSize
* the new grid size
*/
public void zoomOrigin(int oldGridSize, int newGridSize) {
log.trace("Zoom origin to: " + origin);
origin.setLocation(origin.x * newGridSize / oldGridSize, origin.y * newGridSize / oldGridSize);
}
public void removeElement(GridElement gridElement) {
gridElements.remove(gridElement);
remove((Component) gridElement.getComponent());
}
public void addElement(GridElement gridElement) {
gridElements.add(gridElement);
add((Component) gridElement.getComponent(), gridElement.getLayer());
}
public void updateElements() {
for (GridElement e : gridElements) {
e.updateModelFromText();
}
}
public GridElement getElementToComponent(Component component) {
for (GridElement e : gridElements) {
if (e.getComponent().equals(component)) {
return e;
}
}
return null;
}
public void scroll(int amount) {
JScrollBar scrollBar = _scr.getVerticalScrollBar();
int increment = scrollBar.getUnitIncrement();
scrollBar.setValue(scrollBar.getValue() + amount * increment);
}
private DiagramNotification notification;
public void setNotification(DiagramNotification newNotification) {
if (notification != null) {
remove(notification);
}
notification = newNotification;
add(notification);
repaint();
}
}