blob: 8e6fa4c2bb878a48033a01b10395a3ef0818b8c8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Sybase, Inc. and others.
*
* 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:
* Sybase, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.jst.pagedesigner.css2.layout;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.FigureUtilities;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.jst.pagedesigner.css2.ICSSStyle;
import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyID;
import org.eclipse.jst.pagedesigner.css2.property.ICSSPropertyMeta;
import org.eclipse.jst.pagedesigner.css2.style.ITagEditInfo;
import org.eclipse.jst.pagedesigner.css2.value.Length;
import org.eclipse.jst.pagedesigner.css2.widget.BorderUtil;
import org.eclipse.swt.graphics.FontMetrics;
/**
* The block layout for {@link CSSFigure}figures. Basic code structure is from
* BlockFlowLayout.
*
* @author mengbo
*/
public class CSSBlockFlowLayout extends CSSLayout implements ICSSPainter2 {
private LineBox _previousLine = null;
protected BlockBox _blockBox = null;
protected FontMetrics _fontMetrices;
int _userSpecifiedWidth;
int _userSpecifiedHeight;
/*
* whether we need HScroll and VScroll when overflow is set to "scroll".
* will be updated in "endBlock" and used in "paintFigurePostClientArea"
*/
boolean _needHScroll = false;
boolean _needVScroll = false;
/**
* Creates a new CSSBlockFlowLayout with the given BlockFlow.
*/
public CSSBlockFlowLayout(CSSFigure cssfigure) {
super(cssfigure);
}
protected boolean hasMoreThanOneLine() {
return _previousLine != null;
}
public boolean isInlineBlock() {
String obj = getCSSStyle().getDisplay();
return ICSSPropertyID.VAL_INLINE_BLOCK.equals(obj)
|| ICSSPropertyID.VAL_INLINE_TABLE.equals(obj);
}
/**
* whether should expand the width to all available width.
*
* @return
*/
public boolean shouldExpand() {
ICSSStyle style = getCSSStyle();
if (style == null) {
return false;
}
return "block".equalsIgnoreCase(style.getDisplay())
|| "list-item".equalsIgnoreCase(style.getDisplay());
}
// ---------------------------------------------------------------------------------------------------
// preLayout stage. Major job is get the top-left corner information of the
// new block.
/**
* sets up the single block that contains all of the lines.
*/
protected void setupBlock() {
// int recommended = line.getAvailableWidth();
// if (recommended != previousRecommendedWidth)
// Remove all current Fragments
_blockBox.clear();
// Ask for a new line, in case we are in the middle of a line
if (!isInlineBlock()) {
LineBox lineBox = getFlowContext().getCurrentLine();
if (lineBox != null && !lineBox.isEmptyStringLine()) {
getFlowContext().endLine();
}
}
ICSSStyle style = getCSSStyle();
// endLine will result in context create a new line, so we are in the
// new line now.
// passing in the top margin, and context will consider that when create
// the new line.
int marginTop = style.getMarginInsets().top;
LineBox line = getFlowContext().getCurrentLine(marginTop);
// Setup the one fragment for this Block with the correct X and
// available width
// FIXME: according to spec, when using percentage width/height, should
// percentage to
// the "containing block". But we don't have very good "containing
// block" resolution
// implementation yet.
// calculate the min size
// int minWidth = 0;
// int minHeight = 0;
// if (style != null)
// {
// // try to see whether there is any designer specified min size
// ITagEditInfo info = (ITagEditInfo)
// style.getAdapter(ITagEditInfo.class);
// if (info != null)
// {
// minWidth = info.getMinWidth();
// minHeight = info.getMinHeight();
// }
//
// // CSS also has the min-width/min-height property. We should also get
// that,
// // and using the max of the "min-width" css property and the designer
// specified min size.
// int height = getLengthValue(style,ICSSPropertyID.ATTR_MIN_HEIGHT);
// if(height > minHeight)
// {
// minHeight = height;
// }
// int width = getLengthValue(style,ICSSPropertyID.ATTR_MIN_WIDTH);
// if(width > minWidth)
// {
// minWidth = width;
// }
// }
// keep track of user specified size, this will be used when handling
// the "overflow" CSS property.
_userSpecifiedWidth = 0;
_userSpecifiedHeight = 0;
{
int width = getLengthValue(style, ICSSPropertyID.ATTR_WIDTH);
int availableWidth = line.getAvailableWidth()
- style.getMarginInsets().getWidth();
if (width <= 0) {
// no width setting
if (isCalculatingMaxWidth()) {
_blockBox.setRecommendedWidth(Integer.MAX_VALUE);
// _blockBox.setWidth( (minWidth>0?minWidth:0));
} else {
_blockBox.setRecommendedWidth(availableWidth);
if (shouldExpand()) {
_blockBox.setWidth(availableWidth);
} else {
// _blockBox.setWidth( (minWidth>0?minWidth:0));
}
}
} else {
int w = width;
if (!style.isSizeIncludeBorderPadding()) {
w += style.getBorderInsets().getWidth()
+ style.getPaddingInsets().getWidth();
}
// XXX: should we use minWidth or follow user's choice?
// if (w < minWidth)
// {
// w = minWidth;
// }
_userSpecifiedWidth = w;
_blockBox.setWidth(w);
_blockBox.setRecommendedWidth(w);
}
}
{
int height = getLengthValue(style, ICSSPropertyID.ATTR_HEIGHT);
// Object height =
// style.getStyleProperty(ICSSPropertyID.ATTR_HEIGHT);
// Length heightLength = (height instanceof Length) ? (Length)
// height : null;
if (height <= 0) {
// if (minHeight > 0)
// {
// // _blockBox.setHeight(minHeight);
// _blockBox.setRecommendedHeight(minHeight);
// }
// else
{
_blockBox.setHeight(0);
_blockBox.setRecommendedHeight(0);
}
} else {
int h = height;
if (handlingBorderForBlock()
&& !style.isSizeIncludeBorderPadding()) {
h += style.getBorderInsets().getHeight()
+ style.getPaddingInsets().getHeight();
}
// XXX: should we follow minHeight or user's choice?
// if (minHeight > h)
// {
// h = minHeight;
// }
_userSpecifiedHeight = h;
_blockBox.setHeight(h);
_blockBox.setRecommendedHeight(h);
}
}
_blockBox._marginInsets = new Insets(style.getMarginInsets());
if (handlingBorderForBlock()) {
BoxUtil.setupBorderPaddingMargin(_blockBox, getCSSStyle());
}
// as in designer, we don't want to the element to have zero size, so
// set a minimun size here.
// _blockBox.setWidth(Math.max(20, _blockBox.getWidth()));
// int minHeight = getCSSStyle().getCSSFont().getFontSize() +
// _blockBox.getBorderPaddingHeight();
// _blockBox.setHeight(Math.max(minHeight, _blockBox.getHeight()));
_blockBox._y = line._y;
_blockBox._x = line._x;
setBlockVerticalAlign(_blockBox);
}
protected int getLengthValue(ICSSStyle style, String property) {
int lengthValue = 0;
if (style != null) {
Object object = style.getStyleProperty(property);
Length lengthObj = (object instanceof Length) ? (Length) object
: null;
if (lengthObj != null) {
lengthValue = lengthObj.getValue();
if (lengthObj.isPercentage()) {
if (ICSSPropertyID.ATTR_WIDTH.equalsIgnoreCase(property)
|| ICSSPropertyID.ATTR_MIN_WIDTH
.equalsIgnoreCase(property)) {
lengthValue = this.getFlowContext().getContainerWidth()
* lengthValue / 100;
} else if (ICSSPropertyID.ATTR_HEIGHT
.equalsIgnoreCase(property)
|| ICSSPropertyID.ATTR_MIN_HEIGHT
.equalsIgnoreCase(property)) {
// XXX: we should omit it because we don't support
// percentage height now.
lengthValue = 0;
}
}
}
}
return lengthValue;
}
private void setBlockVerticalAlign(BlockBox box) {
ICSSStyle style = getCSSStyle();
if (style != null) {
box.setVerticalAlignData(style
.getStyleProperty(ICSSPropertyID.ATTR_VERTICAL_ALIGN));
}
}
/**
* @see FlowContainerLayout#preLayout()
*/
protected void preLayout() {
super.preLayout();
_blockBox = new BlockBox();
setupBlock();
// Probably could setup current and previous line here, or just previous
}
// -------------------------------------------------------------------------------------------------------
protected void layoutLines() {
List lines = _blockBox.getFragments();
if (lines != null) {
for (int i = 0; i < lines.size(); i++) {
if (lines.get(i) instanceof LineBox) {
layoutLine((LineBox) lines.get(i));
}
}
}
}
/**
* Called by flush(), adds the BlockBox associated with this BlockFlowLayout
* to the current line and then ends the line.
*/
protected void endBlock() {
layoutLines();
ICSSStyle style = getCSSStyle();
if (style != null) {
int minWidth = 0;
int minHeight = 0;
// try to see whether there is any designer specified min size
ITagEditInfo info = (ITagEditInfo) style
.getAdapter(ITagEditInfo.class);
if (info != null) {
minWidth = info.getMinWidth();
minHeight = info.getMinHeight();
}
// CSS also has the min-width/min-height property. We should also
// get that,
// and using the max of the "min-width" css property and the
// designer specified min size.
int height = getLengthValue(style, ICSSPropertyID.ATTR_MIN_HEIGHT);
if (height > minHeight) {
minHeight = height;
}
int width = getLengthValue(style, ICSSPropertyID.ATTR_MIN_WIDTH);
if (width > minWidth) {
minWidth = width;
}
if (minHeight > _blockBox.getHeight()) {
_blockBox.setHeight(minHeight);
}
if (minWidth > _blockBox.getWidth()) {
_blockBox.setWidth(minWidth);
}
}
// reset scroll information.
this._needHScroll = this._needVScroll = false;
// ok, now we need to adjust the _blockBox's size according to the
// "overflow" setting.
// depends on different "overflow" style of this block, different sizing
// policy may apply.
// ICSSStyle style = this.getCSSStyle();
if (style != null) {
Object overflow = style
.getStyleProperty(ICSSPropertyID.ATTR_OVERFLOW);
if (ICSSPropertyID.VAL_HIDDEN.equals(overflow)) {
if (_userSpecifiedWidth > 0) {
_blockBox.setWidth(_userSpecifiedWidth);
}
if (_userSpecifiedHeight > 0) {
_blockBox.setHeight(_userSpecifiedHeight);
}
} else if (ICSSPropertyID.VAL_SCROLL.equals(overflow)
|| ICSSPropertyID.VAL_AUTO.equals(overflow)) {
// adjust _needHScroll and _needVScroll
if (_userSpecifiedWidth > 0
&& _userSpecifiedWidth < _blockBox.getWidth()) {
_needHScroll = true;
}
if (_userSpecifiedHeight > 0
&& _userSpecifiedHeight < _blockBox.getHeight()) {
_needVScroll = true;
}
if (_needHScroll && !_needVScroll) {
if (_userSpecifiedHeight > 0
&& _blockBox.getInternalContentHeight() >= 0
&& _userSpecifiedHeight < _blockBox
.getInternalContentHeight()
+ _blockBox._paddingInsets.getHeight()
+ BorderUtil.SCROLL_WIDTH) {
_needVScroll = true;
}
}
if (!_needHScroll && _needVScroll) {
if (_userSpecifiedWidth > 0
&& _blockBox.getInternalContentWidth() >= 0
&& _userSpecifiedWidth < _blockBox
.getInternalContentWidth()
+ _blockBox._paddingInsets.getWidth()
+ BorderUtil.SCROLL_WIDTH) {
_needHScroll = true;
}
}
if (_userSpecifiedWidth > 0) {
_blockBox.setWidth(_userSpecifiedWidth);
}
if (_userSpecifiedHeight > 0) {
_blockBox.setHeight(_userSpecifiedHeight);
}
}
}
if (getFlowContext().isCurrentLineOccupied()
&& getFlowContext().getCurrentLine().getAvailableWidth() < _blockBox._width
+ _blockBox._marginInsets.getWidth()) {
getFlowContext().endLine();
}
if (!isInlineBlock()) {
LineBox line = getFlowContext().getCurrentLine();
line.setHorizonalData(getCSSStyle().getStyleProperty(
ICSSPropertyID.ATTR_HORIZONTAL_ALIGN));
line.setHtmlInitData(getCSSStyle().getHTMLelementInitValue(
ICSSPropertyID.ATTR_HORIZONTAL_ALIGN));
line.add(_blockBox);
// getFlowContext().addToCurrentLine(_blockBox);
} else {
getFlowContext().addToCurrentLine(_blockBox);
}
getFlowContext().getCurrentLine()._marginInsets.bottom = getCSSStyle()
.getMarginInsets().bottom;
if (!isInlineBlock()) {
getFlowContext().endLine();
}
}
protected void layoutLine(LineBox line) {
// currentLine.x = 0; //XXX: comment out, don't understand why set to 0,
// because it has already
// been set when setupLine(). And if do need, should
// set to getBorderPaddingInsets().left
// if (!isInlineBlock() && shouldExpand())
// {
// FIXME: currently we are using getRecommendedContentWidth,
// what happen if after adding the new line, the new width is bigger
// than
// recommendedContentWidth? should we use getWidth() instead of
// recommendedcontentWidth?
Object textalign = line.getHorizonalData();
if (textalign == null
|| ICSSPropertyMeta.NOT_SPECIFIED.equals(textalign)) {
textalign = (getCSSStyle()
.getStyleProperty(ICSSPropertyID.ATTR_TEXTALIGN));
}
if (textalign == null
|| ICSSPropertyMeta.NOT_SPECIFIED.equals(textalign)) {
textalign = line.getHtmlInitData();
}
if (ICSSPropertyID.VAL_RIGHT.equals(textalign)) {
line._x = _blockBox.getContentWidth() - line.getWidth();
} else if (ICSSPropertyID.VAL_CENTER.equals(textalign)) {
line._x = (_blockBox.getContentWidth() - line.getWidth()) / 2;
}
if (line._x < 0) {
line._x = 0;
}
line.commit();
}
/**
* Adjust all fragments in the current line to have the same baseline. Do
* any additional adjustments, such as horizontal alignment.
*/
protected void addCurrentLine() {
// The follow code is commented out, and moved into layoutLine(line)
// called by endBlock().
// since only when endBlock is called we really know how big is this
// block box, and then can
// do horizontal alignment.
// // currentLine.x = 0; //XXX: comment out, don't understand why set to
// 0, because it has already
// // been set when setupLine(). And if do need, should
// // set to getBorderPaddingInsets().left
// if (!isInlineBlock() && shouldExpand())
// {
// // FIXME: currently we are using getRecommendedContentWidth,
// // what happen if after adding the new line, the new width is bigger
// than
// // recommendedContentWidth? should we use getWidth() instead of
// // recommendedcontentWidth?
//
// Object textalign =
// (getCSSStyle().getStyleProperty(ICSSPropertyID.ATTR_TEXTALIGN));
// if (textalign == ICSSPropertyID.VAL_RIGHT)
// {
// _currentLine._x = _blockBox.getContentWidth() +
// _blockBox.getBorderPaddingInsets().left - _currentLine.getWidth();
// }
// else if (textalign == ICSSPropertyID.VAL_CENTER)
// {
//
// _currentLine._x = _blockBox.getBorderPaddingInsets().left +
// (_blockBox.getContentWidth() - _currentLine.getWidth()) / 2;
// }
// if (_currentLine._x < 0)
// _currentLine._x = 0;
// }
//
// // FIXME: should check vertical alignment here?
// _currentLine.commit();
// layoutLine(_currentLine);
_blockBox.add(_currentLine);
}
/**
* @see FlowContainerLayout#flush()
*/
protected void flush() {
if (_currentLine != null && _currentLine.isOccupied()) {
addCurrentLine();
}
endBlock();
}
/**
* @see FlowContainerLayout#cleanup()
*/
protected void cleanup() {
_currentLine = _previousLine = null;
_fontMetrices = null;
}
// ----------------------------------------------------------------------------------
/**
* Override to setup the line's x, remaining, and available width.
*
* @param line
* the LineBox to set up
*/
protected void setupLine(LineBox line, int topMargin) {
line.clear();
// the caller of getCurrentLine() may add leftMargin and leftPadding and
// leftBorder to line.x
line._x = 0;
// FIXME: here should check the floating boxes, and minus the width of
// them from
// current line.
line.setRecommendedWidth(_blockBox.getRecommendedContentWidth());
if (_previousLine == null) {
line._y = 0;
if (topMargin != Integer.MIN_VALUE) {
line._y += topMargin;
}
} else {
if (topMargin == Integer.MIN_VALUE) {
line._y = _previousLine._y + _previousLine.getHeight()
+ getLinePadding() + _previousLine._marginInsets.bottom; // XXX:
// should
// add
// previous
// margin
// bottom?
} else {
line._y = _previousLine._y
+ _previousLine.getHeight()
+ Math.max(topMargin,
_previousLine._marginInsets.bottom);
}
}
setFontinfoForLine(line);
// line.validate();
}
private void setFontinfoForLine(LineBox line) {
ICSSStyle style = getCSSStyle();
if (style != null) {
if (_fontMetrices == null) {
// as getSwtFont is resource consuming, so we cache the
// _fontMetrics.
_fontMetrices = FigureUtilities.getFontMetrics(style
.getCSSFont().getSwtFont());
}
line.setFontMetrics(_fontMetrices);
}
}
/**
* @see FlowContainerLayout#createNewLine()
*/
protected void createNewLine() {
_currentLine = new LineBox();
setupLine(_currentLine, Integer.MIN_VALUE);
}
protected void createNewLine(int topmargin) {
_currentLine = new LineBox();
setupLine(_currentLine, topmargin);
}
/**
* @see FlowContext#endLine()
*/
public void endLine() {
// this is called from child layouts.
// If there is no current line, state is equivalent to new line
if (_currentLine == null) {
return;
}
if (_currentLine.isOccupied()) {
addCurrentLine(); // finalize the current line layout
} else {
_currentLine = null;
return;
}
LineBox box = _currentLine;
// _currentLine = _previousLine; //XXX: ???? why (yang)
_previousLine = box;
_currentLine = null;
// setupLine(getCurrentLine());
}
/**
* @see org.eclipse.jst.pagedesigner.css2.layout.FlowContext#getCurrentY()
*/
public int getCurrentY() {
return getCurrentLine()._y; // FIXME: margin of previous block?
}
int getLinePadding() {
return 0;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.css2.layout.CSSLayout#useLocalCoordinates()
*/
public boolean useLocalCoordinates() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.css2.layout.FlowFigureLayout#dispose()
*/
public void dispose() {
//
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.css2.layout.ICSSLayout#getFragmentsForRead()
*/
public List getFragmentsForRead() {
List r = new ArrayList(1);
r.add(_blockBox);
return r;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.css2.layout.ICSSLayout#postValidate()
*/
public void postValidate() {
Rectangle r = new Rectangle(_blockBox._x, _blockBox._y, _blockBox
.getWidth(), _blockBox.getHeight());
getCSSFigure().setBounds(r);
List list = getCSSFigure().getChildren();
for (int i = 0; i < list.size(); i++) {
((FlowFigure) list.get(i)).postValidate();
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.jst.pagedesigner.css2.layout.FlowContext#getContainerWidth()
*/
public int getContainerWidth() {
int width = Math.max(0, Math.max(_blockBox.getWidth(), _blockBox
.getRecommendedWidth()));
return width;
}
/**
* when the "overflow" is "scroll", we need to paint the scrollbar
*/
public void paintFigurePostClientArea(Graphics g) {
ICSSStyle style = this.getCSSStyle();
if (style != null) {
Object overflow = style
.getStyleProperty(ICSSPropertyID.ATTR_OVERFLOW);
if (ICSSPropertyID.VAL_SCROLL.equals(overflow)
|| ICSSPropertyID.VAL_AUTO.equals(overflow)) {
if (this._needHScroll || this._needVScroll) {
// as this is using localCoordinate, so translate to
// relative to left/up corder of whole
// blockbox.
g.translate(-_blockBox.getBorderPaddingInsets().left,
-_blockBox.getBorderPaddingInsets().top);
Rectangle rect = new Rectangle(0, 0, _blockBox.getWidth(),
_blockBox.getHeight());
rect.crop(_blockBox._borderInsets);
if (this._needHScroll && this._needVScroll) {
BorderUtil.drawScrollBar(g, BorderUtil.SCROLL_WIDTH,
rect, BorderUtil.BOTH);
} else if (this._needHScroll) {
BorderUtil.drawScrollBar(g, BorderUtil.SCROLL_WIDTH,
rect, BorderUtil.HORIZONTAL_BAR);
} else if (this._needVScroll) {
BorderUtil.drawScrollBar(g, BorderUtil.SCROLL_WIDTH,
rect, BorderUtil.VERTICAL_BAR);
}
}
}
}
}
}