/*
* DBeaver - Universal Database Manager
* Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org)
*
* 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.
*/
/*
* Created on Jul 13, 2004
*/
package org.jkiss.dbeaver.ext.erd.part;
import org.eclipse.draw2d.*;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.*;
import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.widgets.Display;
import org.jkiss.dbeaver.ext.erd.model.ERDAssociation;
import org.jkiss.dbeaver.ext.erd.policy.AssociationBendEditPolicy;
import org.jkiss.dbeaver.ext.erd.policy.AssociationEditPolicy;
import org.jkiss.dbeaver.model.DBIcon;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.runtime.VoidProgressMonitor;
import org.jkiss.dbeaver.model.struct.DBSEntityAssociation;
import org.jkiss.dbeaver.model.struct.DBSEntityAttribute;
import org.jkiss.dbeaver.model.struct.DBSEntityConstraintType;
import org.jkiss.dbeaver.model.struct.DBSEntityReferrer;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.utils.CommonUtils;
import java.util.*;
/**
* Represents the editable primary key/foreign key relationship
*
* @author Serge Rider
*/
public class AssociationPart extends PropertyAwareConnectionPart {
public AssociationPart()
{
}
public ERDAssociation getAssociation()
{
return (ERDAssociation) getModel();
}
@Override
public void activate() {
super.activate();
}
@Override
public void deactivate() {
super.deactivate();
}
@Override
protected void createEditPolicies() {
installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
installEditPolicy(EditPolicy.CONNECTION_BENDPOINTS_ROLE, new AssociationBendEditPolicy());
if (isEditEnabled()) {
installEditPolicy(EditPolicy.COMPONENT_ROLE, new AssociationEditPolicy());
}
}
@Override
protected IFigure createFigure() {
ERDAssociation association = (ERDAssociation) getModel();
PolylineConnection conn = (PolylineConnection) super.createFigure();
//conn.setLineJoin(SWT.JOIN_ROUND);
//conn.setConnectionRouter(new BendpointConnectionRouter());
//conn.setConnectionRouter(new ShortestPathConnectionRouter(conn));
//conn.setToolTip(new TextFlow(association.getObject().getName()));
if (association.getObject().getConstraintType() == DBSEntityConstraintType.INHERITANCE) {
final PolygonDecoration srcDec = new PolygonDecoration();
srcDec.setTemplate(PolygonDecoration.TRIANGLE_TIP);
srcDec.setFill(true);
srcDec.setBackgroundColor(getParent().getViewer().getControl().getBackground());
srcDec.setScale(10, 6);
conn.setSourceDecoration(srcDec);
}
if (association.getObject().getConstraintType() == DBSEntityConstraintType.FOREIGN_KEY) {
final CircleDecoration targetDecor = new CircleDecoration();
targetDecor.setRadius(3);
targetDecor.setFill(true);
targetDecor.setBackgroundColor(getParent().getViewer().getControl().getForeground());
//dec.setBackgroundColor(getParent().getViewer().getControl().getBackground());
conn.setTargetDecoration(targetDecor);
if (!association.isIdentifying()) {
final RhombusDecoration sourceDecor = new RhombusDecoration();
sourceDecor.setBackgroundColor(getParent().getViewer().getControl().getBackground());
//dec.setBackgroundColor(getParent().getViewer().getControl().getBackground());
conn.setSourceDecoration(sourceDecor);
}
}
if (!association.isIdentifying() || association.isLogical()) {
conn.setLineStyle(SWT.LINE_CUSTOM);
conn.setLineDash(new float[] {5} );
}
//ChopboxAnchor sourceAnchor = new ChopboxAnchor(classFigure);
//ChopboxAnchor targetAnchor = new ChopboxAnchor(classFigure2);
//conn.setSourceAnchor(sourceAnchor);
//conn.setTargetAnchor(targetAnchor);
/*
ConnectionEndpointLocator relationshipLocator = new ConnectionEndpointLocator(conn, true);
//relationshipLocator.setUDistance(30);
//relationshipLocator.setVDistance(-20);
Label relationshipLabel = new Label(association.getObject().getName());
conn.add(relationshipLabel, relationshipLocator);
*/
// Set router and initial bends
ConnectionLayer cLayer = (ConnectionLayer) getLayer(LayerConstants.CONNECTION_LAYER);
conn.setConnectionRouter(cLayer.getConnectionRouter());
if (!CommonUtils.isEmpty(association.getInitBends())) {
List<AbsoluteBendpoint> connBends = new ArrayList<>();
for (Point bend : association.getInitBends()) {
connBends.add(new AbsoluteBendpoint(bend.x, bend.y));
}
conn.setRoutingConstraint(connBends);
} else if (association.getPrimaryKeyEntity() == association.getForeignKeyEntity()) {
// Self link
final IFigure entityFigure = ((GraphicalEditPart) getSource()).getFigure();
//EntityPart entity = (EntityPart) connEdge.source.getParent().data;
//final Dimension entitySize = entity.getFigure().getSize();
final Dimension figureSize = entityFigure.getMinimumSize();
int entityWidth = figureSize.width;
int entityHeight = figureSize.height;
List<RelativeBendpoint> bends = new ArrayList<>();
{
RelativeBendpoint bp1 = new RelativeBendpoint(conn);
bp1.setRelativeDimensions(new Dimension(entityWidth, entityHeight / 2), new Dimension(entityWidth / 2, entityHeight / 2));
bends.add(bp1);
}
{
RelativeBendpoint bp2 = new RelativeBendpoint(conn);
bp2.setRelativeDimensions(new Dimension(-entityWidth, entityHeight / 2), new Dimension(entityWidth, entityHeight));
bends.add(bp2);
}
conn.setRoutingConstraint(bends);
}
// Set tool tip
Label toolTip = new Label(getAssociation().getObject().getName() + " [" + getAssociation().getObject().getConstraintType().getName() + "]");
toolTip.setIcon(DBeaverIcons.getImage(DBIcon.TREE_FOREIGN_KEY));
//toolTip.setTextPlacement(PositionConstants.SOUTH);
//toolTip.setIconTextGap();
conn.setToolTip(toolTip);
//conn.setMinimumSize(new Dimension(60, 20));
return conn;
}
/**
* Sets the width of the line when selected
*/
@Override
public void setSelected(int value) {
super.setSelected(value);
if (value != EditPart.SELECTED_NONE) {
((PolylineConnection) getFigure()).setLineWidth(2);
} else {
((PolylineConnection) getFigure()).setLineWidth(1);
}
if (getSource() == null || getTarget() == null) {
// This part seems to be deleted
return;
}
DBSEntityAssociation association = getAssociation().getObject();
if (association instanceof DBSEntityReferrer && association.getReferencedConstraint() instanceof DBSEntityReferrer) {
List<AttributePart> sourceAttributes = getEntityAttributes(
(EntityPart)getSource(),
DBUtils.getEntityAttributes(new VoidProgressMonitor(), (DBSEntityReferrer) association.getReferencedConstraint()));
List<AttributePart> targetAttributes = getEntityAttributes(
(EntityPart)getTarget(),
DBUtils.getEntityAttributes(new VoidProgressMonitor(), (DBSEntityReferrer) association));
Color columnColor = value != EditPart.SELECTED_NONE ? Display.getDefault().getSystemColor(SWT.COLOR_RED) : getViewer().getControl().getForeground();
for (AttributePart attr : sourceAttributes) {
attr.getFigure().setForegroundColor(columnColor);
}
for (AttributePart attr : targetAttributes) {
attr.getFigure().setForegroundColor(columnColor);
}
}
}
private List<AttributePart> getEntityAttributes(EntityPart source, Collection<? extends DBSEntityAttribute> columns)
{
List<AttributePart> erdColumns = new ArrayList<>(source.getChildren());
for (Iterator<AttributePart> iter = erdColumns.iterator(); iter.hasNext(); ) {
if (!columns.contains(iter.next().getAttribute().getObject())) {
iter.remove();
}
}
return erdColumns;
}
@Override
public void performRequest(Request request)
{
if (request.getType() == RequestConstants.REQ_OPEN) {
getAssociation().openEditor();
}
}
public void addBendpoint(int bendpointIndex, Point location) {
Bendpoint bendpoint = new AbsoluteBendpoint(location);
List<Bendpoint> bendpoints = getBendpointsCopy();
bendpoints.add(bendpointIndex, bendpoint);
updateBendpoints(bendpoints);
}
public void removeBendpoint(int bendpointIndex) {
List<Bendpoint> bendpoints = getBendpointsCopy();
if (bendpointIndex < bendpoints.size()) {
bendpoints.remove(bendpointIndex);
updateBendpoints(bendpoints);
}
}
public void moveBendpoint(int bendpointIndex, Point location) {
Bendpoint bendpoint = new AbsoluteBendpoint(location);
List<Bendpoint> bendpoints = getBendpointsCopy();
if (bendpointIndex < bendpoints.size()) {
bendpoints.set(bendpointIndex, bendpoint);
updateBendpoints(bendpoints);
}
}
public List<Bendpoint> getBendpoints() {
Object constraint = getConnectionFigure().getRoutingConstraint();
if (constraint instanceof List) {
// Make constraint copy
return (List<Bendpoint>) constraint;
} else {
return Collections.emptyList();
}
}
private List<Bendpoint> getBendpointsCopy() {
Object constraint = getConnectionFigure().getRoutingConstraint();
if (constraint instanceof List) {
// Make constraint copy
List<Bendpoint> curList = (List<Bendpoint>) constraint;
return new ArrayList<>(curList);
} else {
return new ArrayList<>();
}
}
private void updateBendpoints(List<Bendpoint> bendpoints) {
getConnectionFigure().setRoutingConstraint(bendpoints);
}
@Override
public String toString()
{
return getAssociation().getObject().getConstraintType().getName() + " " + getAssociation().getObject().getName();
}
public static class CircleDecoration extends Ellipse implements RotatableDecoration {
private int radius = 5;
private Point location = new Point();
public CircleDecoration() {
super();
}
public void setRadius(int radius)
{
this.radius = radius;
}
@Override
public void setLocation(Point p) {
location = p;
Rectangle bounds = new Rectangle(location.x- radius, location.y- radius, radius *2, radius *2);
setBounds(bounds);
}
@Override
public void setReferencePoint(Point p) {
// length of line between reference point and location
double d = Math.sqrt(Math.pow((location.x-p.x), 2)+Math.pow(location.y-p.y,2));
// do nothing if link is too short.
if(d < radius)
return;
//
// using pythagoras theorem, we have a triangle like this:
//
// | figure |
// | |
// |_____(l.x,l.y)______|
// (\)
// | \(r.x,r.y)
// | |\
// | | \
// | | \
// | | \
// |_|(p.x,p.y)
//
// We want to find a point that at radius distance from l (location) on the line between l and p
// and center our circle at this point.
//
// I you remember your school math, let the distance between l and p (calculated
// using pythagoras theorem) be defined as d. We want to find point r where the
// distance between r and p is d-radius (the same as saying that the distance
// between l and r is radius). We can do this using triangle identities.
// |px-rx|/|px-lx|=|py-ry|/|py-ly|=|d-radius|/d
//
// we use
// k = |d-radius|/d
// longx = |px-lx|
// longy = |py-xy|
//
// remember that d > radius.
//
double k = (d- radius)/d;
double longx = Math.abs(p.x-location.x);
double longy = Math.abs(p.y-location.y);
double shortx = k*longx;
double shorty = k*longy;
// now create locate the new point using the distances depending on the location of the original points.
int rx, ry;
if(location.x < p.x) {
rx = p.x - (int)shortx;
} else {
rx = p.x + (int)shortx;
}
if(location.y > p.y) {
ry = p.y + (int)shorty;
} else {
ry = p.y - (int)shorty;
}
// For reasons that are still unknown to me, I had to increase the radius
// of the circle for the graphics to look right.
setBounds(new Rectangle(rx- radius, ry- radius, (int)(radius *2.5), (int)(radius *2.5)));
}
}
public static class RhombusDecoration extends PolygonDecoration {
private static PointList GEOMETRY = new PointList();
static {
GEOMETRY.addPoint(0, 0);
GEOMETRY.addPoint(-1, 1);
GEOMETRY.addPoint(-2, 0);
GEOMETRY.addPoint(-1, -1);
}
public RhombusDecoration() {
setTemplate(GEOMETRY);
setFill(true);
setScale(5, 5);
}
}
}