blob: 29d1657f2e1d5d06d797d07633d168c7ff60e1f3 [file] [log] [blame]
/*******************************************************************************
* <copyright>
*
* Copyright (c) 2005, 2014 SAP AG.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API, implementation and documentation
* mwenz - Bug 450993 - ContextButtonPad DomainSpecificContextButtons Graphical glitch if Shape to small
* Aurélien Pupier : fix Bug 502049 to avoid extra spaces for Context Pad
*
* </copyright>
*
*******************************************************************************/
package org.eclipse.graphiti.internal.contextbuttons;
import java.awt.Point;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.graphiti.datatypes.IRectangle;
import org.eclipse.graphiti.tb.IContextButtonEntry;
import org.eclipse.graphiti.tb.IContextButtonPadData;
/**
* An implementation of {@link IContextButtonPadDeclaration}. The calculation of
* the visual definition of the context button pad is based on a reference
* rectangle (around which the context pad is aligned) and on the context button
* entries (which provided the functionality of the context buttons).
*
* @noinstantiate This class is not intended to be instantiated by clients.
* @noextend This class is not intended to be subclassed by clients.
*
*/
public abstract class AbstractContextButtonPadDeclaration implements IContextButtonPadDeclaration {
// ===================== fields set in constructor ========================
/**
* The original reference rectangle as described in
* {@link #getOriginalReferenceRectangle()}
*/
private Rectangle originalReferenceRectangle;
/**
* The adjusted reference rectangle as described in
* {@link #getPadReferenceRectangle()}
*/
private Rectangle padReferenceRectangle;
/**
* The context button pad data containing the {@link IContextButtonEntry}.
*/
private IContextButtonPadData contextButtonPadData;
// ========================= calculated fields ============================
/**
* The combined list of the collapse button and the generic context buttons
* as described in {@link #getCollapseAndGenericButtons()}
*/
private List<IContextButtonEntry> collapseAndGenericButtons;
/**
* The right domain-specific context buttons as described in
* {@link #getDomainButtonsRight()}
*/
private List<IContextButtonEntry> domainButtonsRight;
/**
* The left domain-specific context buttons as described in
* {@link #getDomainButtonsLeft()}
*/
private List<IContextButtonEntry> domainButtonsBottom;
/**
* The bounds of the top pad as described in {@link #getTopPad()}
*/
private Rectangle top;
/**
* The bounds of the right pad as described in {@link #getRightPad()}
*/
private Rectangle right;
/**
* The bounds of the bottom pad as described in {@link #getBottomPad()}
*/
private Rectangle bottom;
/**
* The style of the top pad as described in {@link #getTopPadStyle()}
*/
private PadStyle topStyle = PadStyle.STANDARD;
/**
* The style of the right pad as described in {@link #getRightPadStyle()}
*/
private PadStyle rightStyle = PadStyle.STANDARD;
/**
* The list of positioned context buttons as described in
* {@link #getPositionedContextButtons()}
*/
private List<PositionedContextButton> positionedButtons;
/**
* The containment rectangles as described in
* {@link #getContainmentRectangles()}
*/
private List<Rectangle> containmentRectangles;
/**
* The overlapping containment rectangles as described in
* {@link #getOverlappingContainmentRectangles()}
*/
private List<Rectangle> overlappingContainmentRectangles;
// ============================= constructors =============================
/**
* Creates a new AbstractContextButtonPadDeclaration.
*
* @param referenceRectangle
* The original reference rectangle as described in
* {@link #getOriginalReferenceRectangle()}
* @param contextButtonPadData
* The context button data containing the
* {@link IContextButtonEntry}
*/
public AbstractContextButtonPadDeclaration(IContextButtonPadData contextButtonPadData) {
this.contextButtonPadData = contextButtonPadData;
IRectangle l = contextButtonPadData.getPadLocation();
originalReferenceRectangle = new Rectangle(l.getX(), l.getY(), l.getWidth(), l.getHeight());
padReferenceRectangle = new Rectangle(originalReferenceRectangle);
padReferenceRectangle.grow(1, 1);
initializeDomainButtonLists();
initializeRectangles();
initializeButtonPositions();
initializeContainmentRectangles();
}
// ========================= abstract size getter =========================
/**
* Returns the size of the generic and domain-specific context buttons.
*
* @return The size of the generic and domain-specific context buttons.
*/
protected abstract int getButtonSize();
/**
* Returns the padding between the generic and domain-specific context
* buttons.
*
* @return The padding between the generic and domain-specific context
* buttons.
*/
protected abstract int getButtonPadding();
/**
* Returns the padding between the collapse context button and the other
* generic context buttons.
*
* @return The padding between the collapse context button and the other
* generic context buttons.
*/
protected abstract int getCollapseButtonPadding();
/**
* Returns the padding of the generic and domain-specific context buttons at
* the outside of the context button pad.
*
* @return The padding of the generic and domain-specific context buttons at
* the outside of the context button pad.
*/
protected abstract int getPadPaddingOutside();
/**
* Returns the padding of the generic and domain-specific context buttons at
* the inside of the context button pad.
*
* @return The padding of the generic and domain-specific context buttons at
* the inside of the context button pad.
*/
protected abstract int getPadPaddingInside();
/**
* Returns the horizontal overlap of the pads (top with right, right with
* bottom).
*
* @return The horizontal overlap of the pads (top with right, right with
* bottom).
*/
protected abstract int getPadHorizontalOverlap();
/**
* Returns the vertical overlap of the pads (top with right, right with
* bottom).
*
* @return The vertical overlap of the pads (top with right, right with
* bottom).
*/
protected abstract int getPadVerticalOverlap();
/**
* Returns the length of the pad appendage, which is shown if the
* neighboring pad does not exist.
*
* @return The length of the pad appendage, which is shown if the
* neighboring pad does not exist.
*/
protected abstract int getPadAppendageLength();
// ====================== abstract button creators ========================
/**
* Creates a {@link PositionedContextButton} for a given context button
* entry and position. This method can be implemented to set all the visual
* attributes of the context buttons (line-width, color, opacity, ...).
*/
public abstract PositionedContextButton createButton(IContextButtonEntry entry, Rectangle position);
// =========================== field getter ===============================
/**
* Returns the rectangular bounds of the top pad. The rectangular bounds
* were calculated by the constructor in {@link #initializeRectangles()}. It
* can be null.
*
* @return The rectangular bounds of the top pad.
*/
public Rectangle getTopPad() {
return top;
}
/**
* Returns the rectangular bounds of the right pad. The rectangular bounds
* were calculated by the constructor in {@link #initializeRectangles()}. It
* can be null.
*
* @return The rectangular bounds of the right pad.
*/
public Rectangle getRightPad() {
return right;
}
/**
* Returns the rectangular bounds of the bottom pad. The rectangular bounds
* were calculated by the constructor in {@link #initializeRectangles()}. It
* can be null.
*
* @return The rectangular bounds of the bottom pad.
*/
public Rectangle getBottomPad() {
return bottom;
}
/**
* Returns the top pad style. It is calculated by the constructor in
* {@link #initializeRectangles()}.
*
* @return The top pad style.
*/
public PadStyle getTopPadStyle() {
return topStyle;
}
/**
* Returns the right pad style. It is calculated by the constructor in
* {@link #initializeRectangles()}.
*
* @return The right pad style.
*/
public PadStyle getRightPadStyle() {
return rightStyle;
}
/**
* Returns the original reference rectangle around which the context button
* pad is aligned. It was given in the constructor. Typically these are the
* bounds of the shape around which the context button pad shall appear. It
* must not be null.
*
* @return The original reference rectangle around which the context button
* pad is aligned.
*/
protected final Rectangle getOriginalReferenceRectangle() {
return originalReferenceRectangle;
}
/**
* Returns the adjusted reference rectangle around which the context button
* pad is aligned. It was calculated in the constructor from the original
* reference rectangle. For example the original rectangle could be expanded
* to achieve a distance between the context button pad and the original
* reference rectangle. It must not be null.
*
* @return The adjusted reference rectangle around which the context button
* pad is aligned.
*/
protected final Rectangle getPadReferenceRectangle() {
return padReferenceRectangle;
}
/**
* Returns the list of generic, domain-independent context button entries.
* It was given in the constructor. The generic buttons will be located in
* the top pad. It must not be null but it can be empty.
*
* @return The list of generic, domain-independent context button entries.
*/
protected final List<IContextButtonEntry> getGenericButtons() {
return contextButtonPadData.getGenericContextButtons();
}
/**
* Returns the context button entry for the collapse/expand functionality.
* It was given in the constructor. It can be null.
*
* @return The context button entry for the collapse/expand functionality.
* It was given in the constructor. It can be null.
*/
protected final IContextButtonEntry getCollapseButton() {
return contextButtonPadData.getCollapseContextButton();
}
/**
* Returns the combined list of the collapse button and the generic context
* button entries. Those will be located in the top pad. It must not be null
* but it can be empty. See {@link #getCollapseButton()} and
* {@link #getGenericButtons()}.
*
* @return The combined list of the collapse button and the generic context
* button entries.
*/
protected final List<IContextButtonEntry> getCollapseAndGenericButtons() {
if (collapseAndGenericButtons == null) {
collapseAndGenericButtons = new ArrayList<IContextButtonEntry>(getGenericButtons().size() + 1);
if (getCollapseButton() != null)
collapseAndGenericButtons.add(getCollapseButton());
collapseAndGenericButtons.addAll(getGenericButtons());
}
return collapseAndGenericButtons;
}
/**
* Returns the list of domain-specific context button entries. It was given
* in the constructor. The domain-specific buttons will be located in the
* right pad and bottom pad. It must not be null but it can be empty.
*
* @return The list of domain-specific context button entries.
*/
protected final List<IContextButtonEntry> getDomainButtons() {
return contextButtonPadData.getDomainSpecificContextButtons();
}
/**
* Returns the list of domain-specific context button entries, which are
* located in the right pad. It is calculated in
* {@link #initializeDomainButtonLists()}. It must not be null but it can be
* empty.
*
* @return The list of domain-specific context button entries, which are
* located in the right pad.
*
* @see #getDomainButtons()
*/
protected final List<IContextButtonEntry> getDomainButtonsRight() {
return domainButtonsRight;
}
/**
* Returns the list of domain-specific context button entries, which are
* located in the bottom pad. It is calculated in
* {@link #initializeDomainButtonLists()}. It must not be null but it can be
* empty.
*
* @return The list of domain-specific context button entries, which are
* located in the bottom pad.
*
* @see #getDomainButtons()
*/
protected final List<IContextButtonEntry> getDomainButtonsBottom() {
return domainButtonsBottom;
}
/**
* Returns the list of all positioned context buttons. It must not be null
* but it can be empty. It is calculated in
* {@link #initializeButtonPositions()}.
*
* @return The list of all positioned context buttons.
*/
public final List<PositionedContextButton> getPositionedContextButtons() {
return positionedButtons;
}
/**
* Returns the rectangular bounds around all visible areas of the context
* button pad.
*
* @return The rectangular bounds around all visible areas of the context
* button pad.
*/
public final List<Rectangle> getContainmentRectangles() {
return containmentRectangles;
}
/**
* Returns the overlapping rectangular bounds around all visible areas of
* the context button pad.
*
* @return The overlapping rectangular bounds around all visible areas of
* the context button pad.
*/
public final List<Rectangle> getOverlappingContainmentRectangles() {
return overlappingContainmentRectangles;
}
// ==================== initializing calculated fields ====================
/**
* Determines which domain buttons shall be located in the right pad and
* which in the bottom pad. The algorithm first calculates how many buttons
* fit into the height of the adjusted reference rectangle and locates those
* in the right pad. Any further buttons are located in the bottom pad.
*
* @see #getDomainButtonsRight()
* @see #getDomainButtonsBottom()
*/
protected void initializeDomainButtonLists() {
// calculate maximum number of domain buttons right
int maxNumberOfButtons;
int referenceHeight = getPadReferenceRectangle().height - (2 * getPadVerticalOverlap());
// substract one button from reference height
referenceHeight -= (2 * getButtonPadding()) + getButtonSize();
if (referenceHeight < 0) { // not even one button fits
maxNumberOfButtons = 0;
} else { // one button fits, plus how many other buttons with padding
double additionalButtons = ((double) referenceHeight) / (getButtonSize() + getButtonPadding());
// always round down to avoid extra spaces
maxNumberOfButtons = 1 + (int) Math.floor(additionalButtons);
}
// determine domain buttons right
domainButtonsRight = new ArrayList<IContextButtonEntry>();
for (int i = 0; i < maxNumberOfButtons && i < getDomainButtons().size(); i++) {
domainButtonsRight.add(getDomainButtons().get(i));
}
// determine domain buttons bottom
int rightSize = getDomainButtonsRight().size();
domainButtonsBottom = new ArrayList<IContextButtonEntry>();
for (int i = rightSize; i < getDomainButtons().size(); i++) {
domainButtonsBottom.add(getDomainButtons().get(i));
}
}
/**
* Calculates the rectangular bounds for the top pad, right pad and bottom
* pad.
*/
protected void initializeRectangles() {
Rectangle innerRectangle = new Rectangle(getPadReferenceRectangle());
if (!getDomainButtonsBottom().isEmpty()) {
innerRectangle.height = Math.max(getPadDynamicSize(getDomainButtonsRight().size()),
getPadReferenceRectangle().height + (2 * getPadPaddingInside()));
} else {
innerRectangle.height = getPadDynamicSize(getDomainButtonsRight().size());
}
innerRectangle.height -= 2 * getPadVerticalOverlap();
if (getDomainButtonsBottom().size() > 0 && innerRectangle.height > getPadReferenceRectangle().height) {
// move upwards into the middle of the top buttons and bottom buttons
innerRectangle.y -= (innerRectangle.height - getPadReferenceRectangle().height + 1) / 2;
}
Point innerTop = new Point(innerRectangle.x + innerRectangle.width, innerRectangle.y);
Point innerBottom = new Point(innerTop.x, innerTop.y + innerRectangle.height);
if (getCollapseAndGenericButtons().size() != 0) {
top = new Rectangle();
top.width = getPadDynamicSize(getCollapseAndGenericButtons().size());
if (getCollapseButton() != null && getGenericButtons().size() > 0) {
// adjust with different padding of collapse button
top.width += getCollapseButtonPadding() - getButtonPadding();
}
top.height = getPadConstantSize();
top.x = innerTop.x - top.width + getPadHorizontalOverlap();
top.y = innerTop.y - top.height;
} else if (getPadAppendageLength() > 0) {
topStyle = PadStyle.APPENDAGE;
top = new Rectangle();
top.width = getPadAppendageLength() + getPadHorizontalOverlap();
top.height = getPadVerticalOverlap();
top.x = innerTop.x - top.width + getPadHorizontalOverlap();
top.y = innerTop.y - top.height;
}
if (getDomainButtonsRight().size() != 0 || getDomainButtonsBottom().size() != 0) {
right = new Rectangle();
right.width = getPadConstantSize();
right.height = getPadDynamicSize(getDomainButtonsRight().size());
right.x = innerTop.x;
right.y = innerTop.y - getPadVerticalOverlap();
} else if (getPadAppendageLength() > 0) {
rightStyle = PadStyle.APPENDAGE;
right = new Rectangle();
right.width = getPadHorizontalOverlap();
right.height = getPadAppendageLength() + getPadVerticalOverlap();
right.x = innerTop.x;
right.y = innerTop.y - getPadVerticalOverlap();
}
if (getDomainButtonsBottom().size() != 0) {
bottom = new Rectangle();
bottom.width = getPadDynamicSize(getDomainButtonsBottom().size());
bottom.height = getPadConstantSize();
bottom.x = innerBottom.x - bottom.width + getPadHorizontalOverlap();
bottom.y = innerBottom.y;
}
}
/**
* Determines the positions and sizes of all context buttons.
*/
protected void initializeButtonPositions() {
positionedButtons = new ArrayList<PositionedContextButton>();
for (int i = 0; i < getCollapseAndGenericButtons().size(); i++) {
int iBackwards = getCollapseAndGenericButtons().size() - 1 - i;
int x = top.x + getPadPaddingOutside() + (iBackwards * (getButtonSize() + getButtonPadding()));
if (i == 0 && getCollapseButton() != null && getGenericButtons().size() > 0) {
// adjust with different padding of collapse button
x += getCollapseButtonPadding() - getButtonPadding();
}
int y = top.y + getPadPaddingInside();
Rectangle position = new Rectangle(x, y, getButtonSize(), getButtonSize());
positionedButtons.add(createButton(getCollapseAndGenericButtons().get(i), position));
}
for (int i = 0; i < getDomainButtonsRight().size(); i++) {
int x = right.x + getPadPaddingInside();
int y = right.y + getPadPaddingOutside() + (i * (getButtonSize() + getButtonPadding()));
Rectangle position = new Rectangle(x, y, getButtonSize(), getButtonSize());
positionedButtons.add(createButton(getDomainButtonsRight().get(i), position));
}
for (int i = 0; i < getDomainButtonsBottom().size(); i++) {
int iBackwards = getDomainButtonsBottom().size() - 1 - i;
int x = bottom.x + getPadPaddingOutside() + (iBackwards * (getButtonSize() + getButtonPadding()));
int y = bottom.y + getPadPaddingInside();
Rectangle position = new Rectangle(x, y, getButtonSize(), getButtonSize());
positionedButtons.add(createButton(getDomainButtonsBottom().get(i), position));
}
}
/**
* Determines the containment rectangles.
*
* @see #getContainmentRectangles()
* @see #getOverlappingContainmentRectangles()
*/
protected void initializeContainmentRectangles() {
containmentRectangles = new ArrayList<Rectangle>();
overlappingContainmentRectangles = new ArrayList<Rectangle>();
// first add all pads
if (getTopPad() != null)
containmentRectangles.add(getTopPad());
if (getRightPad() != null)
containmentRectangles.add(getRightPad());
if (getBottomPad() != null)
containmentRectangles.add(getBottomPad());
// then add all buttons which are not completely inside already added
// rectangles (mostly they will be inside the pads)
for (PositionedContextButton button : getPositionedContextButtons()) {
boolean buttonInside = false;
for (Rectangle rectangle : containmentRectangles) {
if (rectangle.contains(button.getPosition())) {
buttonInside = true;
break;
}
}
if (!buttonInside) {
containmentRectangles.add(button.getPosition());
}
}
// create the overlapping containment rectangles from the just chosen
// containment rectangles
Rectangle r = getOriginalReferenceRectangle();
Point referencePoint = new Point(r.x + (r.width / 2), r.y + (r.height / 2));
for (Rectangle rectangle : containmentRectangles) {
Rectangle unionRectangle = rectangle.union(new Rectangle(referencePoint));
overlappingContainmentRectangles.add(unionRectangle);
}
// and add the original reference rectangle itself
overlappingContainmentRectangles.add(getOriginalReferenceRectangle());
}
/**
* Returns the constant size of the pads (width of the right pad, height of
* the top and bottom pad).
*
* @return The constant size of the pads (width of the right pad, height of
* the top and bottom pad).
*/
private int getPadConstantSize() {
return getPadPaddingInside() + getButtonSize() + getPadPaddingInside();
}
/**
* Returns the dynamic size of the pads (height of the right pad, width of
* the top and bottom pad).
*
* @param numberOfButtons
* The number of buttons in the pad for which to calculate the
* size.
* @return The dynamic size of the pads (height of the right pad, width of
* the top and bottom pad).
*/
private int getPadDynamicSize(int numberOfButtons) {
return (2 * getPadPaddingOutside())
+ (numberOfButtons > 0 ? numberOfButtons * getButtonSize() : getPadReferenceRectangle().height)
+ (numberOfButtons > 1 ? (numberOfButtons - 1) * getButtonPadding() : 0);
}
}