/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/
package javax.microedition.lcdui;
import java.util.NoSuchElementException;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Timer;
import java.util.TimerTask;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
/**
* FormLayouter implements form layout algorithm.
*/
class FormLayouter
{
/**
* Form instance (not owned).
*/
private Form form;
/**
* Form's scrolled composite (not owned).
*/
protected ScrolledComposite formComposite;
/**
* Controls vector.
*/
private Vector layoutObjects = new Vector();
/**
* Rows vector.
*/
private Vector rows = new Vector();
/**
* Form's current horizontal alignment
*/
private int currentHLayoutDirective;
private ImageItemLayouter imIL;
private StringItemLayouter sIL;
private GaugeLayouter gL;
private TextFieldLayouter tfL;
private DateFieldLayouter dfL;
private ChoiceGroupLayouter cgL;
private CustomItemLayouter ciL;
private SpacerLayouter sL;
private int vPosition;
private Item deferredScrollToItem;
private boolean isCurrent;
private Control itemMainControl;
private static final int NO_DIRECTION = -1;
private Item currentSelectedItem;
private int direction = NO_DIRECTION;
private boolean mousePressed;
private LayoutObject currentlyUnderMouse;
/**
* Constructor.
*
* @param form Form to perform layout on.
*/
FormLayouter(Form form)
{
this.form = form;
formComposite = form.getFormComposite();
imIL = new ImageItemLayouter(this);
sIL = new StringItemLayouter(this);
gL = new GaugeLayouter(this);
dfL = new DateFieldLayouter(this);
tfL = new TextFieldLayouter(this);
cgL = new ChoiceGroupLayouter(this);
ciL = new CustomItemLayouter(this);
sL = new SpacerLayouter(this);
}
/**
* Dispose and cleanup layouted items.
*/
void dispose()
{
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
eswtClearRows(0, null);
removeAllLayoutObjects();
rows = null;
layoutObjects = null;
}
});
}
/**
* Log out a control with all its children at the given indentation level.
*/
void logControl(int level, Control control)
{
if(control != null)
{
String s2 = control.toString();
s2 = s2.substring(0, s2.indexOf('}') - 1);
String s3 = control.getBounds().toString();
s3 = s3.substring(s3.indexOf('{'));
Logger.verbose(Logger.indent(s2 + s3, level * 2));
if(control instanceof Composite)
{
Control[] arr = ((Composite) control).getChildren();
for(int i = 0; i < arr.length; i++)
{
logControl(level + 1, arr[i]);
}
}
}
}
/**
* Log out all layoutobjects for each item.
*/
void logFormLayout()
{
Logger.verbose(form + " ---------------------------------------------");
LayoutObject lo = null;
Item item = null;
int line = -1;
while((lo = getNextLayoutObjectOfItem(lo, null)) != null)
{
if(lo.getOwningItem() != item)
{
item = lo.getOwningItem();
Logger.verbose(item.toString());
}
if(lo.getRowIdx() != line)
{
line = lo.getRowIdx();
}
Logger.verbose(Logger.indent("Row:" + lo.getRowIdx(), 2));
logControl(2, lo.getControl());
}
Logger.verbose(form + " ---------------------------------------------");
}
/**
* Called when Form is about to be shown.<br>
* NOTE: this is called from eSWT UI-thread
*/
void handleShowCurrentEvent()
{
Logger.method(this, "handleShowCurrentEvent");
isCurrent = true;
// restore our scrolling position (eSWT resets it to 0 by default)
eswtSetScrollingPosition(vPosition, true);
Item item = null;
LayoutObject lo = null;
while((lo = getNextLayoutObjectOfItem(lo, null)) != null)
{
if(lo.getOwningItem() != item)
{
// item border
item = lo.getOwningItem();
getItemLayouter(item).eswtAddListeners(item, lo);
}
}
eswtApplyCurrentFocus();
}
/**
* Called when Form is about to be hidden.<br>
* NOTE: this is called from eSWT UI-thread
*/
void handleHideCurrentEvent()
{
Logger.method(this, "handleHideCurrentEvent");
isCurrent = false;
Item item = null;
LayoutObject lo = null;
while((lo = getNextLayoutObjectOfItem(lo, null)) != null)
{
if(lo.getOwningItem() != item)
{
// item border
item = lo.getOwningItem();
getItemLayouter(item).eswtRemoveListeners(item, lo);
getItemLayouter(item).eswtHandleVisibilityChange(item, false);
}
}
direction = NO_DIRECTION;
}
/**
* Called when Form is beeing resized.<br>
* NOTE: this is called from eSWT UI-thread
*/
void handleResizeEvent(int width, int height)
{
// Logger.method(this, "handleResizeEvent");
int numitems = getItemCount();
for(int i = 0; i < numitems; i++)
{
getItem(i).invalidateCachedSizes();
}
ItemLayouter.eswtUpdateStaticShellSize(width, height);
}
/**
* Do form layout according to startIndex.
*
* @param startItem - index of item which need to be layouted.
*/
void layoutForm(final int startIndex)
{
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
eswtLayoutForm(startIndex);
}
});
}
/**
* eSWT callback for doLayout().
*/
void eswtLayoutForm(int startIndex)
{
int numItems = getItemCount();
Logger.method(this, "eswtLayoutForm", startIndex + " / " + numItems);
eswtUpdateFormComposite(false);
if(numItems > 0)
{
Item previousItem = null;
int startRowIndex = 0;
if(startIndex > 0)
{
// Find row with previous item.
previousItem = getItem(startIndex - 1);
Row prevItemRow = getLastRowOfItem(previousItem);
if(prevItemRow != null)
{
startRowIndex = rows.indexOf(prevItemRow);
}
}
// Clear rows starting from item - partial re-layouting
eswtClearRows(startRowIndex, previousItem);
// Layout items
for(int i = startIndex; i < numItems; i++)
{
eswtLayoutItem(getItem(i));
}
// Update last row
eswtUpdateRow(getLastRow());
}
else
{
eswtClearRows(0, null);
}
// check if we need to scroll to a particular item
if(deferredScrollToItem != null)
{
eswtSetCurrentItem(deferredScrollToItem);
deferredScrollToItem = null;
}
eswtUpdateFormComposite(true);
eswtHandleVisibilityChanges();
if(Logger.isLogVerbose())
{
logFormLayout();
}
// clear invalid selected item
eswtCheckCurrentSelectedItem();
if(currentSelectedItem != null
&& (currentSelectedItem.isFocusable()))
{
eswtApplyCurrentFocus();
}
else
{
// If there's no item currently selected try to find first
// focusable item and set it current (if found):
Item found = eswtGetNextFocusableItem(
getItem(startIndex - 1), SWT.ARROW_RIGHT);
if(found != null)
{
eswtSetCurrentSelectedItem(found, NO_DIRECTION);
}
else
{
eswtApplyCurrentFocus();
}
}
}
/**
* Returns if the parameter is a eSWT directional key code.
*
* @param keyCode key code
*/
private boolean isDirectionKey(int keyCode)
{
return (keyCode == SWT.ARROW_DOWN || keyCode == SWT.ARROW_UP
|| keyCode == SWT.ARROW_LEFT || keyCode == SWT.ARROW_RIGHT);
}
/**
* Handler for key events.<br>
* The implementation moves focus and/or scrolls the form when
* needed. The method is called by the Form.
*
* @param keyCode eSWT key code.
* @param keyType eSWT key type.
*/
final void handleKeyEvent(int keyCode, int keyType)
{
Logger.method(this, "handleKeyEvent", currentSelectedItem,
String.valueOf(keyCode), String.valueOf(keyType));
boolean isDirectionalKey = isDirectionKey(keyCode);
if(keyType == SWT.KeyDown && isDirectionalKey)
{
eswtCheckCurrentSelectedItem();
}
if(currentSelectedItem != null)
{
if(getItemLayouter(currentSelectedItem).eswtOfferKeyEvent(
currentSelectedItem, keyCode, keyType))
{
// if the key has been consumed
return;
}
}
// scrolling/focus traverse only happens on directional key's down event
if(keyType == SWT.KeyDown && isDirectionalKey)
{
// try to find next focusable item
Item next = eswtGetNextFocusableItem(currentSelectedItem, keyCode);
// if no visible & focusable item was found to transfer focus
if(next == currentSelectedItem)
{
// try to scroll a bit
eswtSetScrollingPosition(getNextScrollingPosition(keyCode),
true);
// find next focusable after scrolling
next = eswtGetNextFocusableItem(currentSelectedItem, keyCode);
}
if(next != currentSelectedItem)
{
//textfield always have to be fully visible when focused.
if(next instanceof TextField)
{
eswtScrollToItem(next);
}
eswtSetCurrentSelectedItem(next, keyCode);
}
}
}
/**
* Handler for pointer events.<br>
* The method is called by the Form.
*
* @param x coordinate relative to scrolledComposite
* @param y coordinate relative to scrolledComposite
* @param type event type: SWT.MouseDown, SWT.MouseMove, SWT.MouseUp
*/
final void handlePointerEvent(int x, int y, int type)
{
Logger.method(this, "handlePointerEvent", String.valueOf(x),
String.valueOf(y), String.valueOf(type));
// TODO: change when DirectUI style arrives.
/*
Item item;
if (type == SWT.MouseMove) {
if (currentlyUnderMouse == null
|| !currentlyUnderMouse.contains(x, y)) {
if (currentlyUnderMouse != null) {
//currentlyUnderMouse.getControl().setCapture(false);
}
item = eswtFindItemUnderMouse(x, y);
if (item != null && item != currentSelectedItem
&& item.isFocusable()) {
setCurrentItem(item);
item.internalSetFocused(true);
eswtSetCurrentSelectedItem(item);
//following method causes all mouse events delivered to it
currentlyUnderMouse.getControl().setCapture(true);
Logger.warning("seting capture to:" + item);
}
}
int currentVPosition = getVPosition();
boolean isMouseDirectionUp = false;
boolean doScrolling = false;
int localY = y;
if (y <= currentVPosition) {
localY = Math.max(0, y);
eswtSetScrollingPosition(localY, true);
isMouseDirectionUp = true;
doScrolling = true;
}
else if (y > (currentVPosition + getFormHeight())) {
//check for maxVPosition
if (y > (eswtGetMaxVPosition() + getFormHeight())) {
localY = eswtGetMaxVPosition() + getFormHeight();
}
else {
localY = y;
}
currentVPosition = localY - getFormHeight();
eswtSetScrollingPosition(currentVPosition, true);
isMouseDirectionUp = false;
doScrolling = true;
}
if (mousePressed && doScrolling) {
resetEventTimer(isMouseDirectionUp, localY);
}
}
else
if (type == SWT.MouseDown) {
mousePressed = true;
item = eswtFindItemUnderMouse(x, y);
if (item != null && item != currentSelectedItem
&& item.isFocusable() && getForm().getShell() ==
getForm().getShell().getDisplay().getActiveShell()) {
//eswtScrollToItem(item);
//following method causes all mouse events delivered to it
//currentlyUnderMouse.getControl().setCapture(true);
}
}
else if (type == SWT.MouseUp) {
mousePressed = false;
if (currentlyUnderMouse != null) {
//currentlyUnderMouse.getControl().setCapture(false);
}
}*/
}
/**
* Find item at the specified point.
*
* @param x coordinate.
* @param y coordinate.
* @return Item.
*/
Item eswtFindItemUnderMouse(int x, int y)
{
Row itemRow;
for(int i = 0; i < getRowCount(); i++)
{
itemRow = getRow(i);
if(itemRow.getYShift() <= y && y <= itemRow.getBottomPosition())
{
LayoutObject lo;
for(int j = 0; j < itemRow.size(); j++)
{
lo = itemRow.getLayoutObject(j);
if(lo.contains(x, y))
{
Logger.info("Item under mouse: "
+ lo.getOwningItem());
currentlyUnderMouse = lo;
return lo.getOwningItem();
}
}
break;
}
}
return null;
}
/**
* Gets next (or nearest) focusable item.
*
* @param fromItem Item where to start to search the next focusable item.
* @param dir Search direction, one of the arrow key constants defined
* in class SWT.
*
* @return Nearest focusable item or null if no item found.
*/
final Item eswtGetNextFocusableItem(Item fromItem, int dir)
{
Item nextItem = fromItem;
switch(dir)
{
case SWT.ARROW_RIGHT:
{
LayoutObject obj = getLastLayoutObjectOfItem(fromItem);
while((obj = getNextLayoutObjectOfItem(obj, null)) != null)
{
Item owner = obj.getOwningItem();
if(owner != null && owner != fromItem
&& owner.isFocusable()
&& isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT))
{
nextItem = owner;
break;
}
}
break;
}
case SWT.ARROW_LEFT:
{
LayoutObject obj = getFirstLayoutObjectOfItem(fromItem);
while((obj = getPrevLayoutObjectOfItem(obj, null)) != null)
{
Item owner = obj.getOwningItem();
if(owner != null && owner != fromItem
&& owner.isFocusable()
&& isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT))
{
nextItem = owner;
break;
}
}
break;
}
case SWT.ARROW_DOWN:
{
int minDist = Integer.MAX_VALUE;
LayoutObject start = getLastLayoutObjectOfItem(fromItem);
LayoutObject obj = start;
while((obj = getNextLayoutObjectOfItem(obj, null)) != null)
{
Item owner = obj.getOwningItem();
if(owner != null && owner != fromItem
&& owner.isFocusable() && obj.isBelow(start)
&& isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT))
{
int dist = obj.distanceTo(start);
if(dist < minDist)
{
minDist = dist;
nextItem = owner;
}
}
}
break;
}
case SWT.ARROW_UP:
{
int minDist = Integer.MAX_VALUE;
LayoutObject start = getFirstLayoutObjectOfItem(fromItem);
LayoutObject obj = start;
while((obj = getPrevLayoutObjectOfItem(obj, null)) != null)
{
Item owner = obj.getOwningItem();
if(owner != null && owner != fromItem
&& owner.isFocusable() && obj.isAbove(start)
&& isPartiallyVisible(obj, Config.DFI_VISIBILITY_PERCENT))
{
int dist = obj.distanceTo(start);
if(dist < minDist)
{
minDist = dist;
nextItem = owner;
}
}
}
break;
}
default:
}
return nextItem;
}
/**
* Check if the currentSelectedItem is valid and visible. If not then it
* sets it to null.
*/
final void eswtCheckCurrentSelectedItem()
{
if(currentSelectedItem != null)
{
if(currentSelectedItem.getParent() != getForm()
|| !currentSelectedItem.isVisible())
{
// we need to find another
Logger.method(this, "eswtCheckCurrentSelectedItem");
eswtSetCurrentSelectedItem(null, NO_DIRECTION);
}
}
}
/**
* Sets currentSelectedItem and sets focus to it.<br>
* If one of form's items is already selected when this method is called,
* removes focus from old item and then moves focus to new one.
*
* @param item Item to set as current selected. If null, nothing happens.
* @param dir Direction which is delivered to layouter.
*/
void eswtSetCurrentSelectedItem(Item item, int dir)
{
if(currentSelectedItem != item)
{
Logger.info(this + "::SelectedItem: "
+ currentSelectedItem + " --(" + dir + ")--> " + item);
// Save direction
direction = dir;
// Remove focus from currentSelectedItem and notify its Layouter.
if(currentSelectedItem != null)
{
getItemLayouter(currentSelectedItem).eswtFocusLost(
currentSelectedItem);
}
// Set new currentSelectedItem, must be focusable or null
currentSelectedItem = item;
// Set focus to currentSelectedItem and notify its Layouter.
if(currentSelectedItem != null)
{
getItemLayouter(currentSelectedItem).eswtFocusGained(
currentSelectedItem, dir);
}
// Apply eSWT focus to currentSelectedItem's control
eswtApplyCurrentFocus();
}
}
/**
* Sets currentSelectedItem and sets focus to it.<br>
* If one of form's items is already selected when this method is called,
* removes focus from old item and then moves focus to new one.
*
* @param item Item to set as current selected. If null, nothing happens.
* @param dir Direction which is delivered to layouter.
*/
void eswtSetCurrentSelectedItem(Item item)
{
if(currentSelectedItem != item)
{
Logger.info(this + "::SelectedItem: "
+ currentSelectedItem + " ---> " + item);
// Remove focus from currentSelectedItem and notify its Layouter.
if(currentSelectedItem != null)
{
getItemLayouter(currentSelectedItem).eswtFocusLost(
currentSelectedItem);
}
// Set new currentSelectedItem, must be focusable or null
currentSelectedItem = item;
// Set focus to currentSelectedItem and notify its Layouter.
if(currentSelectedItem != null)
{
getItemLayouter(currentSelectedItem).eswtFocusGained(
currentSelectedItem, NO_DIRECTION);
}
// Apply eSWT focus to currentSelectedItem's control
//eswtApplyCurrentFocus();
}
}
/**
* Sets focus to currentSelectedItem's control if its partially visible.
* Otherwise it sets dummy focus to form's composite.<br>
* <br>
* Note that this method applies focus only to eSWT control. Item focus
* update and layouter notifications are handled in method
* <code>eswtSetCurrentSelectedItem()</code>.<br>
* If currentSelectedItem is null or form is not shown, this method has no
* effect.
*/
void eswtApplyCurrentFocus()
{
if(isFormCurrent())
{
// if any of the Item's LayoutObjects is visible
if(isItemPartiallyVisible(currentSelectedItem))
{
Logger.method(this, "ApplyFocus", currentSelectedItem);
eswtSetFocusToFirstControl(currentSelectedItem);
}
else
{
Logger.method(this, "ApplyFocus", "dummy");
formComposite.forceFocus();
}
}
}
/**
* If the Item is valid and it is layouted, then sets the Item's first
* LayoutObject focused.
*
* @param item an item which first LayoutObject is set focused.
*/
void eswtSetFocusToFirstControl(Item item)
{
if(item != null && item.isFocusable())
{
LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
lo.getControl().forceFocus();
}
}
}
/**
* Gets Current selected item.
*
* @return Current selected item. May also return null.
*/
Item getCurrentSelectedItem()
{
return currentSelectedItem;
}
/**
* Get the direction of scrolling.
*
* @return direction of scrolling.
*/
int getDirection()
{
return direction;
}
/**
* Set focus to an item if it is focusable and scroll form to make it
* visible if it is not.
*
* @param item Item to set as current item.
*/
void setCurrentItem(final Item item)
{
Logger.method(this, "setCurrentItem", item);
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
eswtSetCurrentItem(item);
}
});
}
/**
* eSWT callback for setCurrentItem.
*/
boolean eswtSetCurrentItem(Item item)
{
if(item != null)
{
if(isItemLayouted(item))
{
eswtScrollToItem(item);
deferredScrollToItem = null;
if(item.isFocusable())
{
eswtSetCurrentSelectedItem(item, NO_DIRECTION);
Logger.info("eswtSetCurrentItem" + item);
}
return true;
}
else
{
deferredScrollToItem = item;
}
}
return false;
}
void eswtScrollToItem(Item item)
{
if(item != null)
{
int pos = getItemBottomPosition(item) - getFormHeight();
if(!isItemPartiallyVisible(item))
{
eswtSetScrollingPosition(pos, true);
}
else if(item instanceof TextField && !isItemFullyVisible(item))
{
eswtSetScrollingPosition(pos, true);
}
}
}
void eswtScrolltoRegion(int yTop, int yBottom, int swtDir)
{
if(yTop < vPosition || yBottom > vPosition + getFormHeight())
{
// if the region is somewhat outside the screen
if(swtDir == SWT.ARROW_DOWN || swtDir == SWT.ARROW_RIGHT)
{
// align to top
eswtSetScrollingPosition(yTop, true);
}
else
{
// align to bottom
eswtSetScrollingPosition(yBottom - getFormHeight(), true);
}
}
}
void eswtScrollIfNeeded(final int top, final int bottom)
{
if(bottom > vPosition + getFormHeight())
{
eswtSetScrollingPosition(bottom - (getFormHeight() / 2), true);
}
else if(top < vPosition)
{
eswtSetScrollingPosition(bottom - (getFormHeight() / 2), true);
}
}
/**
* Get control's position relative to composite.
*/
void getControlPositionOnComposite(Control control, Point location)
{
if(control != formComposite)
{
Point rel = control.getLocation();
location.x += rel.x;
location.y += rel.y;
getControlPositionOnComposite(control.getParent(), location);
}
}
/**
* eSWT specific calls to do update ScrolledComposite.
*/
private void eswtUpdateFormComposite(boolean show)
{
if(getRowCount() > 0)
{
if(show)
{
formComposite.updateScrollbar(getLastRow().getBottomPosition());
formComposite.pack();
}
}
// Could happen if changing item from very tall to very short.
// so we have to update VPosition
if(getVPosition() > eswtGetMaxVPosition())
{
eswtSetScrollingPosition(eswtGetMaxVPosition(), false);
}
formComposite.setRedraw(show);
formComposite.setVisible(show);
}
/**
* Clean all form rows starting from startIndex.
*
* @param startIndex Start row from which to clean.
* @param keepItem - item in a startRow which shouldn't be recreated.
*/
private void eswtClearRows(int startIndex, Item keepItem)
{
Logger.method(this, "clearRows", String.valueOf(startIndex), keepItem);
Row row = null;
for(int i = (getRowCount() - 1); i >= startIndex; i--)
{
row = getRow(i);
if(row.cleanRow(keepItem))
{
break;
}
else
{
rows.removeElement(row);
}
}
// one row always should be available.
if((getRowCount() == 0))
{
// rows.addElement(tempRow);
currentHLayoutDirective = Item.LAYOUT_DEFAULT;
Row newRow = new Row(ItemLayouter.getMaximumItemWidth(null),
getCurrentHLayoutDirective());
rows.addElement(newRow);
}
}
/**
* Update Row's internal layout. Handles LAYOUT_2 related post-processing.
*
* @param row Row
*/
private void eswtUpdateRow(Row row)
{
if(row != null)
{
//Logger.verbose("updateRow start: " + row);
int numShrink = row.getNumLayoutObjects(Item.LAYOUT_SHRINK);
int numExpand = row.getNumLayoutObjects(Item.LAYOUT_EXPAND);
//Logger.verbose("shrink: " + numShrink + " expand: " + numExpand);
int vMask = Item.LAYOUT_VSHRINK | Item.LAYOUT_VEXPAND;
// Expand items vertically with VSHRINK or VEXPAND layout directive
LayoutObject lo = null;
int maxHeight = row.getRowHeight(vMask);
while((lo = row.getNextLayoutObject(lo, vMask)) != null)
{
if(lo.getOwningItem().hasLayout(Item.LAYOUT_VSHRINK))
{
int pref = lo.getOwningItem().getPreferredHeight();
getItemLayouter(lo.getOwningItem()).eswtResizeObject(lo,
lo.getWidth(), Math.min(pref, maxHeight));
}
else if(lo.getOwningItem().hasLayout(Item.LAYOUT_VEXPAND))
{
getItemLayouter(lo.getOwningItem()).eswtResizeObject(lo,
lo.getWidth(), maxHeight);
}
}
// Expand items with SHRINK layout directive
if(numShrink > 0)
{
// Get extra space before shrink and expand
int offset = row.getFreeSpace() / numShrink;
// Logger.verbose("shrinkOffset: " + offset);
if(offset >= 0)
{
while((lo = row.getNextLayoutObject(lo, Item.LAYOUT_SHRINK)) != null)
{
int pref = lo.getOwningItem().getPreferredWidth();
int min = lo.getOwningItem().getMinimumWidth();
int itemWidth = Math.min(pref, min + offset);
getItemLayouter(lo.getOwningItem()).eswtResizeObject(lo,
itemWidth, lo.getHeight());
}
}
}
// Expand items with EXPAND layout directive
if(numExpand > 0)
{
// Get extra space after shrink but before expand
int offset = row.getFreeSpace(Item.LAYOUT_EXPAND) / numExpand;
if(offset >= 0)
{
// Logger.verbose("expandOffset: " + offset);
while((lo = row.getNextLayoutObject(lo, Item.LAYOUT_EXPAND)) != null)
{
int pref = lo.getOwningItem().getPreferredWidth();
getItemLayouter(lo.getOwningItem()).eswtResizeObject(lo,
pref + offset, lo.getHeight());
}
}
}
//if (numShrink > 0 || numExpand > 0) {
row.updateRowInternals();
//}
row.updateRowLayout(form.getLeftRightLanguage());
// Logger.verbose("updateRow: " + row);
}
}
/**
* Add a new Row.
*/
private Row eswtAddNewRow()
{
Row lastRow = getLastRow();
eswtUpdateRow(lastRow);
int yShift = (lastRow == null ? 0 : lastRow.getBottomPosition());
// create new Row
Row newRow = new Row(ItemLayouter.getMaximumItemWidth(null),
getCurrentHLayoutDirective());
newRow.setYShift(yShift);
rows.addElement(newRow);
return newRow;
}
/**
* Add a LayoutObject to the last Row.
*
* @param layoutObject the layout object
*/
void eswtAddNewLayoutObject(LayoutObject layoutObject)
{
if(!layoutObjects.contains(layoutObject))
{
layoutObjects.addElement(layoutObject);
}
Row lastRow = getLastRow();
// check if the current Row is full
if(!lastRow.isEmpty()
&& lastRow.getFreeSpace() < layoutObject.getWidth())
{
lastRow = eswtAddNewRow();
}
lastRow.eswtAddLayoutObject(layoutObject);
layoutObject.setRowIdx(getRowCount() - 1);
}
/**
* Optionally add a new Row and adds a LayoutObject.
*
* @param layoutObject the layout object
* @param newRow adds a new row if true. If false, adds new row only if
* there's no space for layoutObject in current row.
*/
void eswtAddNewLayoutObject(LayoutObject layoutObject, boolean newRow)
{
if(newRow)
{
eswtAddNewRow();
}
eswtAddNewLayoutObject(layoutObject);
}
/**
* Layout item in a row, if needed new row is added.
*
* @param row - where to startLayout.
* @param item - Item to Layout
*/
private void eswtLayoutItem(Item item)
{
Row lastRow = getLastRow();
boolean hlChange = setCurrentHLayoutDirective(item.internalGetLayout());
if(hlChange || getItemNewLineBefore(item))
{
// newline directive or horizontal layout changed
if(lastRow.isEmpty())
{
// if the current/last row is empty - use that
lastRow.setRowHLayout(getCurrentHLayoutDirective());
}
else
{
eswtAddNewRow();
}
}
// Use the specific layouter to layout item in the last row
getItemLayouter(item).eswtLayoutItem(getLastRow(), item);
if(form.eswtIsShown())
{
LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
getItemLayouter(item).eswtAddListeners(item, lo);
}
}
if(getItemNewLineAfter(item))
{
eswtAddNewRow();
}
}
/**
* Set Form's Layout directive. if it differ from current set flag
* startFromNewLine = true;
*
* @param newLayoutDirective
* @return true if a layout change has occured
*/
private boolean setCurrentHLayoutDirective(int newLayoutDirective)
{
int newHLayoutDirective = Item.getHorizontalLayout(newLayoutDirective);
if((newHLayoutDirective != currentHLayoutDirective)
&& (newHLayoutDirective != Item.LAYOUT_DEFAULT))
{
currentHLayoutDirective = newHLayoutDirective;
return true;
}
return false;
}
/**
* Get Form current Layout directive.
*
* @return current Layout directive for Form.
*/
private int getCurrentHLayoutDirective()
{
if(currentHLayoutDirective == Item.LAYOUT_DEFAULT)
{
return getLanguageSpecificLayoutDirective();
}
return currentHLayoutDirective;
}
/**
* Returns language specific layout directive.
*
* @return LAYOUT_LEFT or LAYOUT_RIGHT.
*/
int getLanguageSpecificLayoutDirective()
{
if(form.getLeftRightLanguage())
{
return Item.LAYOUT_LEFT;
}
else
{
return Item.LAYOUT_RIGHT;
}
}
private boolean getItemNewLineBefore(Item item)
{
return ((item.internalGetLayout() & Item.LAYOUT_NEWLINE_BEFORE) != 0);
}
private boolean getItemNewLineAfter(Item item)
{
return ((item.internalGetLayout() & Item.LAYOUT_NEWLINE_AFTER) != 0);
}
boolean isItemLayouted(Item item)
{
return (getFirstLayoutObjectOfItem(item) != null);
}
/**
* Returns if the form is shown.
*/
boolean isFormCurrent()
{
return isCurrent;
}
/**
* Returns if the region is partially visible.
*
* @param viewTop viewPort's top position
* @param viewBottom viewPort's bottom position
* @return true if visible
*/
boolean isPartiallyVisible(int yTop, int yBottom)
{
int vBottomPosition = vPosition + getFormHeight();
if((vPosition <= yTop && vBottomPosition <= yTop)
|| (vPosition >= yBottom && vBottomPosition >= yBottom))
{
return false;
}
return true;
}
/**
* Returns if at least the region's given percentage is visible.
*/
boolean isPartiallyVisible(int yTop, int yBottom, int minPercent)
{
int visPercent = getVisibilityPercent(yTop, yBottom);
if(visPercent > minPercent)
{
return true;
}
return false;
}
/**
* Returns the region's visibility percentage.
*/
int getVisibilityPercent(int yTop, int yBottom)
{
if(yTop >= yBottom)
{
return 0;
}
int vBottomPosition = vPosition + getFormHeight();
int r1 = Math.max(vPosition, Math.min(yTop, vBottomPosition));
int r2 = Math.min(vBottomPosition, Math.max(yBottom, vPosition));
return ((r2 - r1) * 100) / (yBottom - yTop);
}
/**
* Returns if the LayoutObject is partially visible.
*/
boolean isPartiallyVisible(LayoutObject lo)
{
if(lo != null)
{
return isPartiallyVisible(lo.getY(), lo.getY() + lo.getHeight());
}
return false;
}
/**
* Returns if at least the LayoutObject's given percentage is visible.
*/
boolean isPartiallyVisible(LayoutObject lo, int minPercent)
{
if(lo != null)
{
return isPartiallyVisible(lo.getY(), lo.getY() + lo.getHeight(),
minPercent);
}
return false;
}
/**
* Returns if the Item is partially visible (if one of its LayoutObjects is
* partially visible).
*
* @param item the Item
* @return true if partially visible
*/
boolean isItemPartiallyVisible(Item item)
{
if(item != null)
{
LayoutObject lo = null;
while((lo = getNextLayoutObjectOfItem(lo, item)) != null)
{
if(isPartiallyVisible(lo))
{
return true;
}
}
}
return false;
}
/**
* Returns true if item is fully visible.
*
* @param item the Item.
* @return true if fully visible.
*/
boolean isItemFullyVisible(Item item)
{
if(item != null)
{
LayoutObject lo = null;
while((lo = getNextLayoutObjectOfItem(lo, item)) != null)
{
if(!isLOFullyVisible(lo))
{
return false;
}
}
}
return true;
}
/**
* Returns if the LayoutObject is fully visible.
*/
boolean isLOFullyVisible(LayoutObject lo)
{
if(lo != null)
{
return isFullyVisible(lo.getY(), lo.getY() + lo.getHeight());
}
return false;
}
/**
* Returns if the region is fully visible.
*
* @param viewTop viewPort's top position
* @param viewBottom viewPort's bottom position
* @return true if visible
*/
boolean isFullyVisible(int yTop, int yBottom)
{
int vBottomPosition = vPosition + getFormHeight();
if((vPosition <= yTop && vBottomPosition >= yBottom))
{
return true;
}
return false;
}
int getItemTopPosition(Item item)
{
LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
return lo.getY();
}
return 0;
}
int getItemBottomPosition(Item item)
{
LayoutObject lo = getLastLayoutObjectOfItem(item);
if(lo != null)
{
return lo.getY() + lo.getHeight();
}
return 0;
}
int getItemCount()
{
return form.size();
}
Item getItem(int index)
{
try
{
return (Item) form.getItems().elementAt(index);
}
catch(ArrayIndexOutOfBoundsException e)
{
// Logger.exception(e);
return null;
}
}
int getItemIndex(Item item)
{
return form.getItems().indexOf(item);
}
int getRowCount()
{
return rows.size();
}
/**
* Return the Row with the given index.
*
* @param index Row's index
* @return a Row
*/
Row getRow(int index)
{
try
{
return (Row) rows.elementAt(index);
}
catch(ArrayIndexOutOfBoundsException e)
{
Logger.exception("getRow", e);
return null;
}
}
/**
* Return the Row to which the given LayoutObject belongs.
*
* @param lo LayoutObject
* @return the owning Row
*/
Row getRow(LayoutObject lo)
{
try
{
return getRow(lo.getRowIdx());
}
catch(NullPointerException e)
{
// Logger.exception("getRow", e);
return null;
}
}
/**
* Returns the last row of the form.
*/
Row getLastRow()
{
try
{
return (Row) rows.lastElement();
}
catch(NoSuchElementException nse)
{
// Logger.exception("getLastRow", nse);
return null;
}
}
/**
* Find last row which contains corresponding Item.
*
* @param item Item in a Row.
* @return Last Row with that item.
*/
Row getLastRowOfItem(Item item)
{
return getRow(getLastLayoutObjectOfItem(item));
}
/**
* Get Form which requires layout.
*
* @return form.
*/
Form getForm()
{
return form;
}
/**
* Form's content height.
*/
int getFormHeight()
{
return form.getHeight();
}
/**
* Form's content width.
*/
int getFormWidth()
{
return form.getWidth();
}
/**
* Returns LayoutObject for the given Item.
*
* @param item Item to layout
* @return LayoutObject
*/
LayoutObject getLayoutObject(Item item)
{
int num = layoutObjects.size();
for(int i = 0; i < num; i++)
{
if(((LayoutObject)layoutObjects.elementAt(i)).getOwningItem() == item)
{
LayoutObject lo = (LayoutObject)(layoutObjects.elementAt(i));
lo.setRowIdx(-1);
return lo;
}
}
return null;
}
/**
* Removes Layout Object for the given Item.
*
* @param item Item to remove LayoutObject
*/
void removeLayoutObject(Item item)
{
int num = layoutObjects.size();
for(int i = 0; i < num; i++)
{
if(((LayoutObject)layoutObjects.elementAt(i)).getOwningItem() == item)
{
LayoutObject lo = (LayoutObject)(layoutObjects.elementAt(i));
layoutObjects.removeElement(lo);
lo.dispose();
break;
}
}
}
/**
* Removes all Layout Objects from the FormLayouter.
*/
void removeAllLayoutObjects()
{
Enumeration e = layoutObjects.elements();
while(e.hasMoreElements())
{
LayoutObject lo = (LayoutObject)e.nextElement();
layoutObjects.removeElement(lo);
lo.dispose();
}
}
/**
* Returns the next LayoutObject belonging to this Item.<br>
*
* @param lo starting LayoutObject; if null then it start from first.
* @param item specifies the parent Item; null means any Item
* @return the next LayoutObject in the layout.
*/
LayoutObject getNextLayoutObjectOfItem(LayoutObject lo, Item item)
{
int startRow = (lo == null ? 0 : lo.getRowIdx());
LayoutObject temp = null;
for(int i = startRow; i < getRowCount(); i++)
{
temp = getRow(i).getNextLayoutObject(lo, item);
if(temp != null && temp != lo)
{
return temp;
}
}
return null;
}
/**
* Returns the previous LayoutObject belonging to this Item.<br>
*
* @param lo starting LayoutObject; if null then it start from last.
* @param item specifies the parent Item; null means any Item
* @return the previous LayoutObject in the layout.
*/
LayoutObject getPrevLayoutObjectOfItem(LayoutObject lo, Item item)
{
int startRow = (lo == null ? rows.size() - 1 : lo.getRowIdx());
LayoutObject temp = null;
for(int i = startRow; i >= 0; i--)
{
temp = getRow(i).getPrevLayoutObject(lo, item);
if(temp != null && temp != lo)
{
return temp;
}
}
return null;
}
/**
* Returns the first LayoutObject of a layouted item.
*
* @param item
* @return the LO, or NULL if the item is not layouted
*/
LayoutObject getFirstLayoutObjectOfItem(Item item)
{
if(item != null)
{
return getNextLayoutObjectOfItem(null, item);
}
return null;
}
/**
* Returns the last LayoutObject of a layouted item.
*
* @param item
* @return the LO, or NULL if the item is not layouted
*/
LayoutObject getLastLayoutObjectOfItem(Item item)
{
if(item != null)
{
return getPrevLayoutObjectOfItem(null, item);
}
return null;
}
/**
* Update item state in form.
*
* @param item
* @param updateReason
* @param param additional parameter
*/
void updateItemState(Item item, int updateReason, Object param)
{
Logger.method(this, "updateItemState", item,
String.valueOf(updateReason), param);
LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
getItemLayouter(item).updateItem(item, lo.getControl(), updateReason,
param);
}
// Clean reason - without resizing flags
int reason = updateReason & Item.UPDATE_SIZE_MASK;
switch(reason)
{
case Item.UPDATE_ADDCOMMAND:
{
if(isFormCurrent() && param != null)
{
if(lo != null && param instanceof Command)
{
lo.addCommand((Command) param);
}
}
break;
}
case Item.UPDATE_REMOVECOMMAND:
{
if(isFormCurrent() && param != null)
{
if(lo != null && param instanceof Command)
{
lo.removeCommand((Command) param);
}
}
break;
}
}
// Check this always - because this is a flag
if((updateReason & Item.UPDATE_HEIGHT_CHANGED)
== Item.UPDATE_HEIGHT_CHANGED)
{
resizeItemAndShift(item);
}
}
int eswtGetMaxVPosition()
{
return formComposite.getSize().y - getFormHeight();
}
/**
* Called by key Form to compute new vertical coordinate to position form's
* content.
*
* @param swtDir scrolling direction.
* @return New vertical position of Form's content.
*/
protected int getNextScrollingPosition(int swtDir)
{
boolean scrollDown = (swtDir == SWT.ARROW_DOWN
|| swtDir == SWT.ARROW_RIGHT);
int formHeight = getFormHeight();
int refPoint;
int ret = vPosition;
if(scrollDown)
{
ret += formHeight / 5;
refPoint = (vPosition + 1) + formHeight;
}
else
{
ret -= formHeight / 5;
refPoint = (vPosition - 1);
}
Row row = null;
for(int i = 0; i < getRowCount(); i++)
{
row = getRow(i);
if(row.isInsideRow(refPoint)
&& (row.getRowHeight() < formHeight))
{
if(scrollDown)
{
ret = row.getBottomPosition() - formHeight;
}
else
{
ret = row.getYShift();
}
break;
}
}
return ret;
}
/**
* Set the scrolling to the specified position.<br>
* This method also updates the Form's scrollbars.
*
* @param position new position
*/
void eswtSetScrollingPosition(int position, boolean keyNav)
{
// check constraints
int newVPosition = position;
int maxVPos = eswtGetMaxVPosition();
newVPosition = Math.min(newVPosition, maxVPos);
newVPosition = Math.max(newVPosition, 0);
vPosition = newVPosition;
formComposite.setRedraw(false);
formComposite.setOrigin(0, vPosition, keyNav);
formComposite.pack();
formComposite.setRedraw(true);
eswtHandleVisibilityChanges();
}
/**
* Returns the scrolling position.
*/
protected int getScrollingPosition()
{
return vPosition;
}
/**
* Updates visibility status of all items.
*/
protected void eswtHandleVisibilityChanges()
{
// Logger.method(this, "eswtHandleVisibilityChanges");
boolean shown = false;
Item item = null;
LayoutObject lo = null;
// Go through all LayoutObjects and check/update visibilities
while((lo = getNextLayoutObjectOfItem(lo, null)) != null)
{
// check if owning item is changing
if(lo.getOwningItem() != item)
{
if(item != null)
{
// set current item's visibility
getItemLayouter(item).eswtHandleVisibilityChange(item, shown);
}
// new item
item = lo.getOwningItem();
shown = false;
}
// track current item's visibility
if(!shown && isFormCurrent() && isPartiallyVisible(lo))
{
shown = true;
}
}
// call it for last item
if(item != null)
{
getItemLayouter(item).eswtHandleVisibilityChange(item, shown);
}
eswtCheckCurrentSelectedItem();
}
/**
* Changes item size and does shift of all Rows.
*
* @param item - item to changeSize.
*/
void resizeItemAndShift(final Item item)
{
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
int newVPosition = eswtResizeItemAndShift(item);
if(newVPosition != vPosition)
{
eswtSetScrollingPosition(newVPosition, true);
}
else
{
eswtHandleVisibilityChanges();
}
}
});
}
/**
* eSWT callback for resizeItemAndShift.
*
* @param item Item to be resized.
* @return new scrolling vPosition
*/
int eswtResizeItemAndShift(Item item)
{
// save the state of the last row before resizing and Shifting.
boolean itemWasVisible = isItemPartiallyVisible(item);
Row row = getLastRowOfItem(item);
int deltaYShift = row.getRowHeight();
// if we un-comment this then when we set true,
// focus will jump to first item.
// eswtUpdateFormComposite(false);
LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
getItemLayouter(item).eswtResizeObject(lo);
}
eswtUpdateRow(row);
/*
// to avoid double call of updateRowInternals
if (row.getNumLayoutObjects(Item.LAYOUT_SHRINK) > 0
|| row.getNumLayoutObjects(Item.LAYOUT_EXPAND) > 0) {
eswtUpdateRow(row);
}
else {
row.updateRowInternals();
}
*/
deltaYShift = row.getRowHeight() - deltaYShift;
// row's height change - all remaining rows are shifted with this.
Row lastRow = getLastRow();
if(row != lastRow)
{
for(int i = rows.indexOf(row) + 1; i < getRowCount(); i++)
{
row = getRow(i);
row.setYShift(row.getYShift() + deltaYShift);
eswtUpdateRow(row);
}
}
// following code updates scrolling if needed.
// need to check where in the form resize happeneed.
int itemRowYShift = getLastRowOfItem(item).getYShift();
// vPosition should be changed in syncexec
int newVPosition = vPosition;
// we need to scroll only if changes happened above the screen.
if(newVPosition >= itemRowYShift)
{
newVPosition = Math.max(0, newVPosition + deltaYShift);
}
// check to avoid gap in the bottom of the form
if(newVPosition + getFormHeight() > lastRow.getBottomPosition())
{
newVPosition = Math.max(0,
lastRow.getBottomPosition() - getFormHeight());
}
eswtUpdateFormComposite(true);
// formComposite.pack();
// formComposite.updateScrollbarSize(lastRow.getBottomPosition());
if(item == currentSelectedItem)
{
if(itemWasVisible)
{
int itemTop = getItemTopPosition(item);
int itemBottom = getItemBottomPosition(item);
// currentSelectedItem has to be focused if it was focused
// before resizing e.g TextField when it is resized by adding a
// new row and it was in the bottom of the Screen.
if(newVPosition <= itemTop
&& (newVPosition + getFormHeight()) >= itemBottom)
{
// do not change vPosition;
}
else if(newVPosition > itemTop)
{
newVPosition = itemTop;
}
else if((newVPosition + getFormHeight()) < itemBottom)
{
newVPosition = itemBottom - getFormHeight();
}
}
}
return newVPosition;
}
/**
* Gets layouter that can layout the specified item.
*
* @param item Item to be layouted.
* @return ItemLayouter or null if no Layouter found.
*/
protected final ItemLayouter getItemLayouter(Item item)
{
if(item instanceof StringItem)
{
return sIL;
}
else if(item instanceof ImageItem)
{
return imIL;
}
else if(item instanceof Gauge)
{
return gL;
}
else if(item instanceof TextField)
{
return tfL;
}
else if(item instanceof DateField)
{
return dfL;
}
else if(item instanceof ChoiceGroup)
{
return cgL;
}
else if(item instanceof CustomItem)
{
return ciL;
}
else if(item instanceof Spacer)
{
return sL;
}
return null;
}
/**
* Returns eSWT Control that represents the item specified.
*/
Control getItemControl(final Item item)
{
final LayoutObject lo = getFirstLayoutObjectOfItem(item);
if(lo != null)
{
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
itemMainControl = getItemLayouter(item)
.eswtFindSpecificControl(item, lo.getControl());
}
});
return itemMainControl;
}
return null;
}
void updateScrolling(final int value, final boolean keyNav)
{
Logger.method("updateScrolling", String.valueOf(value));
ESWTUIThreadRunner.syncExec(new Runnable()
{
public void run()
{
eswtSetScrollingPosition(value, keyNav);
}
});
}
/**
* Get current scrolling value.
*
* @return vPosition.
*/
int getVPosition()
{
return vPosition;
}
private Timer eventTimer = new Timer();
private EventGeneratorTask eventTask;
/**
* Reset timer for do layout with a given start index.
*/
private void resetEventTimer(boolean directionUp, int y)
{
if(eventTimer != null)
{
if(eventTask != null)
{
eventTask.cancel();
eventTask = null;
}
// schedule new timer
eventTask = new EventGeneratorTask(directionUp, y);
eventTimer.schedule(eventTask, Config.DFI_EVENT_TIMER_DELAY);
}
}
/**
* Form Timer task. Triggers the formComposite to Layout.
*/
class EventGeneratorTask extends TimerTask
{
private boolean isUpDirection;
private int localY;
public EventGeneratorTask(boolean direction, int y)
{
isUpDirection = direction;
localY = y;
Logger.info("y is " + localY);
}
public void run()
{
if(isUpDirection)
{
localY -= Config.DFI_EVENT_MOVE_DELTA;
}
else
{
localY += Config.DFI_EVENT_MOVE_DELTA;
}
handlePointerEvent(0, localY, SWT.MouseMove);
}
}
}