/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.ui;
import totalcross.res.*;
import totalcross.sys.*;
import totalcross.ui.event.*;
import totalcross.ui.font.*;
import totalcross.ui.gfx.*;
import totalcross.ui.image.*;
import totalcross.util.*;
/**
* This is a common grid component. The features are:
* <ul>
* <li>Vertical scrollbar to scroll up and down the information on the grid
* <li>Horizontal scrolling in case the columns widths are greater than the parent container's width
* <li>An easy to use interface for adding/removing information to the grid
* <li>An optional check column, which is a column that is clickable, marking an specific line
* as checked/unchecked. This is usefull if you want the user to be able to mark
* multiple lines displayed on the grid
* <li>Columns can be resized so that the user can see all the information displayed in a
* given column
* <li>Style configuration, you can set the color of captions boxes all the way thru the
* stripes colors and vertical line types. The grid is compatible with all user interface
* styles (Flat, WinCE, etc... )
* <li>The GridEvent class contains the events generated by the grid.
* <li>A column may be marked as editable. In this case, an Edit will be placed in the
* currently focused cell. A column can also have choices (ComboBox).
* <li>A column can also have a ComboBox associated.
* <li>The columns can be sorted by clicking in the title.
* <li>If the text is bigger than the size of the column, you can click and hold in the cell
* to display a tooltip with the full text.
* <li>You can customize cell back and fore colors, enabled state and choices by using the
* CellController class.
* <li>Clicking in the column's caption will sort in ascending order; clicking on it again
* sorts on descending order.
* <li>It is possible to assign a DataSource to a Grid, making the elements be fetched on demand.
* <li>A column with size 0 is not displayed, so you can use it to store in the grid important information,
* for example the rowid of a table.
* </ul>
*
* Here�s an example:
* <pre>
* Rect r = getClientRect();
*
* String []gridCaptions = {" WPT "," HDG "," DST "," ETE "," FUEL "};
* int gridWidths[] =
* {
* -25, // negative numbers are percentage of width
* fm.stringWidth(" 000 "),
* -25,
* -20,
* -20,
* };
* int gridAligns[] = { LEFT, CENTER, RIGHT, CENTER, RIGHT };
* grid = new Grid(gridCaptions, gridWidths, gridAligns, false);
* add(grid, LEFT+3,TOP+3,r.width/2,r.height/2);
* grid.secondStripeColor = Color.getRGB(235,235,235);
*
* String[][] data = new String[][]
* {
* {"0AAAA","000","000.0","00:00","00.0"},
* {"1BBBB","111","111.1","11:11","11.1"},
* {"2CCCC","222","222.2","22:22","22.2"},
* {"3DDDD","333","333.3","33:33","33.3"},
* {"4EEEE","444","444.4","44:44","44.4"},
* {"5FFFF","555","555.5","55:55","55.5"},
* };
* grid.setItems(data);
* </pre>
*
* This will create a grid with the given captions, column widths, information alignment
* and a check column.
* <p>
* Don't forget to take a look at tc.samples.ui.grid.GridTest.
* <p>
* Also consider using the ListContainer instead of Grid if you're using devices with big screens.
* See the UIGadgets sample, click menu / Tests2 / ListContainer.
*
* @since SuperWaba 5.5
*/
public class Grid extends Container implements Scrollable
{
/** Abstract class that must be implemented if you want to get a fine control of
* each cell in the grid. Note that you must keep track of the state of each cell
* in your code. How to proceed:
* <ul>
* <li> Create a class that extends CellController and implement each of the methods.
* Then call grid.setCellController passing an instance of the created class.
* <li> Each time a cell is drawn, the getForeColor, getBackColor and getFont methods are called.
* If you want to use the default values, return <code>null</code>.
* <li> If a column has choices activated (with setColumnChoices), each time the ComboBox
* will be opened, the getChoices method is called. This way, you can dynamically change the
* choices for each cell. If you want to use the default values, return <code>null</code>.
* <li> Each time a cell is clicked, the isEnabled method is called. If you return false to it,
* the event is not generated; also, when trying to open the ComboBox or the Edit assigned to
* the column, the control will not be activated. This is valid for a check column too.
* </ul>
* Note that using a CellControler makes the grid drawings a bit slower. Also, if the user sorts the
* columns, you may get scrambled. You can disable sort using <code>grid.disableSort = true</code>.
*/
public static abstract class CellController // guich@580_31
{
/** Must return the foreground color for the given cell, or -1 if none. */
public abstract int getForeColor(int row, int col);
/** Must return the background color for the given cell, or -1 if none. Note that if the grid has a check,
* the back color of the check cell will be requested passing col value as -1. */
public abstract int getBackColor(int row, int col);
/** Must return the choices for a given line and column, if setColumnChoices was set to this
* column. */
public abstract String[] getChoices(int row, int col);
/** Must return if the given cell is enabled, ie, can have input and can be selected.
* If the grid has a check column, returning false will disable the check state change. */
public abstract boolean isEnabled(int row, int col);
/** Must return the font to be used in this cell, or null to use the default control's font.
* Note: if you plan to use a font bigger than in the rest of the grid, be sure to follow
* these steps:
* <ol>
* <li> Construct the grid and set the grid's font to be the one with bigger size
* <li> Call add/setRect
* <li> Restore the grid's default font
* </ol>
* Following this guide, the grid will have a height of the biggest font.
* @since TotalCross 1.0
*/
public Font getFont(int row, int col) {return null;}
}
/** The Flick object listens and performs flick animations on PenUp events when appropriate. */
protected Flick flick;
/** Interface that can be used to fetch data on demmand. This makes the grid slower but
* uses much less memory. Here's a sample:
* <pre>
public String[][] getItems(int startIndex, int count)
{
if (activeRS != null)
{
activeRS.absolute(startIndex);
return activeRS.getStrings(count);
}
return null;
}
* </pre>
* See the AddressBook sample on Litebase.
* @see #lastStartingRow
*/
public interface DataSource // guich@570_87
{
/** Must return a matrix of items that will be displayed at the grid. */
public String[][] getItems(int startingRow, int count);
}
/** Draws a solid vertical line. Used in the property verticalLineStyle */
static public final int VERT_LINE = 1;
/** Draws a dotted vertical line. Used in the property verticalLineStyle */
static public final int VERT_DOT = 2;
/** Draws no vertical lines. Used in the property verticalLineStyle */
static public final int VERT_NONE = 3;
/** When the user clicks on the header check to check all, a CHECK_CHANGED_EVENT is dispatched,
* and the row is set as ALL_CHECKED. */
public static final int ALL_CHECKED = -900; // guich@580_16
/** When the user clicks on the header check, to uncheck all, a CHECK_CHANGED_EVENT is dispatched,
* and the row is set as ALL_UNCHECKED. */
public static final int ALL_UNCHECKED = -901; // guich@580_16
/**
* Set this to false if you dont want the check column to have the rect underneath the
* check mark
*/
public boolean drawCheckBox = true;
/** Check color. Defaults to black. */
public int checkColor = Color.BLACK;
/** Set to true to disable sorting when clicking in the column's header. */
public boolean disableSort; // guich@560_16
/** Set this to false if you dont want to let the user resize the columns */
public boolean enableColumnResize = true; // edisonbrito@563_1
/** How many pixels are used to define the place where the column will be resized.
* Defaults to 3 on pen devices, and 5 on touch devices (must be ODD!)
* @since TotalCross 1.22
*/
public static int columnResizeMargin = Settings.fingerTouch ? Font.NORMAL_SIZE / 2 + Font.NORMAL_SIZE % 2 : 3; // the %2 to keep it ODD
/**
* The column captions. Can be directly assigned, but always make sure it has the same
* number of elements of the widths array, set with the <code>setWidths</code>
* method.
*/
public String[] captions;
/** first stripe color. WHITE by default. */
public int firstStripeColor = Color.WHITE;
/** second stripe color. BRIGHT by default */
public int secondStripeColor = Color.BRIGHT;
/** The color of the selected (highlighted) row. DARK by default */
public int highlightColor = Color.DARK;
/** Sets the caption's box background color. BRIGHT by default */
public int captionsBackColor = Color.BRIGHT;
/** Set to false do don't let the highlighted (selected) row be drawn.
* @since TotalCross 1.39
*/
public boolean drawHighlight = true;
/** Sets the vertical line style of the grid. VERT_DOT by default.
* @see #VERT_LINE
* @see #VERT_DOT
* @see #VERT_NONE
*/
public int verticalLineStyle = VERT_DOT;
/** The current selected line, or -1 if none. */
protected int selectedLine = -1;
/** How many lines fits in a page */
protected int linesPerPage;
/** The text that was in the cell before the user had editted it. */
public String oldCellText; // guich@556_13
/** Set to false to disable the click on the check column of the captions to
* select all and unselect all
*/
public boolean canClickSelectAll=true; // guich@554_27
/** Set it to true to draw a bold check */
public boolean boldCheck; // guich@572_11
/** The tooltip used to display a column's text when it exceeds the column width.
* You can directly customize the tooltip. */
public ToolTip tip;
/** The extra height of the horizontal scroll buttons. Defaults 2 in 160x160 or a multiple of it
* in other resolutions.
* @see ListBox#EXTRA_HEIGHT_FACTOR
*/
public int extraHorizScrollButtonHeight = Settings.screenHeight*2/160; // guich@556_11 - guich@560_11: now depends on the resolution
/** Stores the last line that was requested from the current data source.
* The last lines retrieved will be cached to prevent requesting the same data.
* If you want to ensure that the data will be fetched again, set this member to -1 in getItems.
* @since TotalCross 1.0 beta 5
*/
public int lastStartingRow = -1; // guich@tc100b5_24
/** Defines the sort type for each column of the grid. Specify -2 to disable sort for that column.
* The array is created in the constructor setting all columns to Convert.SORT_AUTODETECT; so you can just change
* a specific column with the sort type you want.
* <br><br>The column index always excludes the check column. So a grid with 3 columns and a check, the sortTypes length will be 3,
* which will be the same length of a grid with 3 columns without a check.
* @see totalcross.sys.Convert#SORT_AUTODETECT
* @see totalcross.sys.Convert#SORT_OBJECT
* @see totalcross.sys.Convert#SORT_STRING
* @see totalcross.sys.Convert#SORT_INT
* @see totalcross.sys.Convert#SORT_DOUBLE
* @see totalcross.sys.Convert#SORT_DATE
* @see totalcross.sys.Convert#SORT_COMPARABLE
* @see totalcross.sys.Convert#SORT_STRING_NOCASE
*/
public int[] sortTypes;
/** Set to true before constructing the Grids to use an horizontal scrollbar instead of the two left/right buttons.
* Note that this member is static so it will affect all Grids created afterwards, unless you reset it to false.
* @since TotalCross 1.01
*/
public static boolean useHorizontalScrollBar; // guich@tc110_38
/** Set to true before calling setDataSource to enable live scrolling when using a DataSource.
* @see #setDataSource
*/
public boolean liveScrolling; // guich@tc110_42
/** Used to show a given (set of) column(s) formatted as currency.
* You must assign this with an array with the column count, and place the wanted number of decimal places, or -1
* if you don't want to format it. Note that the original value must be already formatted with the wanted
* number of decimal places, and also that only the displayed value is changed; the internal value will remain
* without formatting.
* Example:
* <pre>
* Grid g = new Grid(new String[]{"Name","Age","Salary"},false);
* g.currencyDecimalPlaces = new int[]{-1,-1,2}; // ignore name and age, and format salary with 2 decimal places.
* </pre>
*/
public int[] currencyDecimalPlaces; // guich@tc114_35
/** Set to false to disallow the selection of a row if the selected cell is disabled (via CellController).
* @since TotalCross 1.2
*/
public boolean enableSelectDisabledCell = true;
/** Set to true to enable navigation per line instead of per page when in non-penless mode.
* By default, in pen mode (devices with touch-screen), when the user press up/down, the grid is scrolled
* one page at a time. Setting this flag to true makes it scroll a line at a time.
* @since TotalCross 1.23
*/
public boolean lineScroll; // guich@tc122_55
/** Defines the border gap when using CellController.getBackColor. Setting it to something bigger than
* 0 will make a space between the cells.
* @since TotalCross 1.24
*/
public int borderGap; // guich@tc123_52
/** The aligns that are used in the constructor. You may change them after the constructor is called. */
public int[] aligns; //guich@tc125_8: field aligns is now public, as requested by users.
/** When the width of a title is greater than the width you specified for a the column, the title's width
* is used instead. Set this flag to true to disable this behaviour.
* @since TotalCross 1.39
*/
public boolean titleMayBeClipped;
// private members
private Hashtable htImages;
private int checkedCount;
private int lastGetItemsCount; // guich@tc100b5_24
private String[][] lastGetItems; // guich@tc100b5_24
private PenEvent lastPE;
private int lastSortCol=-1;
private boolean ascending = true;
private DataSource ds; // guich@570_87
protected Control[] controls; // guich@570_81: now a column may have edit OR a poplist.
private int[] widths,originalWidths;
private int defaultCheckWidth = 25;
private Vector vItems;
private IntVector ivChecks;
private boolean checkEnabled;
private int itemsCount;
private int xOffset, maxOffset;
private int resizingLine = -1, resizingDx, resizingRealX, resizingOrigWidth;
private int[] captionWidths;
private int gridOffset;
private IntHashtable ihtLinePoints;
private static GridEvent ge = new GridEvent();
private int fourColors[] = new int[4];
private boolean allChecked;
private ScrollBar sbVert,sbHoriz;
private ArrowButton btnLeft, btnRight;
private Rect rCheck, rBox, rTemp = new Rect(), absRect;
private CellController cc;
private boolean recomputeDefaultCaptionWidths; // check if widths was passed as null on constructor
private Container bag;
private int visibleLines;
private Control lastShownControl; // guich@560_25
private int showPlOnNextPenUp=-1;
private boolean isScrolling;
private boolean scScrolled;
private int lineH;
/**
* This will create a grid with the given captions, column widths, information
* alignment and an optional multi-selection check column so that the user can select
* multiple lines of the grid
*
* @param captions
* Captions for the columns. Cannot be null!
* @param widths
* Widths of the columns. If the total width is less than the grid's width,
* the last column will fill until the grid width. If null, the caption widths
* will be computed and used as the row width. If a negative value is passed,
* it will be computed as a percentage against the Grid's width.
* @param aligns
* Alignment of information on the given column. If null, all aligns will be LEFT.
* @param checkEnabled
* True if you want the multi-selection check column, false otherwise
*/
public Grid(String[] captions, int[] widths, int[] aligns, boolean checkEnabled)
{
ignoreOnAddAgain = ignoreOnRemove = true;
this.captions = captions;
if (Settings.screenHeight >= 480) // the tiny arrow is too ugly on iPhone
boldCheck = true;
this.focusTraversable = true; // kmeehl@tc100
if (widths == null) // guich@565_2
{
recomputeDefaultCaptionWidths = true;
widths = computeDefaultCaptionWidhts();
}
this.widths = widths;
originalWidths = new int[widths.length];
Vm.arrayCopy(widths, 0, originalWidths, 0, widths.length);
if (aligns == null) // guich@565_2
{
aligns = new int[widths.length];
Convert.fill(aligns, 0, aligns.length, LEFT);
}
if (captions.length != widths.length || aligns.length != captions.length) // guich@tc115_30
throw new RuntimeException("The caption ("+captions.length+"), widths ("+widths.length+") and align ("+aligns.length+") arrays must have the same length");
sortTypes = new int[widths.length];
Convert.fill(sortTypes, 0, sortTypes.length, Convert.SORT_AUTODETECT);
this.aligns = aligns;
this.checkEnabled = checkEnabled;
this.controls = new Control[captions.length+(checkEnabled?1:0)];
bag = new Container()
{
public void onPaint(Graphics g)
{
paint(g);
}
public void onEvent(Event e)
{
if (e.type == ControlEvent.PRESSED && e.target == tip) // guich@tc100b4_20: handle tip event.
onTip();
}
protected boolean willOpenKeyboard()
{
if (lastPE == null) return false;
int px = lastPE.x;
int py = lastPE.y;
int line = py / lineH - 1;
// finds the clicked column
int col = getColFromX(px,false);
if (col != -1 && controls[col] != null && selectedLine >= 0 && controls[col] instanceof Edit) // show the edit
{
int row0 = ds != null ? lastStartingRow : 0; // guich@tc114_55: consider the DataSource's starting row
if (cc != null && !cc.isEnabled(line+row0, col))
return false;
return controls[col].willOpenKeyboard();
}
return false;
}
};
bag.ignoreOnAddAgain = bag.ignoreOnRemove = true;
tip = new ToolTip(bag,""); // guich@tc100b4_20: add to the bag, not to this
tip.dontShowTipOnMouseEvents();
if (sbVert == null) // guich@tc114_52: may have been created in getPreferredWidth
sbVert = Settings.fingerTouch ? new ScrollPosition() : new ScrollBar(); // guich@580_15: instantiate the scrollbar before the grid is added to the container.
if (useHorizontalScrollBar || Settings.fingerTouch)
{
sbHoriz = Settings.fingerTouch ? new ScrollPosition(ScrollBar.HORIZONTAL) : new ScrollBar(ScrollBar.HORIZONTAL);
sbHoriz.setLiveScrolling(true);
}
onFontChanged();
vItems = new Vector(50);
if (checkEnabled)
{
ivChecks = new IntVector(50);
addCheckColumn();
}
clearValueInt = -1;
if (Settings.fingerTouch)
flick = new Flick(this);
}
/**
* This will create a grid with the given captions
* and an optional multi-selection check column so that the user can select
* multiple lines of the grid.
* The widths will be computed as the width of the grid captions and the alignment
* will be all LEFT.
*
* @param captions
* Captions for the columns
* @param checkEnabled
* True if you want the multi-selection check column, false otherwise
*/
public Grid(String[] captions, boolean checkEnabled) // guihc@565_2
{
this(captions, null, null, checkEnabled);
}
private int hbarX0,vbarY0,hbarDX,vbarDY;
private static final int NONE = 0;
private static final int VERTICAL = 1;
private static final int HORIZONTAL = 2;
private int flickDirection = NONE;
private boolean isFlicking;
public boolean flickStarted()
{
isFlicking = true;
return isScrolling;
}
public void flickEnded(boolean atPenDown)
{
isFlicking = false;
flickDirection = NONE;
}
public boolean canScrollContent(int direction, Object target)
{
if (flickDirection == NONE)
flickDirection = direction == DragEvent.UP || direction == DragEvent.DOWN ? VERTICAL : HORIZONTAL;
if (Settings.fingerTouch)
switch (direction)
{
case DragEvent.UP : return sbVert != null && sbVert.getValue() > sbVert.getMinimum();
case DragEvent.DOWN : return sbVert != null && (sbVert.getValue() + sbVert.getVisibleItems()) < sbVert.getMaximum();
case DragEvent.LEFT : return sbHoriz != null && sbHoriz.getValue() > sbHoriz.getMinimum();
case DragEvent.RIGHT: return sbHoriz != null && (sbHoriz.getValue() + sbHoriz.getVisibleItems()) < sbHoriz.getMaximum();
}
flickDirection = NONE;
return false;
}
private int lastV,lastH;
public boolean scrollContent(int dx, int dy, boolean fromFlick)
{
boolean scrolled = false;
if (flickDirection == VERTICAL && dy != 0 && sbVert != null)
{
vbarDY += dy;
int oldValue = sbVert.getValue();
sbVert.setValue(vbarY0 + vbarDY / lineH);
lastV = sbVert.getValue();
// have to set scrolled to true even if a full line is not scrolled
// because if we have a Grid inside a ScrollContainer it would not
// work if scrolled is set only inside the if below
scrolled = true;
if (oldValue != lastV)
{
gridOffset = lastV;
refreshDataSource();
if (!fromFlick) sbVert.tempShow();
}
}
if (flickDirection == HORIZONTAL && dx != 0 && sbHoriz != null)
{
hbarDX += dx;
int oldValue = sbHoriz.getValue();
sbHoriz.setValue(hbarX0 + hbarDX);
lastH = sbHoriz.getValue();
if (!fromFlick) sbHoriz.tempShow();
scrolled = true;
if (oldValue != lastH)
xOffset = -lastH;
}
if (scrolled)
Window.needsPaint = true;
return scrolled;
}
public boolean wasScrolled()
{
return scScrolled;
}
public Flick getFlick()
{
return flick;
}
public int getScrollPosition(int direction)
{
if (direction == DragEvent.LEFT || direction == DragEvent.RIGHT)
return xOffset;
return gridOffset;
}
private int[] computeDefaultCaptionWidhts()
{
int []widths = new int[captions.length];
for (int i = widths.length; --i >= 0;)
widths[i] = fm.stringWidth(captions[i]);
return widths;
}
/** Sets the CellController instance for this grid. Read the javadocs of the top of
* this page for more information.
* @since SuperWaba 5.8
* @see CellController
*/
public void setCellController(CellController cc) // guich@580_31
{
this.cc = cc;
}
/** Sets the column width of the given column. Positive values are used as pixels, negative values
* represent percentage of the grid's width and 0 hides the column.
* @since TotalCross 1.15
*/
public void setColumnWidth(int col, int newWidth) // guich@tc115_32
{
originalWidths[col] = widths[col] = newWidth;
setWidths(originalWidths);
}
/** Returns an array with the current column widths, not the original ones passed in the constructor.
* Changing these values will NOT change the column's width
* @see #setColumnWidth(int, int)
* @since TotalCross 1.15
*/
public int[] getColumnWidths() // guich@tc115_68
{
int[] ret = new int[widths.length];
Vm.arrayCopy(widths, 0, ret, 0, widths.length);
return ret;
}
/** Sets the given column as an editable one. Note that setting an editable column as
* not editable will remove all its formats. Returns the previous Edit or the new one,
* so you can easily change its formats.
* Important: this must be called AFTER the grid has set its bounds.
*/
public Edit setColumnEditable(int col, boolean editable)
{
Edit ed;
if (col < 0 || col >= captions.length)
return null;
if (checkEnabled)
col++;
if (!editable)
{
ed = (Edit)controls[col];
controls[col] = null;
}
else
{
controls[col] = new Edit();
ed = (Edit)controls[col];
ed.hasBorder = false;
ed.autoSelect = true;
add(ed);
ed.setVisible(false);
tabOrder.removeAllElements(); // guich@580_55: don't let the focus go to the edit.
}
return ed;
}
/** Returns true if the given column is editable.
* @since TotalCross 1.39
*/
public boolean isColumnEditable(int col)
{
return controls[col] != null;
}
/** Makes the given column ComboBox-like. The given choice array is used to create a PopList
* that is shown when the user clicks on the column. Note that calling this method removes any
* Edit assigned to this column.
* You can change dynamically the choices by extending the CellController class.
* @param col The column to set as a ComboBox column
* @param choices The choices that will be displayed. Passing a null value removes any ComboBox
* assigned to the column.
* @see #setCellController
* @returns The created ComboBoxDropDown (so you can customize) or null if choices is null
* @since SuperWaba 5.7
*/
public ComboBoxDropDown setColumnChoices(int col, String[] choices) // guich@570_81
{
if (col < 0 || col >= captions.length)
throw new IllegalArgumentException("col");
if (checkEnabled)
col++;
if (choices == null)
controls[col] = null;
else
controls[col] = new ComboBoxDropDown(new ListBox(choices));
return (ComboBoxDropDown)controls[col];
}
/** Returns the bounds of the given col/row. */
private void getColRect(Rect temp, int col, int row, boolean absolute)
{
int x = xOffset;
int n = captions.length;
for (int i =0; i < n; i++)
{
int w = widths[i];
if (i == col)
{
int xx = x+1;
int ww = w-1;
if (ww > width)
ww = width-1;
if (xx < 0) // before start
{
xOffset -= xx-1;
enableButtons();
Window.needsPaint = true;
xx = 1;
}
int k = width-(xx+ww); // across width?
if (k < 0)
{
xOffset += k;
enableButtons();
Window.needsPaint = true;
xx += k;
}
temp.set(xx,(row+1)*lineH+(absolute ? 0 : (lineH-fmH)/2),ww,absolute ? lineH : fmH);
break;
}
x += w;
}
if (absolute)
{
Control c = this;
do
{
temp.x += c.x;
temp.y += c.y;
c = c.parent;
} while (c != null);
}
}
protected void onFontChanged()
{
lineH = Settings.fingerTouch ? fmH*3/2 : fmH;
int k = lineH;
defaultCheckWidth = 25*k/22;
// compute check and box rects/deltas
rBox = uiAndroid ? new Rect(1,1,k-2,k-2) : new Rect(0,0, k/2, k/2);
rCheck = new Rect(1, -2, 0, 1);
rBox.x = (defaultCheckWidth - rBox.width)/2;
rBox.y = (lineH - rBox.height)/2;
// small adjustments
if (k == 11) // 160
{
rCheck.height++;
rBox.height++;
}
if (recomputeDefaultCaptionWidths)
widths = computeDefaultCaptionWidhts();
boolean difFonts = bag.font != this.font;
bag.setFont(this.font);
tip.setFont(this.font);
if (checkEnabled) // guich@tc114_58
{
widths[0] = defaultCheckWidth;
if (difFonts) // called setFont with a new font? update the values
originalWidths[0] = defaultCheckWidth;
}
}
/**
* Sets the grid items to be displayed. Note that it needs to be conforming to the
* numbers of columns that the grid currently have. This method removes any assigned datasource.
* The strings inside the matrix cannot be null,
* If items is null, the grid will remain empty.
*/
public void setItems(String[][] items)
{
ds = null;
lastStartingRow = lastSortCol = -1;
if (items == null)
removeAllElements(); // guich@tc115_58
else
{
vItems = new Vector(items);
itemsCount = items.length;
checkedCount = 0;
if (checkEnabled)
{
ivChecks = new IntVector(itemsCount);
ivChecks.setSize(itemsCount);
}
sbVert.setMaximum(itemsCount);
sbVert.setLiveScrolling(true);
if (gridOffset > itemsCount-linesPerPage) // guich@572_18
sbVert.setValue(gridOffset = Math.max(0, itemsCount-linesPerPage));
}
Window.needsPaint = true;
}
/** Sets the data source of this grid to be the given one. Note that when using
* data sources, the add, remove, insert, etc methods CANNOT BE USED.
* A data source is mostly used to assign a ResultSet to it, so its nonsense any data modification.
* Note that the scroll is made <b>not</b> <i>live scrolling</i> to speedup data retrieval.
* @since SuperWaba 5.7
*/
public void setDataSource(DataSource ds, int nrItems)
{
if (ds != null)
{
this.ds = ds;
lastStartingRow = -1;
vItems = null;
if (checkEnabled)
ivChecks = new IntVector(nrItems);
itemsCount = nrItems;
checkedCount = 0;
sbVert.setMaximum(itemsCount);
sbVert.setLiveScrolling(liveScrolling);
if (gridOffset > itemsCount-linesPerPage) // guich@572_18
sbVert.setValue(gridOffset = Math.max(0, itemsCount-linesPerPage));
Window.needsPaint = true;
}
}
/**
* Add a new line to the end of the grid. Its up to the user to call Window.needsPaint = true
* afterwards. This method does not work if there's a datasource assigned.
* @param item string containing the information of the row
*/
public void add(String[] item)
{
add(item, -1);
}
/**
* Add a new line at the given index position of the grid.
* Its up to the user to call Window.needsPaint = true afterwards.
* This method does not work if there's a datasource assigned.
* @param item string containing the information of the row
* @param row index position to insert the row in
*/
public void add(String [] item, int row)
{
if (vItems != null)
{
if (checkEnabled)
ivChecks.insertElementAt(0, row);
vItems.insertElementAt( item, row);
itemsCount++;
sbVert.setMaximum(itemsCount);
Window.needsPaint = true;
}
}
/**
* Appends the give lines at the end of the grid.<br>
* Its up to the user to call Window.needsPaint = true afterwards. This method does not work if there's a datasource assigned.
*
* @param items
* the lines to be appended to the grid.
*
* @since TotalCross 1.2
*/
public void add(String[][] items) //flsobral@tc120_35: added convenience method.
{
if (items != null && vItems != null)
{
if (checkEnabled)
ivChecks.addElements(new int[items.length]);
vItems.addElements(items);
itemsCount += items.length;
sbVert.setMaximum(itemsCount);
}
}
/**
* Replace a given line by the specified by its index with the
* supplied one. This method does not work if there's a datasource assigned.
* @param item String containing the information of the new line
* @param row Index position to insert the row in
*/
public void replace( String []item, int row)
{
if (vItems != null && 0 <= row && row < itemsCount)
Vm.arrayCopy(item, 0, vItems.items[row], 0, item.length); // guich@557_6: just copy the new item over the current one
}
/** Move the items at given indexes.
* @since TotalCross 1.53
*/
public boolean move(int row, boolean up)
{
if (up && row > 0)
{
Object o = vItems.items[row-1];
vItems.items[row-1] = vItems.items[row];
vItems.items[row] = o;
if (checkEnabled)
{
int i = ivChecks.items[row-1];
ivChecks.items[row-1] = ivChecks.items[row];
ivChecks.items[row] = i;
}
return true;
}
else
if (!up && row < itemsCount-1)
{
Object o = vItems.items[row+1];
vItems.items[row+1] = vItems.items[row];
vItems.items[row] = o;
if (checkEnabled)
{
int i = ivChecks.items[row+1];
ivChecks.items[row+1] = ivChecks.items[row];
ivChecks.items[row] = i;
}
return true;
}
return false;
}
/**
* Remove the given line index from the grid.
* This method does not work if there's a datasource assigned.
*/
public boolean del(int row)
{
if (row < 0 || row >= itemsCount || vItems == null) return false;
vItems.removeElementAt(row);
if (checkEnabled)
{
setChecked(row, false); // guich@tc100b5_34
ivChecks.removeElementAt(row);
}
sbVert.setMaximum(--itemsCount); // guich@tc110_64: decrease itemsCount
sbVert.setValue(0);
gridOffset = 0;
if (itemsCount == 0 && selectedLine >= 0) // if there are no more items but one remains selected
selectedLine = -1;
else
if (itemsCount == row && row == selectedLine) // was the last item the selected one?
selectedLine--;
Window.needsPaint = true;
return true;
}
/** Returns the text's column of the given row number.
* If the grid has a check column, col must start from 1. */
public String getCellText(int row, int col) // guich@554_27
{
try
{
return getItem(row)[col-(checkEnabled?1:0)];
}
catch (Exception e)
{
return null;
}
}
/** Get the information on the currently selected line.
* Any changes made into the returned array is applied
* back to the grid automatically (because it holds the grid's data). */
public String[] getSelectedItem()
{
if( selectedLine == -1 )
return null;
return getItem(selectedLine);
}
/** Get the index of the currently selected row, or -1 if none is selected. */
public int getSelectedIndex()
{
return selectedLine;
}
/** Checks or unchecks the given index based on the second argument.
* This method only works with a grid that has checks and repaint must be called.
*/
public void setChecked(int row, boolean check)
{
int v = check ? 1 : 0;
if (checkEnabled && ivChecks.items[row] != v) // guich@tc100b5_34
{
ivChecks.items[row] = v;
checkedCount += check ? 1 : -1;
allChecked = (checkedCount == itemsCount);
}
}
/** Check if a given row is checked */
public boolean isChecked(int row)
{
return checkEnabled && row >= 0 && ivChecks.items[row] == 1; // guich@556_6: added lineIndex check
}
/** Return a vector containing all information inside the grid. Note that if a datasource is assigned,
* this method returns null. */
public Vector getItemsVector()
{
return vItems;
}
/** Returns the line corresponding to the given index. This method works with or without a datasource. */
public String[] getItem(int row)
{
if (row < 0 || row >= itemsCount)
return null;
return vItems != null ? (String[])vItems.items[row] : getDataSourceItems(row,1)[0];
}
/** Causes a refresh to be made in the items of the datasource.
* @since TotalCross 1.01
*/
public void refreshDataSource()
{
lastStartingRow = -1;
Window.needsPaint = true;
}
/** Scrolls the grid to the given row. */
public void scrollTo(int row)
{
sbVert.setValue(row);
gridOffset = sbVert.getValue();
refreshDataSource();
}
private String[][] getDataSourceItems(int startingRow, int count) // guich@tc100b5_24
{
if (count == lastGetItemsCount && startingRow == lastStartingRow)
return lastGetItems;
lastStartingRow = startingRow;
lastGetItemsCount = count;
return lastGetItems = ds.getItems(startingRow, count);
}
private void paintStripes(Graphics g)
{
// sets the striped grid-style -- vlima
int w = width - 1;
int h = lineH;
int c = linesPerPage >> 1, y, dy;
// fill the first stripe as the whole grid
dy = lineH << 1;
boolean first = (Math.max(0, gridOffset) & 1) == 0;
g.backColor = first ? this.firstStripeColor : this.secondStripeColor;
g.fillRect(1, h, w, h * linesPerPage);
// now fill the second stripe
g.backColor = !first ? this.firstStripeColor : this.secondStripeColor;
for (y = dy; c > 0; y += dy, c--)
g.fillRect(1, y, w, h);
}
/** Draws the captions of the grid */
private void drawCaptions(Graphics g)
{
String[] data = captions;
int cols = captions.length;
int kx = xOffset;
/**
* create rect around the captions, fill it with an specified color, draws the
* vertical line all the way thru the component, and then draws the captions text
* inside of it -- vlima
*/
g.clearClip();
/**
* fill up the unused space in case the user drags the last column to the left
*/
g.backColor = uiAndroid ? parent.backColor : this.captionsBackColor;
g.fillRect(0, 0, width, lineH);
if (!uiAndroid)
g.drawRect(0, 0, width + 1, lineH + 1);
else
try
{
if (npcapt == null)
npcapt = NinePatch.getInstance().getNormalInstance(NinePatch.GRID_CAPTION,width,lineH+5,captionsBackColor,false);
// draw top
g.setClip(0,0,width,lineH);
NinePatch.tryDrawImage(g,npcapt,0,0);
g.clearClip();
}
catch (ImageException ie)
{
if (Settings.onJavaSE)
ie.printStackTrace();
}
g.setClip(2,0,width-4,height); // don't allow draw over the borders
g.backColor = this.captionsBackColor;
int lineY0 = uiAndroid ? 0 : lineH;
for (int i = 0; i < cols; i++)
{
int w = widths[i];
if (w > 0)
{
// draw the caption borders
if (!uiAndroid)
{
if (uiVista && isEnabled()) // guich@573_6
g.fillVistaRect(kx, 0, w + 1, lineH,captionsBackColor,true,false);
else
g.fillRect(kx, 0, w + 1, lineH);
if (uiFlat)
g.drawRect(kx, 0, w + 1, lineH + 1);
else
g.draw3dRect(kx, 0, w + 1, lineH, Graphics.R3D_RAISED, false, false, fourColors);
}
// draw the lines in the body of the Grid
if (i > 0)
{
switch (this.verticalLineStyle)
{
case VERT_DOT:
g.drawDots(kx, lineY0, kx, height - 2);
break;
case VERT_NONE:
// doesn't draw the vertical line. Note that the columns are still resizable
break;
default:
case VERT_LINE:
g.drawLine(kx, lineY0, kx, height - 2);
break;
}
}
else
if (this.checkEnabled && canClickSelectAll) // guich@572_10: only draw the box if can click select all
drawCheck(g, 0, this.allChecked);
if (i > 0 || !this.checkEnabled)
{
int yy = (lineH-fmH)/2;
int capw = captionWidths[i];
boolean greater = capw > w;
if (greater) g.setClip(kx+2,yy,w-4,fmH);
g.drawText(data[i], kx + 2 + (greater ? 0 : (w - capw) / 2), yy, textShadowColor != -1, textShadowColor);
if (greater) g.setClip(2,0,width-4,height); // don't allow draw over the borders
}
kx += w;
}
}
g.clearClip();
}
private Image npCheckBack,npCheck;
private void drawCheck(Graphics g, int y, boolean checked)
{
boolean uiAndroid = Control.uiAndroid;
if (uiAndroid)
try
{
if (drawCheckBox)
{
if (npCheckBack == null)
npCheckBack = Resources.checkBkg.getNormalInstance(rBox.height,rBox.height,foreColor);
g.drawImage(npCheckBack,xOffset + rBox.x, y + rBox.y);
}
if (checked)
{
int hh = drawCheckBox ? rBox.height : lineH;
if (npCheck == null)
npCheck = Resources.checkSel.getPressedInstance(hh,hh,backColor,checkColor != -1 ? checkColor : foreColor,isEnabled());
g.drawImage(npCheck, xOffset + (drawCheckBox ? rBox.x : 1), y + (drawCheckBox ? rBox.y : 0));
}
}
catch (Exception e)
{
if (Settings.onJavaSE)
e.printStackTrace();
uiAndroid = false;
}
if (!uiAndroid) // no else here!
{
if (drawCheckBox)
{
Rect r = this.rBox;
g.drawRect(xOffset + r.x, y + r.y, r.height, r.height);
}
if (checked)
{
Rect r = this.rCheck;
int h = lineH + r.height;
int xx = xOffset + r.x;
if (boldCheck && !drawCheckBox)
xx-=2;
int tmp = g.foreColor;
g.foreColor = this.checkColor;
g.translate(xx, y + r.y);
Check.paintCheck(g, h, h);
if (boldCheck)
{
g.translate(0,drawCheckBox?-2:2);
Check.paintCheck(g, h, h);
g.translate(0,drawCheckBox?2:-2);
}
g.translate(-xx, -(y + r.y));
g.foreColor = tmp;
}
}
}
protected void onColorsChanged(boolean colorsChanged)
{
npCheck = npCheckBack = null;
if (!uiAndroid) Graphics.compute3dColors(isEnabled(), backColor, foreColor, fourColors);
if (colorsChanged) // guich@tc100
{
if (sbVert != null) sbVert .setBackForeColors(backColor, foreColor);
if (btnLeft != null) btnLeft .setBackForeColors(backColor, foreColor);
if (btnRight != null) btnRight.setBackForeColors(backColor, foreColor);
if (sbHoriz != null) sbHoriz .setBackForeColors(backColor, foreColor);
}
}
private Image npback,npcapt;
private void paint(Graphics g)
{
int bc = getBackColor();
int fc = getForeColor();
g.clearClip();
g.backColor = bc;
if (!transparentBackground) // guich@tc115_18
{
if (isStriped())
paintStripes(g);
else
if (!uiAndroid)
g.fillRect(0,0,width,height); // guich@566_9: erase the background if drawStripes is false
if (uiAndroid)
try
{
if (npback == null)
npback = NinePatch.getInstance().getNormalInstance(NinePatch.GRID,width,height,captionsBackColor,false);
NinePatch.tryDrawImage(g,npback,0,0);
}
catch (ImageException ie)
{
if (Settings.onJavaSE)
ie.printStackTrace();
}
}
g.foreColor = fc;
drawCaptions(g);
g.backColor = bc;
int ty = lineH;
if (itemsCount > 0)
{
// per column drawing: faster
int cols = captions.length;
int base = checkEnabled ? 1 : 0;
int cx = xOffset + 2;
int xx = cx<0?0:cx; // guich@555_13
int maxX = bag.width;
boolean checkDrawn = !checkEnabled;
int i0 = Math.max(0, gridOffset);
int rows = Math.min(i0 + linesPerPage, itemsCount);
Object []items;
if (vItems != null)
items = vItems.items;
else // using a datasource
{
rows -= i0; // don't move from here!
String[][] temp = getDataSourceItems(i0,rows);
int count = temp.length;
items = new Object[count];
Vm.arrayCopy(temp, 0, items, 0, count);
i0 = 0;
}
// draw each column at a time
CellController cc = this.cc; // local copy
Font f;
int cf,cb=0,cfo = fc;
int[] currencyDecimalPlaces = this.currencyDecimalPlaces;
int row0 = ds != null ? lastStartingRow : 0; // guich@tc114_55: consider the DataSource's starting row
for (int j = 0; j < cols; j++)
{
int w = widths[j];
ty = lineH;
if (w > 0 && cx+w > 0 && cx <= maxX) // ignore columns not being shown
{
int cw = Math.min(w-1, width - cx+1);
g.setClip(cx-1, uiAndroid ? ty : ty+1, cw, lineH * linesPerPage); // guich@580_32: fixed clip rect based on GridTest.CellControl tab
if (checkDrawn)
{
int align = aligns[j];
for (int i = i0; i < rows; i++, ty += lineH)
{
int currentRow = i+row0;
String []line = (String[])items[i]; // don't put i+row0!
String columnText = line[j-base];
Image columnImg = htImages != null ? (Image)htImages.get(columnText) : null;
if (columnText == null) // prevent NPE
columnText = "";
if (currencyDecimalPlaces != null && currencyDecimalPlaces[j] >= 0)
columnText = Convert.toCurrencyString(columnText,currencyDecimalPlaces[j]);
if (cc == null || (f = cc.getFont(currentRow,j)) == null) // guich@tc100: allow font change
f = this.font;
int tx;
if (columnImg != null)
{
tx = cx + (align == LEFT ? 0
: align == CENTER ? (w - columnImg.getWidth()) / 2
: w - 3 - columnImg.getHeight()); // RIGHT
}
else
{
g.setFont(f);
tx = cx + (align == LEFT ? 0
: align == CENTER ? (w - f.fm.stringWidth(columnText)) / 2
: w - 3 - f.fm.stringWidth(columnText)); // RIGHT
}
cf = -1;
boolean isSelectedLine = drawHighlight && currentRow == selectedLine;
if (cc != null || isSelectedLine) // guich@580_31
{
cb = -1;
if (isSelectedLine | (cc != null && (cb = cc.getBackColor(currentRow,j)) != -1)) // have to compute both arguments
{
if (!isSelectedLine)
g.backColor = cb;
else
if (cc != null && cb != -1)
g.backColor = Color.interpolate(highlightColor,cb); // guich@tc126_20;
else
g.backColor = highlightColor;
g.fillRect(cx-1+borderGap,ty+borderGap,w-1-borderGap-borderGap,lineH-borderGap-borderGap);
}
if (cc != null && (cf = cc.getForeColor(currentRow,j)) != -1)
g.foreColor = isEnabled() ? cf : Color.interpolate(cf, g.backColor); // guich@tc139: shade color if not enabled
}
if (columnImg != null)
g.drawImage(columnImg,tx, ty+(lineH-fmH)/2);
else
g.drawText(columnText, tx, ty+(lineH-fmH)/2, textShadowColor != -1, textShadowColor);
if (cf != -1) // restore original fore color if it has changed
g.foreColor = cfo;
}
}
else
{
for (int i = i0; i < rows; i++, ty += lineH)
{
int currentRow = i+row0;
if (currentRow == selectedLine | (cc != null && (cb = cc.getBackColor(currentRow,0)) != -1))
{
if (currentRow != selectedLine)
g.backColor = cb;
else
if (cc != null && cb != -1)
g.backColor = Color.interpolate(highlightColor,cb); // guich@tc126_20;
else
g.backColor = highlightColor;
g.fillRect(xx-1,ty,w-1,lineH);
}
if (checkEnabled) // guich@tc110_60
drawCheck(g, ty, ivChecks.items[currentRow] == 1);
}
}
}
checkDrawn = true;
cx += w;
}
}
g.clearClip();
if (!uiAndroid)
g.drawRect(0, lineH, width + 1, height - lineH); // guich@555_8: removed +1 bc on 3d it overrides scrollbar box - guich@tc115_2: moved to here, after the items were drawn
else
{
// draw bottom
g.expandClipLimits(0,0,0,5);
g.setClip(0,height-5,width,5);
g.drawImage(npcapt,0,height-lineH-5);
g.expandClipLimits(0,0,0,-5);
g.clearClip();
}
}
/**
* We need to add the check column information to widths, aligns, and captions supplied
* by the user
*/
private void addCheckColumn()
{
int n = this.widths.length;
int[] oldWidths = widths;
int[] oldOriginalWidths = originalWidths;
int[] oldAligns = aligns;
String[] oldCaptions = captions;
widths = new int[n+1];
originalWidths = new int[n+1];
aligns = new int[n+1];
captions = new String[n+1];
for (int i = 0; i < n; i++)
{
widths[i+1] = oldWidths[i];
originalWidths[i+1] = oldOriginalWidths[i];
aligns[i+1] = oldAligns[i];
captions[i+1] = oldCaptions[i];
}
widths[0] = originalWidths[0] = defaultCheckWidth;
aligns[0] = LEFT;
captions[0] = " ";
}
private void setWidths(int[] newWidths)
{
int n = newWidths.length, i;
this.widths = new int[n]; // important
Vm.arrayCopy(newWidths, 0, this.widths, 0, n);
if (captionWidths == null) captionWidths = new int[n];
if (ihtLinePoints == null)
ihtLinePoints = new IntHashtable(n);
else
ihtLinePoints.clear();
int totalW = 0;
n--;
int lastVisibleCol = n; // guich@557_12: find out the last visible col
for (i = n; i >= 0; i--)
if (newWidths[i] != 0)
{
lastVisibleCol = i;
break;
}
int availW = bag.width;
int percW = availW;
if (checkEnabled) percW -= defaultCheckWidth;
for (i = 0; i <= n; i++)
{
if (newWidths[i] > 10000)
throw new RuntimeException("FILL is no longer supported as a column width! Width of column "+i+" set to "+newWidths[i]);
if (newWidths[i] == 0) // column not being shown?
continue;
if (newWidths[i] < 0) // percent?
widths[i] = percW * -widths[i] / 100;
int cw = captionWidths[i] = fm.stringWidth(captions[i]) + 3;
if (widths[i] <= cw && !titleMayBeClipped) widths[i] = cw;
totalW += widths[i];
if (i == lastVisibleCol)
{
if (totalW < availW) // make sure that the last col will have the grid's width if needed
{
widths[i] += availW - totalW;
totalW = availW;
}
maxOffset = availW - totalW;
if (maxOffset > 0) // guich@557_13: is grid now smaller than the max?
{
widths[i] += maxOffset;
maxOffset = 0;
}
}
// used in the resize column routine
if (enableColumnResize && (!checkEnabled || i > 0))
{
int l = (totalW << 16) | i; // store the real position with the line index
for (int step = (columnResizeMargin-1) / 2, z = -step; z <= step; z++) // guich@tc122_27
ihtLinePoints.put(totalW + z, l);
}
}
// check if we need to enable the horizontal scroll buttons
enableButtons();
}
private void enableButtons()
{
if (xOffset < maxOffset) // shift to the left if there's no more need to shift and it is shifted
xOffset = maxOffset;
if (sbHoriz != null)
{
sbHoriz.setVisibleItems(bag.width);
sbHoriz.setMaximum(bag.width-maxOffset);
sbHoriz.setValue(-xOffset);
}
else
{
btnLeft.setEnabled(isEnabled() && xOffset < 0);
btnRight.setEnabled(isEnabled() && xOffset > maxOffset);
}
}
public void initUI()
{
sbVert.setBackForeColors(backColor, foreColor);
if (Settings.keyboardFocusTraversable || lineScroll || Settings.fingerTouch)
{
sbVert.setFocusLess(true); // guich@570_39
if (sbHoriz != null) sbHoriz.setFocusLess(true);
}
int by = 0;
int extraHB = 0;
boolean b = uiAdjustmentsBasedOnFontHeightIsSupported; uiAdjustmentsBasedOnFontHeightIsSupported = false;
if (Settings.fingerTouch || uiAndroid) // must be added before the ScrollPositions, otherwise the bars will not be drawn correctly
if (uiAndroid)
add(bag, 0,0,FILL - (Settings.fingerTouch ? 0 : sbVert.getPreferredWidth()), FILL-4); // guich@554_31: +1
else
add(bag, 0,0,FILL, FILL); // guich@554_31: +1
if (sbHoriz != null)
sbHoriz.setBackForeColors(backColor, foreColor);
else
{
int hh = 3 * lineH / 11;
add(btnRight = new ArrowButton(Graphics.ARROW_RIGHT, hh, foreColor));
add(btnLeft = new ArrowButton(Graphics.ARROW_LEFT, hh, foreColor));
btnRight.setBackForeColors(backColor, foreColor);
btnLeft.setBackForeColors(backColor, foreColor);
if ((this.height/btnRight.getPreferredHeight() > ListBox.EXTRA_HEIGHT_FACTOR)) // guich@tc100b5_21: same code from ListBox.onBoundsChanged
extraHB = Settings.screenHeight*4/160;
by = (btnRight.getPreferredHeight()+extraHorizScrollButtonHeight+extraHB) << 1; // get the height of the two buttons together
if (uiAndroid) by += 4;
}
// add the scrollbar next to the grid
add(sbVert,RIGHT, Settings.fingerTouch ? lineH : 0, PREFERRED, FILL - by);
sbVert.setValues(0, linesPerPage, 0, itemsCount); // guich@580_15: set the current itemsCount
sbVert.setLiveScrolling(true);
sbVert.setBackForeColors(backColor, foreColor);
if (sbHoriz != null)
add(sbHoriz, LEFT,BOTTOM-(uiAndroid?4:0),Settings.fingerTouch ? FILL : FIT+1,PREFERRED);
else
{
// add the two horizontal scroll buttons below the scrollbar
btnLeft.setRect(RIGHT, AFTER, SAME, PREFERRED+extraHorizScrollButtonHeight+extraHB);
btnRight.setRect(RIGHT, AFTER, SAME, PREFERRED+extraHorizScrollButtonHeight+extraHB);
}
if (!Settings.fingerTouch && !uiAndroid)
add(bag, 0,0,FILL - (Settings.fingerTouch ? 0 : sbVert.getWidth()), FILL - (!Settings.fingerTouch && sbHoriz != null ? sbHoriz.getPreferredHeight() : 0)); // guich@554_31: +1
uiAdjustmentsBasedOnFontHeightIsSupported = b;
tabOrder.removeAllElements(); // don't let get into us on focus traversal
onBoundsChanged(false);
setWidths(originalWidths);
setTooltipRect();
tip.borderColor = Color.BLACK;
}
private void setTooltipRect()
{
if (tip != null)
{
absRect = bag.getAbsoluteRect();
Window w = getParentWindow();
if (w != null)
{
absRect.x -= w.x;
absRect.y -= w.y;
}
tip.setRect(absRect);
}
}
/** If all widths (passed in constructor) are positive (ie, not a percentage), then it will use the sum as the preferred width. */
public int getPreferredWidth()
{
// guich@tc114_52: if all values are positive, use them to compute a preferred width
int sum = 0;
for (int i =0; i < widths.length; i++)
if (widths[i] >= 0)
sum += widths[i];
else
{
sum = 0;
break;
}
if (sum > 0) // ok?
sum += (Settings.fingerTouch ? 0 : new ScrollBar().getPreferredWidth()) + (checkEnabled ? defaultCheckWidth : 0);
else
sum = Settings.screenWidth>>1; // else, use the default, which is screen width / 2
return sum + insets.left+insets.right;
}
public int getPreferredHeight()
{
return (useHorizontalScrollBar ? sbHoriz.getPreferredHeight() : 0) + (visibleLines > 0 ? (visibleLines + 1) * lineH : Settings.screenHeight>>1) + insets.top+insets.bottom + (uiAndroid ? 4 : 0); // guich@tc126_
}
protected void onBoundsChanged(boolean screenChanged)
{
int sbh = (!Settings.fingerTouch && sbHoriz != null) ? sbHoriz.getPreferredHeight() : 0;
int lh = height - lineH + 1 - sbh; // height of the vertical grid line (captions excluded)
if (uiAndroid) lh -= 4;
linesPerPage = lh / lineH;
if (sbVert != null)
sbVert.setVisibleItems(linesPerPage); // guich@556_8: fixed problem when linesPerPage changes
// changes the height to fit in linesPerPage exactly
height = (height-sbh) / lineH * lineH + sbh;
if (uiAndroid) height += 4;
if (asContainer.finishedStart) // luciana@570_18: fixed problem when setRect is called more than once
{
sbVert.reposition();
if (sbHoriz != null)
sbHoriz.reposition();
else
{
btnLeft.reposition();
btnRight.reposition();
}
bag.reposition();
setWidths(originalWidths);
}
setTooltipRect();
}
/** Remove all elements from the grid, leaving it blank. Removes any assigned datasource. */
public void removeAllElements()
{
vItems = new Vector(50);
if (checkEnabled) ivChecks.removeAllElements();
itemsCount = 0;
ds = null;
lastStartingRow = -1;
allChecked = false;
gridOffset = itemsCount = 0;
selectedLine = -1; // guich@580_4
sbVert.setMaximum(0);
xOffset = 0; // guich@tc112_12
enableButtons(); // guich@tc112_12
Window.needsPaint = true;
}
/** Selects the clearValueInt row, which defaults to -1. */
public void clear()
{
setSelectedIndex(clearValueInt);
}
/** Mark/unmark all lines */
private void invertAllMarks()
{
markAll(!allChecked);
postGridEvent(0, allChecked ? ALL_CHECKED : ALL_UNCHECKED, false); // guich@580_16
Window.needsPaint = true;
}
/** Checks or unchecks all rows in this grid.
* @since TotalCross 1.2
*/
public void markAll(boolean check)
{
if (checkEnabled)
{
this.allChecked = check;
int value = allChecked ? 1:0;
int[] items = ivChecks.items;
int row0 = ds != null ? lastStartingRow : 0; // guich@tc114_55: consider the DataSource's starting row
for (int i = itemsCount-1; i >= 0; i--)
if (cc == null || cc.isEnabled(i+row0,0)) // guich@580_31
items[i] = value;
checkedCount = check ? itemsCount : 0; // guich@tc123_30
}
}
/** Scrolls the grid horizontaly as needed */
public boolean horizontalScroll(boolean toLeft)
{
int step = bag.width >> 1;
int newOffset = toLeft ? Math.min(xOffset + step, 0) : Math.max(xOffset - step, maxOffset);
if (newOffset != xOffset)
{
xOffset = newOffset;
enableButtons();
Window.needsPaint = true;
return true;
}
return false;
}
private void hideControl()
{
Control c = lastShownControl;
if (c == null) // guich@573_13
return;
lastShownControl = null;
int col = (c.appId >> 24 & 127);
int row = c.appId & 0xFFFFFF;
oldCellText = getCellText(row,col);
if (c instanceof Edit)
{
Edit ed = (Edit)c;
Window.needsPaint = true;
ed.setVisible(false);
String s = ed.getText();
if (!oldCellText.equals(s))
{
setCellText(row, col, s);
postGridEvent(col,selectedLine,true);
}
}
else
{
ListBox lb = ((ComboBoxDropDown)c).lb;
String sel;
if (lb.getSelectedIndex() >= 0 && !oldCellText.equals(sel = (String)lb.getSelectedItem()))
{
Window.needsPaint = true;
setCellText(row, col, sel);
postGridEvent(col,selectedLine,true);
}
}
}
/** Sets the text of a column. If the grid has a check column, col must start from 1.
* @since SuperWaba 5.8
*/
public void setCellText(int row, int col, String text)
{
try
{
String[] item = getItem(row);
if (checkEnabled)
col--;
String old = item[col];
item[col] = text;
if (!text.equals(old) && Settings.sendPressEventOnChange)
postGridEvent(col,row,true);
}
catch (Exception aioobe)
{
if (Settings.onJavaSE)
Vm.warning("Wrong index! "+row+"x"+col+" - "+aioobe);
}
}
private boolean isStriped()
{
return firstStripeColor != secondStripeColor || firstStripeColor != backColor;
}
private void showControl(int row, int col)
{
if (hadParentScrolled())
return;
int row0 = ds != null ? lastStartingRow : 0; // guich@tc114_55: consider the DataSource's starting row
if (cc != null && !cc.isEnabled(row+row0, col)) // guich@580_31
return;
Control c = controls[col];
lastShownControl = c;
getColRect(rTemp,col,row,c instanceof ComboBoxDropDown);
c.setRect(rTemp);
c.appId = (col<<24) | selectedLine; // guich@557_11: replaced line by selectedLine
if (c instanceof Edit)
{
Edit ed = (Edit)c;
if (cc != null)
{
Font f = cc.getFont(row+row0, col);
ed.setFont(f != null ? f : this.font);
}
ed.setBackColor( Color.darker((isStriped() ? ((selectedLine&1)==0 ? firstStripeColor : secondStripeColor) : backColor)));
ed.setText(getCellText(selectedLine, col));
ed.setVisible(true);
ed.requestFocus();
ed.bringToFront();
if (Settings.virtualKeyboard)
ed.popupKCC();
}
else
{
ComboBoxDropDown pl = (ComboBoxDropDown)c;
Object [] oldItems=null,newItems;
if (cc != null)
{
Font f = cc.getFont(row+row0, col);
pl.setFont(f != null ? f : this.font);
if ((newItems=cc.getChoices(row+row0, col)) != null) // guich@580_31
{
oldItems = pl.lb.getItems();
pl.lb.itemCount = 0; // can't call removeAll bc the string array will have all elements set to null
pl.lb.add(newItems);
pl.setRect(rTemp); // must compute the rect again
}
}
pl.lb.setSelectedIndex(-1);
pl.popup();
hideControl();
if (oldItems != null) // guich@580_31
{
pl.lb.itemCount = 0;
pl.lb.add(oldItems);
pl.setRect(rTemp);
}
}
}
private void onTip() // is the tooltip being shown? set the text to the cell one if needed
{
// uses the last event to find the clicked col
int col = ge.col;
if (!isDisplayed() || (lastPE != null && lastPE.y <= lineH) || (ge != null && ge.row >= itemsCount) || col < 0 || col >= widths.length || selectedLine < 0 || controls[col] != null) // guich@557_5: check all bounds - guich@563_6: added isDisplayed - guich@570_92: don't show tip when there's a control assigned - guich@tc115_67: ge.row, not col must be > itemsCount
{
tip.setText("");
if (resizingLine >= 0) // guich@tc100b4_24: don't let the window be updated if we're resizing a column
Window.needsPaint = false;
}
else
{
String s = getCellText(ge.row, ge.col);
if (s == null || s.length() == 0) return;
int fmw = s != null ? fm.stringWidth(s) : 0; // guich@tc100b5_43
int x1 = Convert.sum(widths, 0, ge.col) + xOffset; // guich@tc122_2: if this is the last column and it is not completely visible, show the tooltip
int x2 = x1 + widths[ge.col];
int cellw = x2 > bag.width ? bag.width - x1 : widths[col];
if (fmw <= cellw)
tip.setText("");
else
{
tip.setEnabled(true);
rTemp.set(absRect.x+2, absRect.y+(ge.row-gridOffset+1)*lineH, absRect.width-10,lineH); // guich@557_10: sub gridOffset in case the grid was scrolled.
tip.setText(Convert.insertLineBreak(rTemp.width, fm, s));
tip.setControlRect(rTemp);
}
}
}
private int pendownLine;
public void onEvent(Event e)
{
if (e.target == bag) // guich@tc100: redirect events from our bag to ourselves
e.target = this;
if (isEnabled())
switch (e.type)
{
case ControlEvent.FOCUS_IN:
if (e.target instanceof Edit && itemsCount > 0 && lastShownControl == null) // guich@573_14: when the Calendar closes, a requestFocus is called for the control, so we handle it
{
lastShownControl = (Edit)e.target;
this.requestFocus(); // will issue a FOCUS_OUT event
}
break;
case ControlEvent.FOCUS_OUT:
if (e.target instanceof Edit && itemsCount > 0) // hide the edit
hideControl(/*(Edit)e.target*/);
break;
case ControlEvent.PRESSED:
if (e.target instanceof ComboBoxDropDown && Settings.keyboardFocusTraversable) // guich@582_15: keep highlighting off
isHighlighting = false;
else
if (e.target == sbVert)
{
int newOffset = sbVert.getValue();
if (gridOffset != newOffset) // guich@555_8: this will be called twice (when button is pressed and when released) due to liveScrolling
{
gridOffset = newOffset;
Window.needsPaint = true;
}
}
else
if (e.target == btnLeft || e.target == btnRight)
horizontalScroll(e.target == btnLeft);
else
if (sbHoriz != null && e.target == sbHoriz)
{
xOffset = -sbHoriz.getValue();
Window.needsPaint = true;
}
break;
case PenEvent.PEN_DOWN:
scScrolled = false;
if (e.target == this)
{
vbarY0 = sbVert != null ? sbVert.getValue() : 0;
hbarX0 = sbHoriz != null ? sbHoriz.getValue() : 0;
hbarDX = vbarDY = 0;
PenEvent pe = lastPE = (PenEvent)e;
int px = pe.x - xOffset;
int py = pe.y;
pendownLine = getLine(py);
if (py > height) // guich@580_48: don't allow events if it occurs below the grid's height
break;
if (lastShownControl != null) // guich@560_25
hideControl();
/**
* check if we clicked on the check column caption If so, behave
* accordingly. One click mark all, one click after all marked dismark all.
*/
if (checkEnabled && canClickSelectAll && py <= lineH && px <= widths[0])
{
invertAllMarks();
break; // guich@554_27: nothing more to do.
}
int line = ihtLinePoints.get(px, -1);
if (line >= 0) // clicked in a row? resize it
{
resizingLine = line & 0xFFFF;
resizingRealX = line >> 16;
resizingDx = px - resizingRealX;
resizingOrigWidth = widths[resizingLine];
e.consumed = true; // don't let the tooltip timer be started
}
else
if (py < lineH && itemsCount > 1) // guich@555b: quicksort the column
{
int col = getColFromX(px,true);
if (!disableSort && col >= (checkEnabled ? 1: 0))
{
selectedLine = -1;
qsort(col);
Window.needsPaint = true;
}
}
else
if (!Settings.fingerTouch && py > lineH) // in data - fingertouch devices must handle only on pen up
clickedOnData(pe.x,px,py);
}
// else ignoreNextEvent = true; // when the user press a button, a repaint is
// propagated to the parent (we), resulting on an undesirable flicker
break;
case PenEvent.PEN_DRAG_END:
if (flick != null && Flick.currentFlick == null && itemsCount > linesPerPage)
e.consumed = true;
break;
case PenEvent.PEN_DRAG:
{
int rl = resizingLine;
if (e.target == this && rl != -1)
{
Event.clearQueue(PenEvent.PEN_DRAG);
int px = ((PenEvent) e).x;
int dx = px - resizingDx - resizingRealX - xOffset;
widths[rl] = resizingOrigWidth + dx; // guich@tc110_47: update in realtime
setWidths(widths);
Window.needsPaint = true;
e.consumed = true;
}
else
if (Settings.fingerTouch)
{
DragEvent de = (DragEvent)e;
int dx = -de.xDelta;
int dy = -de.yDelta;
if (isScrolling)
{
scrollContent(dx, dy, true);
e.consumed = true;
}
else
{
int direction = DragEvent.getInverseDirection(de.direction);
e.consumed = true;
if (canScrollContent(direction, de.target) && scrollContent(dx, dy, true))
isScrolling = scScrolled = true;
}
}
break;
}
case PenEvent.PEN_UP:
if (e.target == this)
{
PenEvent pe = (PenEvent)e;
if (Settings.fingerTouch && !isFlicking && !isScrolling && Flick.currentFlick == null)
{
if (pe.y > lineH && pendownLine == getLine(pe.y))
clickedOnData(pe.x,pe.x - xOffset,pe.y);
}
if (!isFlicking)
flickDirection = NONE;
isScrolling = false;
int rl = resizingLine;
if (rl != -1)
{
int px = pe.x;
int dx = px - resizingDx - resizingRealX - xOffset;
if (dx == 0 && rl == widths.length - 1) // the last row cannot have its size increased by the user, so we expand it
dx = resizingOrigWidth/3;
widths[rl] = resizingOrigWidth + dx;
setWidths(widths);
e.consumed = Window.needsPaint = true;
resizingLine = -1;
}
else
if (showPlOnNextPenUp != -1)
{
int col = (showPlOnNextPenUp >> 24 & 127);
int row = showPlOnNextPenUp & 0xFFFFFF;
showPlOnNextPenUp = -1;
showControl(row,col);
}
}
break;
case KeyEvent.KEY_PRESS:
{
KeyEvent ke = (KeyEvent) e;
if (isHighlighting || ke.target instanceof Edit) break; // let the Edit work - guich@582_16: check if isHighlighting, and, in this case, just exit.
int key = ke.key;
if (key == ' ' && checkEnabled && canClickSelectAll) // guich@570_113: make the space on devices with a keyboard (and without a keyboard)
invertAllMarks();
else
if (selectedLine >= 0 && '0' <= key && key <= '9') // guich@573_15: allow edit by using a shortcut from 0 to 9
{
int col = key - '0';
int row0 = ds != null ? lastStartingRow : 0;
if (cc == null || cc.isEnabled(selectedLine+row0,col))
{
if (checkEnabled && col == 0) // guich@580_31: verify if the user can change this check state
{
setChecked(selectedLine,!isChecked(selectedLine));
postGridEvent(checkEnabled ? 0 : 1,selectedLine,false);
getParentWindow().repaintNow(); // this is the only way it worked in the 6600. at no other ways the screen was updated after pressing 0.
}
else // else, post a selected event
if (col < widths.length)
{
if (controls[col] != null) // show the edit
showControl(selectedLine, col);
else
{
/* if (Settings.keyboardFocusTraversable) // guich@582_
{
Graphics g = getGraphics();
getColRect(rTemp, col, selectedLine, true); // guich@tc100: not sure if is "true" here
for (int i =0; i < 4; i++)
{
g.drawCursor(rTemp.x,rTemp.y,rTemp.width,rTemp.height);
Vm.sleep(50);
repaintNow();//Window.updateScreen();
}
}
*/ postGridEvent(col,selectedLine,false); // guich@580_51
}
}
else break; // prevent repaint
} else break;
}
Window.needsPaint = true;
break;
}
case KeyEvent.SPECIAL_KEY_PRESS:
{
KeyEvent ke = (KeyEvent) e;
int key = ke.key;
if (ke.target instanceof Edit)
{
if (ke.key == SpecialKeys.ESCAPE) // allow the grid to work with keys when an Edit looses focus with the ESC key
requestFocus();
break; // let the Edit work - guich@582_16: check if isHighlighting, and, in this case, just exit.
}
if ((Settings.keyboardFocusTraversable || lineScroll) && (ke.isPrevKey() || ke.isNextKey() || ke.isActionKey()))
{
if (ke.isUpKey()) // guich@550_15: added support for navigate using all arrows
{
if (itemsCount == 0) return;
int line = selectedLine;
if (--line < 0)
line = Settings.circularNavigation ? itemsCount-1 : 0; // guich@tc115_19
if (line != selectedLine) // guich@tc115_19: only if selection changed
setSelectedIndex(line);
//postGridEvent(1,selectedLine,false); guich@572_21: now the SELECTED event is dispatched only when a key is pressed.
}
else
if (ke.isDownKey())
{
if (itemsCount == 0) return;
int line = selectedLine;
if (++line >= itemsCount)
line = Settings.circularNavigation ? 0 : itemsCount-1; // guich@tc115_19
if (line != selectedLine) // guich@tc115_19: only if selection changed
setSelectedIndex(line);
//postGridEvent(1,selectedLine,false); guich@572_21: now the SELECTED event is dispatched only when a key is pressed.
}
else
if (key == SpecialKeys.ESCAPE || ke.isActionKey()) // guich@570_81: added ESCAPE - guich@573_27: added actionKey
setHighlighting();
else
if (key == SpecialKeys.LEFT || key == SpecialKeys.RIGHT)
horizontalScroll(key == SpecialKeys.LEFT);
}
else
if (!Settings.keyboardFocusTraversable && !lineScroll) // guich@555_12
{
if (ke.isUpKey() && itemsCount > linesPerPage) // guich@571_8: do only when needed
{
sbVert.setValue(Math.max(0,sbVert.getValue()-linesPerPage));
int v = sbVert.getValue();
if (v != gridOffset)
gridOffset = v;
else
return;
} else
if (ke.isDownKey() && itemsCount > linesPerPage) // guich@571_8: do only when needed
{
sbVert.setValue(sbVert.getValue()+linesPerPage);
int v = sbVert.getValue();
if (v != gridOffset)
gridOffset = v;
else
return;
} else
if (key == SpecialKeys.LEFT) // guich@510_6
horizontalScroll(true); else
if (key == SpecialKeys.RIGHT)
horizontalScroll(false);
}
/*
* guich@573_45
if (ke.isActionKey())
{
if (checkEnabled && selectedLine != -1) // guich@572_21: now the enter key always dispatch a SELECTED event, not only when checkEnabled. - guich@573_5: check if there's a line selected
setChecked(selectedLine, !isChecked(selectedLine));
postGridEvent(checkEnabled ? 0 : 1,selectedLine, false);
}*/
Window.needsPaint = true;
break;
}
}
}
private int getLine(int py)
{
return py / lineH - 1;
}
private void clickedOnData(int pex, int px, int py)
{
int line = py / lineH - 1;
// finds the clicked column
int col = getColFromX(px,false);
if (col < 0) return;
int newSel = line+gridOffset; // guich@tc114_55: consider the DataSource's starting row - guich@tc162: don't consider it
if (selectedLine != newSel && (enableSelectDisabledCell || cc == null || cc.isEnabled(newSel,0))) // only if changed
setSelectedIndex(newSel);
// handles the click on the check column
if (checkEnabled && pex <= widths[0])
{
if (0 <= newSel && newSel < itemsCount) // just in case you did not click on a line first - guich@580_31: verify if the user can change this check state
{
if (cc == null || cc.isEnabled(newSel,0)) // guich@tc120_54: don't let the check be down, but post the event
{
setChecked(newSel, !isChecked(newSel));
Window.needsPaint = true;
}
else
if (!enableSelectDisabledCell)
return;
postGridEvent(col,newSel,false);
}
}
else
{
if (controls[col] != null && selectedLine >= 0) // show the edit
{
if (controls[col] instanceof ComboBoxDropDown)
showPlOnNextPenUp = (col << 24) | line;
else
showControl(line, col);
}
postGridEvent(col,selectedLine,false);
}
}
private int getColFromX(int px, boolean doGap)
{
int n = widths.length;
int []w = widths;
int x1 = xOffset,x2 = w[0],i=0;
int maxX1 = width - xOffset;
while (true)
{
if (w[i] > 0)
{
int gap = doGap?3:0; // if doGap is true (for quicksort), we ignore clicks near the col divisions - guich@tc120_23: only 3 pixels is enough
if ((x1+gap) <= px && px <= (x2-gap))
return i;
}
if (++i == n)
break;
if (w[i] > 0)
{
x1 = x2;
x2 += w[i];
if (x1 > maxX1)
break;
}
}
return -1;
}
/** Performs a quicksort in the items of the given column in the given order. This method does not work if there's a datasource assigned.
* @since TotalCross 1.15
*/
public void qsort(int col, boolean ascending) // guich@tc115_28
{
this.ascending = ascending;
if (checkEnabled)
col--;
if (itemsCount > 1 && vItems != null)
{
int sortType,lin=0;
do
{
sortType = sortTypes[col] == Convert.SORT_AUTODETECT ? Convert.detectSortType(getItem(lin)[col]) : sortTypes[col]; // guich@565_1: support numeric sort
}
while (sortType == Convert.SORT_STRING && ++lin < itemsCount);
try
{
// autodetect the sort type
switch (sortType)
{
case Convert.SORT_INT: qsortInt (col, 0, itemsCount-1, ascending); break;
case Convert.SORT_DOUBLE: qsortDouble (col, 0, itemsCount-1, ascending); break;
case Convert.SORT_DATE: qsortDate (col, 0, itemsCount-1, ascending); break;
case Convert.SORT_STRING_NOCASE: qsortStringNocase(col, 0, itemsCount-1, ascending); break;
default: qsortString (col, 0, itemsCount-1, ascending); break;
}
}
catch (Exception e)
{
// try again, moving invalid values out of the sort
int lowestValid = 0, highestValid = itemsCount-1;
Date d = sortType == Convert.SORT_DATE ? new Date() : null;
for (int i = 0; i <= highestValid; i++)
{
try
{
switch (sortType)
{
case Convert.SORT_INT: Convert.toInt(((String[])vItems.items[i])[col]); break;
case Convert.SORT_DOUBLE: Convert.toDouble(((String[])vItems.items[i])[col]); break;
case Convert.SORT_DATE: d.set(((String[])vItems.items[i])[col], Settings.dateFormat); break;
default: return; // get out - dont know what kind of exceptions can be thrown with String
}
}
catch (Exception ee)
{
if (ascending)
swap(lowestValid++, i);
else
swap(highestValid--, i);
}
}
try
{
// sort as string the invalid part
if (ascending)
qsortStringNocase(col, 0, lowestValid-1, ascending);
else
qsortStringNocase(col, highestValid, itemsCount-1, ascending);
switch (sortType)
{
case Convert.SORT_INT: qsortInt (col, lowestValid, highestValid, ascending); break;
case Convert.SORT_DOUBLE: qsortDouble (col, lowestValid, highestValid, ascending); break;
case Convert.SORT_DATE: qsortDate (col, lowestValid, highestValid, ascending); break;
}
}
catch (Exception eee) {if (Settings.onJavaSE) e.printStackTrace();}
}
}
}
/** Performs a quicksort in the items of the given column. This method does not work if there's a datasource assigned. */
public void qsort(int col) // guich@563_7
{
if (checkEnabled)
col--;
if (sortTypes[col] == -2) // guich@tc100
return;
if (col == lastSortCol) // guich@tc100
ascending = !ascending;
else
{
ascending = true;
lastSortCol = col;
}
qsort(checkEnabled ? col+1 : col, ascending);
}
private void swap(int low, int high)
{
if (checkEnabled)
{
int t = ivChecks.items[low];
ivChecks.items[low] = ivChecks.items[high];
ivChecks.items[high] = t;
}
Object temp = vItems.items[low];
vItems.items[low] = vItems.items[high];
vItems.items[high] = temp;
}
private void qsortInt(int col, int first, int last, boolean ascending) throws InvalidNumberException // guich@220_34
{
if (first >= last)
return;
int low = first;
int high = last;
Object []items = vItems.items;
int mid = Convert.toInt(((String[])items[(first+last) >> 1])[col]);
while (true)
{
if (ascending)
{
while (high >= low && mid > Convert.toInt(((String[])items[low ])[col])) // guich@566_25: added "high > low" here and below - guich@568_5: changed to >=
low++;
while (high >= low && mid < Convert.toInt(((String[])items[high])[col]))
high--;
}
else
{
while (high >= low && mid < Convert.toInt(((String[])items[low ])[col])) // guich@566_25: added "high > low" here and below - guich@568_5: changed to >=
low++;
while (high >= low && mid > Convert.toInt(((String[])items[high])[col]))
high--;
}
if (low <= high)
swap(low++, high--);
else
break;
}
if (first < high)
qsortInt(col,first,high,ascending);
if (low < last)
qsortInt(col,low,last,ascending);
}
private double getDoubleValue(String s, double def)
{
return s.equals("") ? -1 : Convert.toDouble(s,def); // guich@tc210: support empty cells
}
private void qsortDouble(int col, int first, int last, boolean ascending) throws InvalidNumberException // guich@220_34
{
if (first >= last)
return;
int low = first;
int high = last;
Object []items = vItems.items;
double mid = getDoubleValue(((String[])items[(first+last) >> 1])[col], Convert.MIN_DOUBLE_VALUE);
while (true)
{
if (ascending)
{
while (high >= low && mid > getDoubleValue(((String[])items[low ])[col], Convert.MIN_DOUBLE_VALUE))
low++;
while (high >= low && mid < getDoubleValue(((String[])items[high])[col], Convert.MAX_DOUBLE_VALUE))
high--;
}
else
{
while (high >= low && mid < getDoubleValue(((String[])items[low ])[col], Convert.MIN_DOUBLE_VALUE))
low++;
while (high >= low && mid > getDoubleValue(((String[])items[high])[col], Convert.MAX_DOUBLE_VALUE))
high--;
}
if (low <= high)
swap(low++, high--);
else
break;
}
if (first < high)
qsortDouble(col,first,high,ascending);
if (low < last)
qsortDouble(col,low,last,ascending);
}
private void qsortDate(int col, int first, int last, boolean ascending) throws InvalidDateException // guich@220_34
{
if (first >= last)
return;
int low = first;
int high = last;
Object []items = vItems.items;
byte df = Settings.dateFormat;
Date d = new Date();
int mid = d.set(((String[])items[(first+last) >> 1])[col], df);
while (true)
{
if (ascending)
{
while (high >= low && mid > d.set(((String[])items[low ])[col], df))
low++;
while (high >= low && mid < d.set(((String[])items[high])[col], df))
high--;
}
else
{
while (high >= low && mid < d.set(((String[])items[low ])[col], df))
low++;
while (high >= low && mid > d.set(((String[])items[high])[col], df))
high--;
}
if (low <= high)
swap(low++, high--);
else
break;
}
if (first < high)
qsortDate(col,first,high,ascending);
if (low < last)
qsortDate(col,low,last,ascending);
}
private void qsortString(int col, int first, int last, boolean ascending) // guich@220_34
{
if (first >= last)
return;
int low = first;
int high = last;
Object []items = vItems.items;
String mid = ((String[])items[(first+last) >> 1])[col];
while (true)
{
if (ascending)
{
while (high >= low && mid.compareTo(((String[])items[low ])[col]) > 0)
low++;
while (high >= low && mid.compareTo(((String[])items[high])[col]) < 0)
high--;
}
else
{
while (high >= low && mid.compareTo(((String[])items[low ])[col]) < 0)
low++;
while (high >= low && mid.compareTo(((String[])items[high])[col]) > 0)
high--;
}
if (low <= high)
swap(low++, high--);
else
break;
}
if (first < high)
qsortString(col,first,high,ascending);
if (low < last)
qsortString(col,low,last,ascending);
}
private void qsortStringNocase(int col, int first, int last, boolean ascending) // guich@220_34
{
if (first >= last)
return;
int low = first;
int high = last;
Object []items = vItems.items;
String mid = ((String[])items[(first+last) >> 1])[col].toLowerCase();
while (true)
{
if (ascending)
{
while (high >= low && mid.compareTo(((String[])items[low ])[col].toLowerCase()) > 0)
low++;
while (high >= low && mid.compareTo(((String[])items[high])[col].toLowerCase()) < 0)
high--;
}
else
{
while (high >= low && mid.compareTo(((String[])items[low ])[col].toLowerCase()) < 0)
low++;
while (high >= low && mid.compareTo(((String[])items[high])[col].toLowerCase()) > 0)
high--;
}
if (low <= high)
swap(low++, high--);
else
break;
}
if (first < high)
qsortStringNocase(col,first,high,ascending);
if (low < last)
qsortStringNocase(col,low,last,ascending);
}
private void postGridEvent(int col, int row, boolean isTextChangedEvent)
{
if (enableSelectDisabledCell || cc == null || (row == -1 || cc.isEnabled(row, col))) // guich@580_31 - guich@tc120_54: commented out
{
ge.touch();
ge.target = this;
ge.row = row;
ge.col = col;
ge.checked = isChecked(selectedLine);
ge.type = isTextChangedEvent ? GridEvent.TEXT_CHANGED_EVENT : (col == 0 && checkEnabled) ? GridEvent.CHECK_CHANGED_EVENT : GridEvent.SELECTED_EVENT;
postEvent(ge);
ge.target = null;
}
}
/** Sets the selected row index to the given one. Note: if this grid has checks,
* the selected line is used to scroll to mark the line that may be checked;
* if the grid does not have checks, then it acts like a normal selected line.
*/
public void setSelectedIndex(int row) // guich@555_7
{
Graphics g=null;
if ( selectedLine != -1 ) // disable the last selected line
{
g = bag.getGraphics();
//drawCursor(g, selectedLine, false);
selectedLine = -1;
}
if (0 <= row && row < itemsCount) // guich@555_7: removed Math.min(itemsCount, linesPerPage), otherwise it will only select the first items
{
// check if the line is currently visible
int l = row - gridOffset;
if (l < 0 || l >= linesPerPage)
{
sbVert.setValue(l<0 ? Math.max(row-linesPerPage+1,0):row);
gridOffset = sbVert.getValue();
}
// changed the order of things so we can have
// the selected line index updated by clicking on the grid
if (g == null) g = bag.getGraphics();
//drawCursor(g, row, true);
selectedLine = row;
isHighlighting = false;
}
if (g != null && isDisplayed())
repaintNow();
//updateScreen();
}
/** Returns the number of lines in this grid */
public int size()
{
return itemsCount;
}
/** Repositions this control in the screen. */
public void reposition()
{
npback = npcapt = null;
reposition(false);
}
public void getFocusableControls(Vector v)
{
if (visible && isEnabled()) v.addElement(this);
}
public Control handleGeographicalFocusChangeKeys(KeyEvent ke)
{
if (!ke.isUpKey() && !ke.isDownKey())
return (ke.key == SpecialKeys.LEFT || ke.key == SpecialKeys.RIGHT) && horizontalScroll(ke.key == SpecialKeys.LEFT) ? this : null;
if ((ke.isUpKey() && selectedLine <= 0) || (ke.isDownKey() && selectedLine == itemsCount-1))
return null;
_onEvent(ke);
return this;
}
/** Sets the number of visible lines (excluding the caption), used to make PREFERRED height return the given number of lines as the grid height.
* This method must be called before setRect.
* @since TotalCross 1.0
*/
public void setVisibleLines(int visibleLines)
{
this.visibleLines = visibleLines;
}
/** Returns the DataSource assigned for this grid, or null if there are none. */
public DataSource getDataSource()
{
return ds;
}
/** This method does nothing.
* @see #removeAllElements
*/
public void removeAll()
{
}
/** Returns the number of items checked.
* @since TotalCross 1.27
*/
public int getCheckCount() // guich@tc126_52
{
return checkedCount;
}
/** Sets an image to be used in the Grid. You must use the same tag in the grid's item.
* The image will be resized to the current font's height, so be sure to call this method
* AFTER the font is set (or after the grid is added to the container).
* <pre>
try
{
String []gridCaptions = {"Image", "Name", "Details" };
int gridWidths[] = {-25, -50, -25};
int gridAligns[] = {CENTER,LEFT,LEFT};
String items[][] =
{
{"@foto1", "Car number one", "good car"},
{"@foto2", "Car number two", "great car"},
};
Grid grid = new Grid(gridCaptions, gridWidths, gridAligns, false);
add(grid, LEFT+5, AFTER+2, FILL-10, PREFERRED);
grid.setImage("@foto1",new Image("foto1.jpg"));
grid.setImage("@foto2",new Image("foto2.jpg"));
grid.setItems(items);
}
catch (Exception ee)
{
MessageBox.showException(ee,true);
}
</pre>
* @since TotalCross 1.53.
*/
public void setImage(String tag, Image image) throws ImageException
{
if (htImages == null)
htImages = new Hashtable(20);
htImages.put(tag, Settings.enableWindowTransitionEffects ? image.smoothScaledFixedAspectRatio(fmH,true) : image.hwScaledFixedAspectRatio(fmH,true));
}
private class NextEdit extends Thread
{
int col,row;
NextEdit(int col, int row) {this.col=col; this.row=row;}
public void run()
{
setSelectedIndex(row);
showControl(row,col);
}
}
/** Traverse throught the Edits of this Grid. */
public Control moveFocusToNextControl(Control c, boolean forward) // guich@tc125_26
{
if (c instanceof Edit)
{
int col = (c.appId >> 24 & 127);
int row = (c.appId & 0xFFFFFF) + (forward ? 1 : -1);
if (0 <= row && row < itemsCount && (cc == null || cc.isEnabled(row,col)))
{
new NextEdit(col,row).start();
return c;
}
else
{
col += (forward ? 1 : -1);
if (0 <= col && col < widths.length && controls[col] != null && controls[col] instanceof Edit)
for (int i = 0; i < itemsCount; i++)
if (cc == null || cc.isEnabled(i,col))
{
new NextEdit(col,i).start();
return c;
}
}
}
return parent.moveFocusToNextControl(c, forward);
}
}