/********************************************************************************* * 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); } }