/* * * * Copyright 1990-2009 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License version * 2 only, as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License version 2 for more details (a copy is * included at /legal/license.txt). * * You should have received a copy of the GNU General Public License * version 2 along with this work; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa * Clara, CA 95054 or visit www.sun.com if you need additional * information or have any questions. */ package javax.microedition.lcdui; import com.sun.midp.log.Logging; import com.sun.midp.log.LogChannels; import com.sun.midp.configurator.Constants; /** * Layout management class for <code>Form</code>. * See DisplayableLF.java for naming convention. */ class LayoutManager { // Required test: multiple/simultaneous forms with different layout /** * Singleton design pattern. Obtain access using instance() method. */ LayoutManager() { sizingBox = new int[3]; // x,y,width } /** * Do layout. * SYNC NOTE: caller must hold LCDUILock around a call to this method * * @param layoutMode one of <code>FULL_LAYOUT</code> or * <code>UPDATE_LAYOUT</code> * @param numOfLFs number of elements in the calling form * @param itemLFs reference to the items array of the calling form * @param inp_viewportWidth width of the screen area available for the form * @param inp_viewportHeight height of the screen area available * for the form * @param viewable area needed for the content of the form */ void lLayout(int layoutMode, ItemLFImpl[] itemLFs, int numOfLFs, int inp_viewportWidth, int inp_viewportHeight, int[] viewable) { viewportWidth = inp_viewportWidth; viewportHeight = inp_viewportHeight; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "\n<<<<<<<<<< Doing " + (layoutMode == FULL_LAYOUT ? "FULL_LAYOUT" : "UPDATE_LAYOUT") + "... >>>>>>>>>>"); } if (layoutMode == FULL_LAYOUT) { // first Layout updateBlock(0, 0, true, itemLFs, numOfLFs, viewable); } else { // UPDATE_LAYOUT // most of the time only one or two items are updated at a time. // here we find the minimum items that needs a re-layout, and // calling layout for them if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "UPDATE_LAYOUT - START"); } // loop on all the items, and find which item needs an update // If more than one Item needs update, they both will update, // in their layout order. // * we keep a "moving anchor" to use when we identify // an invalid Item. This anchor is always at the beginning // of the row above the current Item checked, or at the // beginning of the row of the current Item, in case there // was an explicit line break. int anchorIndex = 0; // this index is needed to identify new lines to be set as // anchors later. int newLineIndex = 0; // find where to start the layout. It should be at the beginning // of the line above the invalid item, or of the same line // in case the line break is explicit. // We loop on all the Items to find the first invalid, while // keeping the anchorIndex up do date. When calling "updateBlock", // the index will jump directly to the next block, because we laid // out all the Items in that block. for (int index = 0; index < numOfLFs; index++) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "\n["+itemLFs[index]+"]" + "BEFORE: index: " +index + "\t[" + itemLFs[index].bounds[X] + "," + itemLFs[index].bounds[Y] + "," + itemLFs[index].bounds[WIDTH] + "," + itemLFs[index].bounds[HEIGHT] + "]\t newLine?" + itemLFs[index].isNewLine + " lineHeight=" + itemLFs[index].rowHeight + "\t actualBoundsInvalid[" + itemLFs[index].actualBoundsInvalid[X] + "," + itemLFs[index].actualBoundsInvalid[Y] + "," + itemLFs[index].actualBoundsInvalid[WIDTH] + "," + itemLFs[index].actualBoundsInvalid[HEIGHT] + "]\t ** viewable: " + index + "\t[" + viewportWidth + "," + viewable[HEIGHT] +"]"); } if (itemLFs[index].actualBoundsInvalid[WIDTH] || itemLFs[index].actualBoundsInvalid[X]) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "> WIDTH or X is invalid!"); } // if width is changed, than we have to do a layout two // a block of Items. So it covers the height as well call // layout block. The index will jump to the first item on // the next block. // return the last item on the block: index = updateBlock(anchorIndex, index, false, itemLFs, numOfLFs, viewable); // set the anchor on the next item, if there is one. // if (i+1) is larger than the length of itemLFs, the for // loop will end anyway so we don't have to check it here. anchorIndex = index + 1; } else if (itemLFs[index].actualBoundsInvalid[HEIGHT]) { // item current height int h = itemLFs[index].bounds[HEIGHT]; // item preferred height int ph = itemLFs[index].lGetAdornedPreferredHeight( itemLFs[index].bounds[WIDTH]); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "> HEIGHT is invalid from:" + h + " to:" + ph); } if (h != ph) { itemLFs[index].lSetSize(itemLFs[index].bounds[WIDTH], ph); itemLFs[index].rowHeight += (ph-h); // NOTE: We should check whole row height, // instead of just this item. itemLFs[index].actualBoundsInvalid[HEIGHT] = false; if (numOfLFs > index+1) { itemLFs[index+1].actualBoundsInvalid[Y] = true; // NOTE: We should calculate new LineHeight, // instead of just Item Height updateVertically(index+1, itemLFs, numOfLFs, viewable); } else { // only need to update the viewable viewable[HEIGHT] += (ph - h); } } } else if (itemLFs[index].actualBoundsInvalid[Y]) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "> *only* Y is invalid for #" + index); } updateVertically(index, itemLFs, numOfLFs, viewable); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "> Y - done"); } } else { // current item is valid. // check if i can be a new anchor point // (has an explicit line break before it, // or after the previous Item). if (itemLFs[index].isNewLine) { if (itemLFs[index].equateNLB() || ((index > 0) && (itemLFs[index-1].equateNLA()))) { // explicit newline anchorIndex = index; newLineIndex = index; } else { // implicit newline // we can move the anchor to the next line: // set the anchorIndex to be the old newLineIndex // (which is the first item on the row above the // current item). anchorIndex = newLineIndex; // set i as the first in its line newLineIndex = index; } } } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "AFTER: index: " + index + "\t[" + itemLFs[index].bounds[X] + "," + itemLFs[index].bounds[Y] + "," + itemLFs[index].bounds[WIDTH] + "," + itemLFs[index].bounds[HEIGHT] + "]\t newLine?" + itemLFs[index].isNewLine + " lineHeight=" + itemLFs[index].rowHeight + "\t actualBoundsInvalid[" + itemLFs[index].actualBoundsInvalid[X] + "," + itemLFs[index].actualBoundsInvalid[Y] + "," + itemLFs[index].actualBoundsInvalid[WIDTH] + "," + itemLFs[index].actualBoundsInvalid[HEIGHT] + "]\t ** viewable: " +index + "\t[" + viewportWidth + "," + viewable[HEIGHT] + "]"); } } // for loop if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "UPDATE_LAYOUT - DONE"); } } // correct viewable area if required // if there are no items in the form just reset viewable[HEIGHT] if (numOfLFs == 0) { viewable[HEIGHT] = 0; } } /** * Used both to do a full layout or just update a layout. * * assumptions: startIndex<=invalidIndex * * @param startIndex The index to start the layout. Should start a row. * @param invalidIndex The index causing the re-layout, should * be equal or greater than startIndex * @param fullLayout if <code>true</code>, does a full layout and ignores * the rest of the parameters sent to this method. * @param itemLFs reference to the items array of the calling form * @param numOfLFs number of elements in the calling form * @param viewable area needed for the content of the form * * @return the index of the last <code>Item</code> laid out */ private int updateBlock(int startIndex, int invalidIndex, boolean fullLayout, ItemLFImpl[] itemLFs, int numOfLFs, int[] viewable) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "\n - updateBlock(START="+startIndex +", INVALID="+invalidIndex +", Full Layout="+fullLayout+") {"); } // SYNC NOTE: layout() is always called from within a hold // on LCDUILock int oldWidth = viewable[WIDTH]; int oldHeight = viewable[HEIGHT]; // If we don't have any Items, just return if (numOfLFs == 0) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " we don't have any Items, just return }"); } return 0; } int rowStart; if (fullLayout) { // The index of the first Item in the horizontal row rowStart = 0; } else { rowStart = startIndex; } // The sizingBox starts out life with the size of the viewport, // but gets whittled down as each Item gets laid out and occupies // space in it. It effectively keeps a running total of what space // is available due to the Items which have already been laid out // We only allow space for the traversal indicator if we // have more than one item - because we only draw the traversal // indicator if we have more than one item to traverse to. // LF's width is set to the maximum allowable width, // while view's height is initialized with initial padding and // and grows when new row is added. sizingBox[X] = 0; sizingBox[Y] = 0; sizingBox[WIDTH] = viewportWidth; viewable[WIDTH] = viewportWidth; if (fullLayout) { viewable[HEIGHT] = 0; } else if (numOfLFs > 1 && startIndex > 0) { sizingBox[Y] = itemLFs[startIndex-1].bounds[Y] + itemLFs[startIndex-1].rowHeight; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "sizingBox[Y]=" + sizingBox[Y]); } } // A running variable which maintains the height of the // tallest item on a line int lineHeight = 0; int pW, pH; String locale = System.getProperty("microedition.locale"); if (locale != null && locale.equals("he-IL")) { rl_direction = true; } else { rl_direction = false; } int curAlignment = (rl_direction) ? Item.LAYOUT_RIGHT : Item.LAYOUT_LEFT; // We loop through the Items starting in startIndex, until we reach // the end of the block, and return the index of the next block, // or just finishing the for loop if this is the last block. for (int index = startIndex; index < numOfLFs; index++) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "..\n\tFOR LOOP: startIndex=" + startIndex + " index=[" + index + "] invalidIndex=" + invalidIndex); } // If the Item can be shrunken, get its minimum width, // and its preferred if it is not if (itemLFs[index].shouldHShrink()) { pW = itemLFs[index].lGetAdornedMinimumWidth(); } else { if (itemLFs[index].lGetLockedWidth() != -1) { pW = itemLFs[index].lGetLockedWidth(); } else { // if height is locked default preferred width // will be used, otherwise width will be calculated // based on lockedHeight pW = itemLFs[index].lGetAdornedPreferredWidth( itemLFs[index].lGetLockedHeight()); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " no shrink - locked w - pW=" + pW + " viewable[width]=" + viewable[WIDTH]); } } } // We have a notion of the maximum allowable width an Item can // have, so we enforce it first here: if (!Constants.SCROLLS_HORIZONTAL && (pW > viewable[WIDTH])) { pW = viewable[WIDTH]; } // We use a separate boolean here to check for each case of // requiring a new line (rather than the if() from hell) boolean newLine = (index > 0 && itemLFs[index - 1].equateNLA() || itemLFs[index].equateNLB() || // no room for this item on the same row pW > sizingBox[WIDTH]); if (isImplicitLineBreak(curAlignment, index, itemLFs)) { curAlignment = itemLFs[index].getLayout() & LAYOUT_HMASK; newLine = true; } // We linebreak if there is an existing row; // possible only when there is more than 1 item if (newLine && (lineHeight > 0)) { // index > 0, as the calculation of newLine guarantee // // ** NEW LINE ** // if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " --new line--"); } // First, handle current row's layout directives try { // used for layout update // int oldRowHeight = itemLFs[index-1].rowHeight; boolean wasNewLine = itemLFs[index].isNewLine; // now it's certainly first in line itemLFs[index].isNewLine = true; // layout items in previous row lineHeight = layoutRowHorizontal(rowStart, index - 1, sizingBox[WIDTH], lineHeight, itemLFs); layoutRowVertical(rowStart, index - 1, lineHeight, itemLFs, numOfLFs); if (fullLayout) { if (numOfLFs > 1) { viewable[HEIGHT] += lineHeight; } else { viewable[HEIGHT] += lineHeight + 1; } } else { // UPDATE_LAYOUT if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "** 1 ** row height=" + lineHeight); } // cases: // 1. this item was first in row already // > than update layout wouldn't be called for the // > row above it unless there was a change in that // > row. // > Therefore we can finish the loop, and just update // > the Y coordinates for the rest of the Items. if (wasNewLine && index > invalidIndex) { // we can call updateVertically by returning // index-1 and marking next Y as invalid. itemLFs[index].actualBoundsInvalid[Y] = true; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report( Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " returning index-1 and "+ "marking next Y as invalid. }"); } return (index-1); // (some Items after it may still be invalid, // so the calling method will continue the layout). } // 2. this item wasn't first on its row // > then we should continue a regular layout. if (!wasNewLine) { itemLFs[index].actualBoundsInvalid[X] = true; } } } catch (Throwable t) { Display.handleThrowable(t); } // Then, reset the sizingBox, lineHeight, and rowStart sizingBox[X] = 0; if (fullLayout) { sizingBox[Y] = viewable[HEIGHT]; } else { sizingBox[Y] = itemLFs[index-1].bounds[Y] + itemLFs[index-1].rowHeight; if (numOfLFs <= 1) { sizingBox[Y] += 1; } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "** 2 ** sizingBox[Y]=" + sizingBox[Y]); } } sizingBox[WIDTH] = viewportWidth; lineHeight = 0; rowStart = index; itemLFs[index].isNewLine = true; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " (new line end)"); } // // ** NEW LINE - END ** // } else { // keep isNewLine flag up to date if (index == 0 || newLine) { itemLFs[index].isNewLine = true; } else { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "** " +index +" is not a new line **"); } // this is not a new line itemLFs[index].isNewLine = false; } } pH = getItemHeight(index, pW, itemLFs); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " updateBlock.. pH = " +pH); } // If the Item is changing size, set the flag so that callPaint() // will call the Item's sizeChanged() method before painting if (oldWidth != viewportWidth || oldHeight != viewportHeight || itemLFs[index].bounds[WIDTH] != pW || itemLFs[index].bounds[HEIGHT] != pH) { itemLFs[index].sizeChanged = true; } if (!fullLayout && (index > invalidIndex)) { // if we've reached the end of the block (explicit linebreak) // than we can safely return if (itemLFs[index].equateNLB() || ((index > 0) && (itemLFs[index-1].equateNLA()))) { // we can stop the loop, returning the last Item laid out if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "** stop layout, explicit lb **\n}"); } itemLFs[index].actualBoundsInvalid[Y] = true; return (index - 1); // identify more occasions where only Y will change } else if (itemLFs[index].bounds[X] == sizingBox[X] && // itemLFs[index].bounds[Y] == sizingBox[Y] && itemLFs[index].bounds[WIDTH] == pW && itemLFs[index].bounds[HEIGHT] == pH) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "\n** no need to layout **\n}"); } itemLFs[index].actualBoundsInvalid[X] = false; // notice the "true": (only the Y coordinate is invalid) itemLFs[index].actualBoundsInvalid[Y] = true; itemLFs[index].actualBoundsInvalid[WIDTH] = false; itemLFs[index].actualBoundsInvalid[HEIGHT] = false; return (index - 1); } } // We assign bounds to the item, which is its pixel location, // width, and height in coordinates which represent offsets // of the viewport origin (that is, are in the viewport // coordinate space) itemLFs[index].lSetSize(pW, pH); itemLFs[index].lSetLocation(sizingBox[X], sizingBox[Y]); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] index ("+ index + " lineHeight == "+ lineHeight + ") set height to:" + pH); } itemLFs[index].actualBoundsInvalid[X] = false; itemLFs[index].actualBoundsInvalid[Y] = false; itemLFs[index].actualBoundsInvalid[WIDTH] = false; itemLFs[index].actualBoundsInvalid[HEIGHT] = false; // If this Item is currently the tallest on the line, its // height becomes our prevailing lineheight if (pH > lineHeight) { lineHeight = pH; } // We whittle down the sizingBox by the Item's dimensions, // effectively maintaining how much space is left for the // remaining Items, if the item has some positive width if (pW > 0) { sizingBox[WIDTH] -= pW; // we know that item fits on this row but padding // might not fit if (sizingBox[WIDTH] < 0) { sizingBox[WIDTH] = 0; } sizingBox[X] += pW; } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "\t\tindex: " +index + "\t[" + itemLFs[index].bounds[X] + "," + itemLFs[index].bounds[Y] + "," + itemLFs[index].bounds[WIDTH] + "," + itemLFs[index].bounds[HEIGHT] + "]"); } } // for // Before we quit, layout the last row we did in the loop try { int oldRowHeight = itemLFs[rowStart].rowHeight; lineHeight = layoutRowHorizontal(rowStart, numOfLFs - 1, sizingBox[WIDTH], lineHeight, itemLFs); int rowY = itemLFs[rowStart].bounds[Y]; layoutRowVertical(rowStart, numOfLFs - 1, lineHeight, itemLFs, numOfLFs); viewable[HEIGHT] = rowY + lineHeight; } catch (Throwable t) { Display.handleThrowable(t); } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " returning invalidIndex:"+invalidIndex+" }"); } return invalidIndex; } /** * Calculating how many pixels should the <pre>startIndex<> item move up * or down, and loop from this item until the end, adding the delta * to all these items. * We know where this startIndex should be, we know where it is * now, so we can know how much to move everything. * * The viewable height is updated accordingly. * * @param startIndex the index of the first item that should move * up or down. It should be first in its row, * and the Item before it should be laid out * correctly, with rowHeight set up. * @param itemLFs reference to the items array of the calling form * @param numOfLFs number of elements in the calling form * @param viewable area needed for the content of the form */ private void updateVertically(int startIndex, ItemLFImpl[] itemLFs, int numOfLFs, int[] viewable) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "### in updateVertically for #" + startIndex + ".\t"); } int deltaY = 0; int newY = 0; // loop on all the items, starting with this one, // updating their Y, and unmark their flag if (startIndex == 0) { newY = 0; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "newY=" + newY); } } else { newY = itemLFs[startIndex-1].bounds[Y] + itemLFs[startIndex-1].rowHeight; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " itemLFs[si-1].bounds[Y]=" + itemLFs[startIndex-1].bounds[Y] + " itemLFs[si-1].rowHeight=" + itemLFs[startIndex-1].rowHeight); } } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, ">>> CustomItemLFImpl -- lRepaint()" + " itemLFs[si].bounds[Y]=" + itemLFs[startIndex].bounds[Y] + " newY=" + newY); } deltaY = newY - itemLFs[startIndex].bounds[Y]; if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " delta= " + deltaY); } if (deltaY == 0) { itemLFs[startIndex].actualBoundsInvalid[Y] = false; return; } for (int i = startIndex; i < numOfLFs; i++) { itemLFs[i].lMove(0, deltaY); } itemLFs[startIndex].actualBoundsInvalid[Y] = false; // update viewable height viewable[HEIGHT] += deltaY; } /** * After the contents of a row have been determined, layout the * items on that row, taking into account the individual items' * horizontally oriented layout directives. * * @param rowStart the index of the first row element * @param rowEnd the index of the last row element * @param hSpace the amount of empty space in pixels in this row before * inflation * @param rowHeight the old row height * @param itemLFs reference to the items array of the calling form * * @return the new rowHeight for this row after all of the inflations */ private int layoutRowHorizontal(int rowStart, int rowEnd, int hSpace, int rowHeight, ItemLFImpl[] itemLFs) { // SYNC NOTE: protected by the lock around calls to layout() if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] layoutRowHorizontal -- rowStart=" + rowStart + " rowEnd="+rowEnd + " hSpace=" + hSpace + " rowHeight="+rowHeight); } hSpace = inflateHShrinkables(rowStart, rowEnd, hSpace, itemLFs); hSpace = inflateHExpandables(rowStart, rowEnd, hSpace, itemLFs); // if any of the items were inflated we have to recalculate // the new row height for this row rowHeight = 0; for (int i = rowStart; i <= rowEnd; i++) { if (rowHeight < itemLFs[i].bounds[HEIGHT]) { rowHeight = itemLFs[i].bounds[HEIGHT]; } } if (hSpace == 0) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] layoutRowHorizontal -- done -- " + "(hSpace == 0) -- returning "+rowHeight); } return rowHeight; } int curAlignment = getCurHorAlignment(itemLFs, rowStart); switch (curAlignment) { case Item.LAYOUT_CENTER: hSpace = hSpace / 2; /* fall through */ case Item.LAYOUT_RIGHT: for (; rowStart <= rowEnd; rowStart++) { itemLFs[rowStart].lMove(hSpace, 0); } break; case Item.LAYOUT_LEFT: default: break; } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] layoutRowHorizontal -- done " + "-- returning "+rowHeight); } return rowHeight; } /** * Gets the current horizontal alignment of the item. If Item's * horizontal layout bits are not set its current horizontal * alignment is the same as of the previous Item. * * @param itemLFs reference to the items array of the calling form * @param index the index of an item in the itemLFs array which * current horizontal alignment needs to be found out * @return currentl horizontal alignment of an Item with passed in * index */ private int getCurHorAlignment(ItemLFImpl[] itemLFs, int index) { for (int hAlign, i = index; i >= 0; i--) { hAlign = itemLFs[i].getLayout() & LAYOUT_HMASK; if (hAlign != Item.LAYOUT_DEFAULT) return hAlign; } // default layout if (rl_direction) { return Item.LAYOUT_RIGHT; } else { return Item.LAYOUT_LEFT; } } /** * Inflate all the horizontally 'shrinkable' items on a row. * * @param rowStart the index of the first row element * @param rowEnd the index of the last row element * @param space the amount of empty space left in pixels in this row * @param itemLFs reference to the items array of the calling form * * @return the amount of empty space on this row after shinkage */ private int inflateHShrinkables(int rowStart, int rowEnd, int space, ItemLFImpl[] itemLFs) { // SYNC NOTE: protected by the lock around calls to layout() if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHShrinkables -- rowStart=" + rowStart + " rowEnd=" + rowEnd + " space=" + space); } if (space == 0) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHShrinkables -- returning " + "(space == 0)"); } return 0; } // To inflate shrinkables, we make a first pass gathering // the smallest proportion (the baseline) int baseline = Integer.MAX_VALUE; int pW, prop = 0; for (int i = rowStart; i <= rowEnd; i++) { if (itemLFs[i].shouldHShrink()) { pW = itemLFs[i].lGetLockedWidth(); if (pW == -1) { pW = itemLFs[i].lGetAdornedPreferredWidth( itemLFs[i].lGetLockedHeight()); } prop = pW - itemLFs[i].lGetAdornedMinimumWidth(); if (prop > 0 && prop < baseline) { baseline = prop; } } } // If there are no shrinkables, return if (baseline == Integer.MAX_VALUE) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHShrinkables -- returning " + "(baseline == Integer.MAX_VALUE) space == " + space); } return space; } prop = 0; // Now we loop again, adding up all the proportions so // we can find the adder for (int i = rowStart; i <= rowEnd; i++) { if (itemLFs[i].shouldHShrink()) { pW = itemLFs[i].lGetLockedWidth(); if (pW == -1) { pW = itemLFs[i].lGetAdornedPreferredWidth( itemLFs[i].lGetLockedHeight()); } prop += ((pW - itemLFs[i].lGetAdornedMinimumWidth()) / baseline); } } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHShrinkables -- prop == "+prop); } // Now we compute the adder, and add it to each of the // shrinkables, times its proportion int adder = space / prop; for (int i = rowStart; i <= rowEnd; i++) { if (itemLFs[i].shouldHShrink()) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "### item "+i+" before shrinking is:" + itemLFs[i].bounds[WIDTH]); } pW = itemLFs[i].lGetLockedWidth(); if (pW == -1) { pW = itemLFs[i].lGetAdornedPreferredWidth( itemLFs[i].lGetLockedHeight()); } space = pW - itemLFs[i].lGetAdornedMinimumWidth(); // The proportionate amount of space to add prop = adder * (space / baseline); // We only enlarge the item to its preferred width at // a maximum if (space > prop) { space = prop; } itemLFs[i].lSetSize(itemLFs[i].bounds[WIDTH] + space, getItemHeight(i, itemLFs[i].bounds[WIDTH] + space, itemLFs)); // Now we have to shift the rest of the elements on the line for (int j = i + 1; j <= rowEnd; j++) { itemLFs[j].lMove(space, 0); } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "### item " + i + " shrank to:" + itemLFs[i].bounds[WIDTH]); } } } space = viewportWidth - (itemLFs[rowEnd].bounds[X] + itemLFs[rowEnd].bounds[WIDTH]); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHShrinkables -- " + "returning (end). space == " + space); } return space; } /** * Inflate all the horizontally 'expandable' items on a row. * * @param rowStart the index of the first row element * @param rowEnd the index of the last row element * @param space the amount of empty space on this row * @param itemLFs reference to the items array of the calling form * * @return the amount of empty space after expansion */ private int inflateHExpandables(int rowStart, int rowEnd, int space, ItemLFImpl[] itemLFs) { // SYNC NOTE: protected by the lock around calls to layout() if (space == 0) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHExpandables -- " + "returning (space == 0)"); } return 0; } int numExp = 0; // We first loop through and count the expandables for (int i = rowStart; i <= rowEnd; i++) { if (itemLFs[i].shouldHExpand()) { numExp++; } } if (numExp == 0 || space < numExp) { if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHExpandables -- returning " + "(numExp == 0 || space < numExp) space = " + space); } return space; } space = space / numExp; // We then add the same amount to each Expandable for (int i = rowStart; i <= rowEnd; i++) { if (itemLFs[i].shouldHExpand()) { itemLFs[i].lSetSize(itemLFs[i].bounds[WIDTH] + space, getItemHeight(i, itemLFs[i].bounds[WIDTH] + space, itemLFs)); itemLFs[i].lGetContentSize(itemLFs[i].lGetContentBounds(),itemLFs[i].bounds[WIDTH] + space); // We right shift each subsequent item on the row for (int j = i + 1; j <= rowEnd; j++) { itemLFs[j].lMove(space, 0); } } } space = viewportWidth - (itemLFs[rowEnd].bounds[X] + itemLFs[rowEnd].bounds[WIDTH]); if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] inflateHExpandables -- " + "returning (end) space = " + space); } return space; } /** * After the contents of a row have been determined, layout the * items on that row, taking into account the individual items' * vertically oriented layout directives. * * @param rowStart the index of the first row element * @param rowEnd the index of the last row element * @param itemLFs reference to the items array of the calling form * @param lineHeight the overall height in pixels of the line * @param numOfLFs number of elements in the calling form */ private void layoutRowVertical(int rowStart, int rowEnd, int lineHeight, ItemLFImpl[] itemLFs, int numOfLFs) { // SYNC NOTE: protected by the lock around calls to layout() int space = 0; int pH = 0; for (int i = rowStart; i <= rowEnd; i++) { // set the row height itemLFs[i].rowHeight = lineHeight; // Items that have the LAYOUT_VSHRINK directive are expanded // to their preferred height or to the height of the row, // whichever is smaller. Items that are still shorter than // the row height and that have the LAYOUT_VEXPAND directive // will expand to the height of the row. if (itemLFs[i].shouldVExpand()) { itemLFs[i].lSetSize(itemLFs[i].bounds[WIDTH], lineHeight); } else if (itemLFs[i].shouldVShrink()) { pH = itemLFs[i].lGetLockedHeight(); if (pH == -1) { pH = itemLFs[i]. lGetAdornedPreferredHeight(itemLFs[i].bounds[WIDTH]); } if (pH > lineHeight) { pH = lineHeight; } itemLFs[i].lSetSize(itemLFs[i].bounds[WIDTH], pH); } // initially the items are aligned at the top so we simply // add on to the height switch (itemLFs[i].getLayout() & LAYOUT_VMASK) { case Item.LAYOUT_VCENTER: space = lineHeight - itemLFs[i].bounds[HEIGHT]; if (space > 0) { itemLFs[i].lMove(0, space / 2); } break; case Item.LAYOUT_TOP: // it's already there... break; case Item.LAYOUT_BOTTOM: // the layout algorithm must align the Items along the bottom // if there is no vertical directive specified. default: space = lineHeight - itemLFs[i].bounds[HEIGHT]; if (space > 0) { itemLFs[i].lMove(0, space); } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "Default V layout -- space = " + space + " itemLFs[i].Y = " + itemLFs[i].bounds[Y]); } } } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, "[F] layoutRowVertical -- done"); } } /** * This method checks if we need a new line due to change in * horizontal alignment. If horizontal alignment is not set on * <code>Item</code> with index thisItem then current horizontal * alignment is not changed and no row break is needed. * * @param curAlignment current horizontal alignment until this Item * @param thisItem index of the <code>Item</code> from which to start * the scan * @param itemLFs reference to the items array of the calling form * * @return <code>true</code> if a new line is needed */ private boolean isImplicitLineBreak(int curAlignment, int thisItem, ItemLFImpl[] itemLFs) { if (thisItem == 0) { return false; } int hAlign = itemLFs[thisItem].getLayout() & LAYOUT_HMASK; // alignment is not changed if (hAlign == 0) { return false; } return (hAlign != curAlignment); } /** * Get item's height based on the width. * * @param index the index of the item which height is being calculated * @param pW the width set for the item * @param itemLFs reference to the items array of the calling form * * @return the height of the item */ private int getItemHeight(int index, int pW, ItemLFImpl[] itemLFs) { // SYNC NOTE: protected by the lock around calls to layout() int pH; // If the Item can be shrunken, we use its minimum height, // and its preferred height if it is not if (itemLFs[index].shouldVShrink()) { pH = itemLFs[index].lGetAdornedMinimumHeight(); } else { pH = itemLFs[index].lGetLockedHeight(); if (pH == -1) { pH = itemLFs[index].lGetAdornedPreferredHeight(pW); } } // If we can't scroll vertically, clip the item's height // if it can't fit in the view. We would also enforce a // notion of a "maximum vertical height" here if we had one if (!Constants.SCROLLS_VERTICAL && pH > viewportHeight) { pH = viewportHeight; } if (Logging.REPORT_LEVEL <= Logging.INFORMATION) { Logging.report(Logging.INFORMATION, LogChannels.LC_HIGHUI_FORM_LAYOUT, " LayoutManager- getItemHeight("+index+","+pW+ ") returns "+pH); } return pH; } /** * Singleton design pattern: obtain access to the single instance of * this class using this method. * * @return Single instance of LayoutManager */ static LayoutManager instance() { return singleInstance; } /** * A bit mask to capture the horizontal layout directive of an item. */ static final int LAYOUT_HMASK = 0x03; /** * A bit mask to capture the vertical layout directive of an item. */ static final int LAYOUT_VMASK = 0x30; /** * 'sizingBox' is a [x,y,w,h] array used for dynamic sizing of * <code>Item</code>s during the layout. It starts with the size of the * viewport, but can shrink or grow according to the <code>Item</code> * it tries to lay out. * * It is used by layoutBlock and layoutRow. */ private int[] sizingBox; /** Do a full layout. */ final static int FULL_LAYOUT = -1; /** Only update layout. */ final static int UPDATE_LAYOUT = -2; /** * Single instance of the LayoutManager class. */ static LayoutManager singleInstance = new LayoutManager(); /** layout derection depend on the language conventions in use */ private boolean rl_direction; /** Used as an index into the viewport[], for the x origin. */ final static int X = DisplayableLFImpl.X; /** Used as an index into the viewport[], for the y origin. */ final static int Y = DisplayableLFImpl.Y; /** Used as an index into the viewport[], for the width. */ final static int WIDTH = DisplayableLFImpl.WIDTH; /** Used as an index into the viewport[], for the height. */ final static int HEIGHT = DisplayableLFImpl.HEIGHT; /** Width of viewport, as passed to layout(). */ int viewportWidth = 0; /** Height of viewport, as passed to layout(). */ int viewportHeight = 0; }