/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2001 Daniel Tauchke *
* Copyright (C) 2001-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.sys.*;
import totalcross.ui.event.*;
import totalcross.ui.gfx.*;
import totalcross.ui.image.*;
import totalcross.util.*;
/**
* ListBox is a complete implementation of a Listbox.
* You can use the up/down keys to scroll and enter the first
* letter of an item to select it.
* <p>
* Note: the color used in the <code>setBackColor()</code> method will be used in the scrollbar
* only. The background color of the control will be a lighter version of the
* given color.
* <p>
* Here is an example showing how it can be used:
*
* <pre>
* import totalcross.ui.*;
*
* public class MyProgram extends MainWindow
* {
* ListBox lb;
*
* public void initUI()
* {
* lb = new ListBox();
* add(lb);
* lb.add(new String[]{"Daniel","Jenny","Helge","Sandra"});
* lb.add("Marc");
* // you may set the rect by using PREFERRED only after the items were added.
* lb.setRect(LEFT,TOP,PREFERRED,PREFERRED); // use control's preferred width based on the size of the elements
* }
*
* public void onEvent(Event event)
* {
* switch (event.type)
* {
* case ControlEvent.PRESSED:
* if (event.target == lb)
* Object element = lb.getSelectedItem(); // in most cases, this is just a String and may be casted to such
* }
* }
* }
* </pre>
* The first item has index 0.
*/
public class ListBox extends Container implements Scrollable
{
protected Vector items = new Vector();
protected int offset;
protected int selectedIndex=-1;
protected int itemCount;
protected int visibleItems;
protected int btnX,btnX0;
protected ScrollBar sbar;
protected boolean simpleBorder; // used by PopList
protected int xOffset; // guich@500_16
protected int back0,back1;
private int fColor;
private int fourColors[] = new int[4];
protected int customCursorColor=-1;
private IntVector ivWidths;
private int xOffsetMin;
private ArrowButton btnLeft, btnRight;
private int dragDistanceY,dragDistanceX; // kmeehl@tc100
private boolean isScrolling;
private Image npback;
private boolean scScrolled;
/** The gap between the icon and the text. Used in IconItem. Defaults to fmH*4/3.
* If you plan to change this value, do it after calling setFont (if you call it).
* @since TotalCross 1.61
*/
public int iconGap;
/** Set to false to disable border drawing. */
public boolean drawBorder = true;
/** Used to show an icon and a text. You can mix IconItem with other item types in the
* ListBox. Example:
* <pre>
* lb.add("this is a simple text");
* lb.add(new IconItem("this is a text with icon", iconImage));
* </pre>
*
* The icon should have the same size of the font's height, which can be set with:
* <pre>
* iconImage = originalImage.smoothScaledFixedAspectRatio(fmH,true,-1);
* </pre>
*
* @since TotalCross 1.61
*/
public static class IconItem
{
public String text;
public Image icon;
public IconItem(String text, Image icon)
{
this.text = text;
this.icon = icon;
}
public String toString()
{
return text;
}
}
/** An interface that makes easier to draw custom items.
* Example:
* <pre>
class ItemSeek implements ListBox.CustomDrawingItem
{
int tpsinc;
boolean admin;
String plat,date;
ItemSeek(String s)
{
// 21Wi2014/12/05
tpsinc = s.charAt(0)-'0';
admin = s.charAt(1) == '1';
plat = s.substring(2,4);
date = s.substring(4);
}
public void onItemPaint(Graphics g, int dx, int dy, int w, int h)
{
g.drawText(data,dx,dy);
// and also other items
}
}
* </pre>
* @since TotalCross 3.1
*/
public static interface CustomDrawingItem
{
public void onItemPaint(Graphics g, int dx, int dy, int w, int h);
}
/** When the ListBox has horizontal buttons and its height divided by the button height is greater
* than this value (10), the horizontal button heights are increased.
* @see #extraHorizScrollButtonHeight
* @see #enableHorizontalScroll()
*/
public static int EXTRA_HEIGHT_FACTOR = 10;
/** IntHashtable used to specify different background colors for some items. Example:
* <pre>
* list.ihtBackColor = new IntHashtable(10);
* ihtBackColors.put(10,0xAABBCC); // will make line number 10 with back color 0xAABBCC.
* </pre>
* Specify a null value if you want to use the default back color (this also makes drawing faster).
* Note that its up to you to update the hashtable if an item is inserted or removed.
* @since TotalCross 1.0 beta 4
*/
public IntHashtable ihtBackColors;
/** IntHashtable used to specify different foreground colors for some items. Example:
* <pre>
* list.ihtForeColor = new IntHashtable(10);
* ihtForeColors.put(10,Color.RED); // will make line number 10 with fore color RED.
* </pre>
* Specify a null value if you want to use the default fore color (this also makes drawing faster).
* Note that its up to you to update the hashtable if an item is inserted or removed.
* @since TotalCross 1.0 beta 4
*/
public IntHashtable ihtForeColors;
/** The extra height of the horizontal scroll buttons. Defaults 2 in 160x160 or a multiple of it
* in other resolutions.
* @see #EXTRA_HEIGHT_FACTOR
* @see #enableHorizontalScroll()
*/
public int extraHorizScrollButtonHeight = Settings.screenHeight*2/160; // guich@560_11: now depends on the resolution
/** The Flick object listens and performs flick animations on PenUp events when appropriate. */
protected Flick flick;
/** Sets the number of visible lines, used to make PREFERRED height return the given number of lines as the grid height.
* @since TotalCross 1.13
*/
public int visibleLines = -1;
/** If true, all ListBox will have the selection bar drawn in
* the full width instead of the selected's text width
* @since SuperWaba 5.5
*/
public static boolean useFullWidthOnSelection; // guich@550_21
public static final double DEFAULT_ITEM_HEIGHT_FACTOR = 1.5d;
/** In finger touch devices, sets a factor by which the font height will be multiplied to increase the item's height.
* Defaults to 1.5 when Settings.fingerTouch is true, and 1 when its false.
*
* You can change this value before the constructor and restore it after the constructor to change the height
* of a single ListBox.
* <pre>
* ListBox.itemHeightFactor = 1;
* ... create listbox
* ListBox.itemHeightFactor = ListBox.DEFAULT_ITEM_HEIGHT_FACTOR;
* </pre>
* @since TotalCross 1.5
*/
public static double itemHeightFactor = uiAndroid ? DEFAULT_ITEM_HEIGHT_FACTOR : 1;
protected double ihFactor = itemHeightFactor;
/** Used by the DBListBox to store the data column that is displayed. */
protected int dataCol=-1;
/** Creates an empty Listbox. */
public ListBox()
{
this(null);
}
/** Creates a Listbox with the given items. */
public ListBox(Object []items)
{
ignoreOnAddAgain = ignoreOnRemove = true;
sbar = Settings.fingerTouch ? new ScrollPosition() : new ScrollBar();
sbar.focusTraversable = false;
super.add(sbar);
sbar.setLiveScrolling(true);
if (items != null)
{
this.items = new Vector(items);
itemCount = items.length;
}
sbar.setMaximum(itemCount);
this.focusTraversable = true; // kmeehl@tc100
if (Settings.fingerTouch)
{
flick = new Flick(this);
flick.shortestFlick = 50; // make the listbox more responsive
}
iconGap = fmH*4/3;
}
public boolean flickStarted()
{
dragDistanceX = dragDistanceY = 0;
return isScrolling; // only start flick if already scrolling
}
public void flickEnded(boolean atPenDown)
{
}
public boolean canScrollContent(int direction, Object target)
{
if (Settings.fingerTouch)
switch (direction)
{
case DragEvent.UP: return sbar.getValue() > sbar.getMinimum();
case DragEvent.DOWN: return (sbar.getValue() + sbar.getVisibleItems()) < sbar.getMaximum();
case DragEvent.LEFT: return xOffset < 0;
case DragEvent.RIGHT: return xOffset > xOffsetMin;
}
return false;
}
public boolean scrollContent(int xDelta, int yDelta, boolean fromFlick)
{
boolean hFlick = xDelta != 0 && ivWidths != null;
boolean vFlick = yDelta != 0;
int itemH = getItemHeight(0);
if (hFlick)
{
if ((xDelta < 0 && xOffset >= 0) || (xDelta > 0 && xOffset <= xOffsetMin))
hFlick = false;
else
{
dragDistanceX += xDelta;
if (dragDistanceX <= -itemH || dragDistanceX >= itemH)
{
int offsetDelta = dragDistanceX / itemH;
dragDistanceX %= itemH;
xOffset += -offsetDelta * itemH; // invert signal to follow weird onPaint implementation
if (xOffset < xOffsetMin)
xOffset = xOffsetMin;
else if (xOffset > 0)
xOffset = 0;
enableButtons();
Window.needsPaint = true;
}
}
}
if (vFlick)
{
int cur = sbar.getValue();
if ((yDelta < 0 && cur <= sbar.getMinimum()) || (yDelta > 0 && cur >= sbar.getMaximum())) // already at the top/bottom of the view window
vFlick = false;
else
{
dragDistanceY += yDelta;
if (dragDistanceY <= -itemH || dragDistanceY >= itemH)
{
int offsetDelta = dragDistanceY / itemH;
dragDistanceY %= itemH;
sbar.setValue(offset + offsetDelta);
int newOffset = sbar.getValue();
if (newOffset == offset) // did not scroll
vFlick = false;
else
{
if (!fromFlick) sbar.tempShow();
offset = newOffset;
Window.needsPaint = true;
}
}
}
}
return hFlick || vFlick;
}
public int getScrollPosition(int direction)
{
if (direction == DragEvent.LEFT || direction == DragEvent.RIGHT)
return xOffset;
return offset;
}
/** Adds support for horizontal scroll on this listbox. Two buttons will appear below
* the vertical scrollbar. The add, replace and remove operations will be a bit slower
* because the string's width will have to be computed in order to correctly set the max
* horizontal scroll.
* @since SuperWaba 5.6
* @see #extraHorizScrollButtonHeight
*/
public void enableHorizontalScroll() // guich@560_9
{
if (itemCount > 0)
{
int n = itemCount,m=0,w;
int []widths = new int[n];
for (int i = 0; i < n; i++)
if ((w = widths[i] = fm.stringWidth(items.items[i].toString())) > m)
m = w;
ivWidths = new IntVector(widths);
verifyItemWidth(m);
}
else
{
ivWidths = new IntVector();
xOffsetMin = 0;
enableButtons();
}
}
private void enableButtons()
{
if (xOffset < xOffsetMin) // shift to the left if there's no more need to shift and it is shifted
xOffset = xOffsetMin;
if (btnLeft != null)
{
btnLeft.setEnabled(isEnabled() && xOffset < 0);
btnRight.setEnabled(isEnabled() && xOffset > xOffsetMin);
}
}
/** Adds an array of Objects to the Listbox */
public void add(Object []moreItems)
{
add(moreItems, 0, moreItems.length);
}
/** Adds a range of an array of Objects to the Listbox */
public void add(Object []moreItems, int startAt, int size)
{
int realSize = moreItems.length < startAt + size ? moreItems.length - startAt : size;
if (itemCount == 0) // guich@310_5: directly assign the array if this listbox is empty
{
Object[] array = new Object[realSize];
Vm.arrayCopy(moreItems, startAt, array, 0, realSize);
this.items = new Vector(array);
itemCount = realSize;
if (ivWidths != null) // guich@560_9
enableHorizontalScroll(); // just recompute for all items
}
else
{
int n = realSize; //moreItems.length; // guich@450_36
itemCount += n;
for (int i = startAt; i < n; i++)
items.addElement(moreItems[i]);
if (ivWidths != null) // guichich@560_9
{
int w,m=0,mx = -xOffsetMin;
for (int i = 0; i < n; i++)
{
ivWidths.addElement(w=fm.stringWidth(moreItems[i].toString()));
if (w > mx)
m = w;
}
verifyItemWidth(m);
}
}
sbar.setEnabled(isEnabled() && visibleItems < itemCount);
sbar.setMaximum(itemCount); // guich@210_12: forgot this line!
}
/** Adds an Object to the Listbox */
public void add(Object item)
{
items.addElement(item);
if (ivWidths != null) // guich@560_9
{
int w = fm.stringWidth(item.toString());
ivWidths.addElement(w);
verifyItemWidth(w);
}
itemCount++;
sbar.setEnabled(isEnabled() && visibleItems < itemCount);
sbar.setMaximum(itemCount);
}
/** Adds the given text to this ListBox, breaking the text if it goes beyond the ListBox' limits, and also breaking if it contains \n.
* Returns the number of lines. Note that each part of the text is considered a new item. This method is slower than the other <code>add</code> methods.
* @since TotalCross 1.24
*/
public int addWrapping(String text) // guich@tc124_21
{
if (fm.stringWidth(text) <= btnX && text.indexOf('\n') < 0)
{
add(text);
return 1;
}
String[] lines = Convert.tokenizeString(Convert.insertLineBreak(btnX, fm, text),'\n');
add(lines);
return lines.length;
}
/** Adds an Object to the Listbox at the given index */
public void insert(Object item, int index)
{
items.insertElementAt(item, index);
if (ivWidths != null) // guich@560_9
{
int w = fm.stringWidth(item.toString());
ivWidths.insertElementAt(w, index);
verifyItemWidth(w);
}
itemCount++;
sbar.setEnabled(isEnabled() && visibleItems < itemCount);
sbar.setMaximum(itemCount);
}
/** Empties this ListBox, setting all elements of the array to <code>null</code>
so they can be garbage collected.
<b>Attention!</b> If you used the same object array
to initialize two ListBoxes (or ComboBoxes), this method will null both ListBoxes
(because they use the same array reference),
and you'll get a null pointer exception!
*/
public void removeAll() // guich@210_13
{
items.removeAllElements();
sbar.setMaximum(0);
itemCount = 0;
offset=0; // wolfgang@330_23
xOffset = xOffsetMin = 0;
if (ivWidths != null) // guich@560_9
{
ivWidths.removeAllElements();
enableButtons();
}
selectedIndex = -1; // seanwalton@401.26
Window.needsPaint = true;
}
/** Removes the Object at the given index from the Listbox */
public void remove(int itemIndex) // guich@200final_12: new method
{
if (0 <= itemIndex && itemIndex < itemCount)
{
items.removeElementAt(itemIndex);
itemCount--;
if (ivWidths != null) // guich@560_9
{
int old = ivWidths.items[itemIndex];
ivWidths.removeElementAt(itemIndex);
if (old == -xOffsetMin) // was this the max offset? recompute the remaining ones
{
int m = 0;
int []widths = ivWidths.items;
int n = ivWidths.size();
for (int i =0; i < n; i++)
if (widths[i] > m)
m = widths[i];
verifyItemWidth(m);
}
}
sbar.setMaximum(itemCount);
sbar.setEnabled(isEnabled() && visibleItems < itemCount);
if (selectedIndex == itemCount) // last item was removed?
setSelectedIndex(selectedIndex-1);
if ( itemCount == 0 ) // olie@200b4_196: if after removing the list has 0 items, setSelectedIndex( -1 ) is called, which does nothing (see there), then selectedIndex keeps being 0 which is wrong, it has to be -1
selectedIndex = -1;
if (itemCount <= visibleItems && offset != 0) // guich@200final_13
offset = 0;
Window.needsPaint = true;
}
}
/** Removes an Object from the Listbox */
public void remove(Object item)
{
int index;
if (itemCount > 0 && (index=items.indexOf(item)) != -1)
remove(index);
}
/** Replace the Object at the given index, starting from 0 */
public void setItemAt(int i, Object s)
{
if (0 <= i && i < itemCount)
{
items.items[i] = s;
if (ivWidths != null) // guich@560_9
verifyItemWidth(ivWidths.items[i] = fm.stringWidth(s.toString()));
Window.needsPaint = true;
}
}
/** Get the Object at the given Index. Returns an empty string if the index is outside of range. */
public Object getItemAt(int i)
{
if (0 <= i && i < itemCount)
return items.items[i];
return "";
}
/** Returns the selected item of the Listbox or an empty String Object if none is selected */
public Object getSelectedItem()
{
int sel = getSelectedIndex();
return sel >= 0 ? items.items[sel] : ""; // guich@200b4: handle no selected index yet.
}
/** Returns the position of the selected item of the Listbox or -1 if the listbox has no selected index yet. */
public int getSelectedIndex()
{
return selectedIndex;
}
/** Returns all items in this ListBox. If the elements are Strings, the array
* can be casted to String[].
*/
public Object []getItems()
{
return items.toObjectArray();
}
/** Used internally
*/
protected Object []getItemsArray()
{
return items.items;
}
/** Returns the index of the item specified by the name, or -1 if not found. */
public int indexOf(Object name)
{
return items.indexOf(name);
}
/** Selects the given name. If the name is not found, the current selected item is not changed.
* @since SuperWaba 4.01
*/
public void setSelectedItem(Object name) // guich@401_25
{
int idx = indexOf(name);
if (idx != -1)
setSelectedIndex(idx);
}
/** Select the given index and scroll to it if necessary. */
public void setSelectedIndex(int i)
{
setSelectedIndex(i, Settings.sendPressEventOnChange);
}
/** Select the given index and scroll to it if necessary, sending or not the pressed event. */
public void setSelectedIndex(int i, boolean sendPressEvent)
{
if (0 <= i && i < itemCount && i != selectedIndex/* && height != 0*/) // guich@tc100: commented height!=0 otherwise Watch's combobox will not be set properly
{
int vi = sbar.getVisibleItems();
int ma = sbar.getMaximum();
if (offset+vi > ma) // astein@200b4_195: fix list items from being lost when the comboBox.setSelectedIndex() method is used
offset=Math.max(ma-vi,0); // guich@220_4: fixed bug when the listbox is greater than the current item count
selectedIndex = i;
if (selectedIndex >= offset + vi) // kmeehl@tc100
{
offset = selectedIndex - vi + 1;
sbar.setValue(offset);
}
else
if (selectedIndex < offset)
{
offset = selectedIndex;
sbar.setValue(offset);
}
Window.needsPaint = true;
if (sendPressEvent)
postPressedEvent();
}
else
if (i == -1) // guich@200b4_191: unselect all items
{
offset = 0;
selectedIndex = -1;
if (height != 0)
{
sbar.setValue(0);
Window.needsPaint = true;
}
if (sendPressEvent)
postPressedEvent();
}
}
/** Selects the last item added to this listbox, doing a scroll if needed. Calls repaintNow.
* @since SuperWaba 5.6
*/
public void selectLast()
{
selectLast(true);
}
/** Selects the last item added to this listbox, doing a scroll if needed, and sending or not the event. Calls repaintNow.
*/
public void selectLast(boolean sendPressEvent)
{
if (itemCount > 0)
{
setSelectedIndex(itemCount-1, sendPressEvent);
repaintNow();
}
}
/** Returns the number of items */
public int size()
{
return itemCount;
}
/** Do nothing. Adding a control to a ListBox is nonsense. */
public void add(Control control)
{
Vm.warning("add(Control) cannot be used in the ListBox class!");
}
/** Do nothing. Removing a control from a ListBox is nonsense. */
public void remove(Control control)
{
Vm.warning("remove(Control) cannot be used in the ListBox class!");
}
/** Returns the preferred width, ie, the size of the largest item plus the size of the scrollbar. */
public int getPreferredWidth()
{
int extra = (simpleBorder?4:6);
if (!Settings.fingerTouch && sbar.isVisible()) // guich@tc115_77: only include sbar if its visible
extra += sbar.getPreferredWidth();
int maxWidth = 0;
for (int i = itemCount-1; i >= 0; i--)
{
int w = getItemWidth(i);
if (w > maxWidth)
maxWidth = w;
}
return maxWidth + extra + insets.left+insets.right;
}
/** Returns the number of items multiplied by the font metrics height */
public int getPreferredHeight()
{
int n = visibleLines == -1 ? itemCount : visibleLines; // guich@tc113_11: use visibleLines if set
int lineH = getItemHeight(0);
int h = Math.max(lineH*n,sbar.getPreferredHeight())+(simpleBorder?4:6);
if (ivWidths != null && h < 4*lineH) // guich@tc114_64
h = 4*lineH;
return (n==1 ? h-1 : h) + insets.top+insets.bottom;
}
/** This is needed to recalculate the box size for the selected item if the control is resized by the main application */
protected void onBoundsChanged(boolean screenChanged)
{
iconGap = fmH*4/3;
npback = null;
int btnW = sbar.getPreferredWidth();
int extraHB = 0;
if ((this.height/btnW > EXTRA_HEIGHT_FACTOR)) // guich@tc100b4_28 - guich@tc100b5_21: now using a proportion of the button.
extraHB = Settings.screenHeight*4/160;
int m = uiFlat?0:simpleBorder?1:2,n=0;
visibleItems = (height-m-2) / getItemHeight(0);
btnX = width - m - btnW;
btnX0 = btnX;
if (!sbar.isVisible())
btnX = width-1;
if (ivWidths != null) // guich@560_9: handle horiz scroll?
{
if (btnRight == null)
{
int hh = 3 * fmH / 11;
super.add(btnRight = new ArrowButton(Graphics.ARROW_RIGHT, hh, foreColor));
super.add(btnLeft = new ArrowButton(Graphics.ARROW_LEFT, hh, foreColor));
btnRight.focusTraversable = btnLeft.focusTraversable = false;
tabOrder.removeElement(btnRight); // guich@572_6: remove them from the tabOrder, otherwise it will block the control navigation in some situations (AllTests)
tabOrder.removeElement(btnLeft);
onColorsChanged(true); // guich@tc111_6
}
n = (btnRight.getPreferredHeight() + extraHorizScrollButtonHeight + extraHB) << 1;
if (uiFlat) n-=2; // in flat, make the buttons overlap a bit
enableButtons();
}
sbar.setMaximum(itemCount);
sbar.setVisibleItems(visibleItems);
sbar.setEnabled(visibleItems < itemCount);
if (Settings.fingerTouch)
{
sbar.setRect(RIGHT-1,m,PREFERRED,FILL, null, screenChanged);
if (ivWidths != null)
{
btnLeft.setVisible(false);
btnRight.setVisible(false);
}
}
else
sbar.setRect(btnX,m,btnW,height-(m<<1)-n, null, screenChanged);
if (Settings.keyboardFocusTraversable) sbar.setFocusLess(true); // guich@570_39
if (ivWidths != null) // guich@560_9: handle horiz scroll?
{
n = uiFlat ? 1 : 0; // in flat, make the buttons overlap a bit
// add the two horizontal scroll buttons below the scrollbar
btnLeft.setRect (SAME, AFTER-n, SAME, PREFERRED+extraHorizScrollButtonHeight+extraHB, null, screenChanged);
btnRight.setRect(SAME, AFTER-n, SAME, PREFERRED+extraHorizScrollButtonHeight+extraHB, null, screenChanged);
btnLeft.repositionAllowed = btnRight.repositionAllowed = false; // we'll handle the reposition ourselves
}
if (visibleItems >= itemCount) // volkernigge@572_7: fixed listbox problem when an index was selected before the bounds were set.
offset = 0;
}
/** Searches this ListBox for an item with the first letter matching the given char. The search is made case insensitive. Note: if you override this class you must implement this method. */
protected void find(char c)
{
int i;
c = Convert.toUpperCase(c); // dbeers@570_103: make sure that the letter is uppercased.
int foundIndex = -1; // guich@450_30: fix search when exist repeating letters (cat, chicken, cow - pressing C 3 times)
// first search from the next item
for (i =selectedIndex+1; i < itemCount; i++)
{
String s = items.items[i].toString(); // guich@220_37
if (s.length() > 0 && Convert.toUpperCase(s.charAt(0)) == c) // first letter matches?
{
foundIndex = i;
break;
}
}
if (foundIndex == -1 && selectedIndex >= 0) // if didnt found, search from the beginning.
for (i =0; i < selectedIndex; i++)
{
String s = items.items[i].toString(); // guich@220_37
if (s.length() > 0 && Convert.toUpperCase(s.charAt(0)) == c) // first letter matches?
{
foundIndex = i;
break;
}
}
if (foundIndex != -1)
{
setSelectedIndex(foundIndex);
Window.needsPaint = true;
}
}
/** Handles the events for this control. */
public void onEvent(Event event)
{
PenEvent pe;
if (isEnabled())
switch (event.type)
{
case ControlEvent.PRESSED:
if (event.target == sbar)
{
int newOffset = sbar.getValue();
if (newOffset != offset) // guich@200final_3: avoid unneeded repaints
{
offset = newOffset;
Window.needsPaint = true;
}
}
else
if (event.target == btnLeft || event.target == btnRight)
horizontalScroll(event.target == btnLeft);
break;
case KeyEvent.KEY_PRESS:
find(Convert.toUpperCase((char)((KeyEvent)event).key));
break;
case KeyEvent.SPECIAL_KEY_PRESS:
KeyEvent ke = (KeyEvent)event;
if (Settings.keyboardFocusTraversable && ke.isActionKey()) // guich@550_15
postPressedEvent();
else
if (ke.isPrevKey() || ke.isNextKey()) // guich@220_19 - guich@330_45
{
if (Settings.keyboardFocusTraversable)
{
if (ke.isUpKey())
setSelectedIndex(Settings.circularNavigation ? (selectedIndex==0 ? itemCount-1 : (selectedIndex-1)) : Math.max(selectedIndex-1,0));
else
if (ke.isDownKey())
setSelectedIndex(Settings.circularNavigation ? (selectedIndex==itemCount-1 ? 0 : (selectedIndex+1)) : Math.min(selectedIndex+1,itemCount-1));
else
if (ke.key == SpecialKeys.LEFT)
{
if (!horizontalScroll(true))
leftReached();
}
else
if (ke.key == SpecialKeys.RIGHT) // guich@560_9
horizontalScroll(false);
}
else
if (ke.key == SpecialKeys.LEFT || ke.key == SpecialKeys.RIGHT) // guich@560_9
horizontalScroll(ke.key == SpecialKeys.LEFT);
else
sbar._onEvent(event);
}
break;
case PenEvent.PEN_UP:
if (event.target == this && !isScrolling) // if scrolling, do not end selection
{
pe = (PenEvent)event;
if (Settings.fingerTouch)
handleSelection(((pe.y- (simpleBorder?3:4)) / getItemHeight(0)) + offset); // guich@200b4: corrected line selection
// Post the event
int newSelection = ((pe.y- (simpleBorder?3:4)) / getItemHeight(0)) + offset; // guich@200b4_2: corrected line selection
if (isInsideOrNear(pe.x,pe.y) && pe.x < btnX && newSelection < itemCount)
postPressedEvent();
endSelection();
}
isScrolling = false;
break;
case PenEvent.PEN_DRAG:
DragEvent de = (DragEvent)event;
if (Settings.fingerTouch)
{
if (isScrolling)
{
scrollContent(-de.xDelta, -de.yDelta, true);
event.consumed = true;
}
else
{
int direction = DragEvent.getInverseDirection(de.direction);
event.consumed = true;
if (canScrollContent(direction, de.target) && scrollContent(-de.xDelta, -de.yDelta, true))
isScrolling = scScrolled = true;
}
}
break;
case KeyEvent.ACTION_KEY_PRESS: // guich@tc113_9
if (!(this instanceof MultiListBox) && selectedIndex >= 0)
{
boolean old = isHighlighting;
postPressedEvent();
isHighlighting = old;
}
break;
case PenEvent.PEN_DOWN:
scScrolled = false;
pe = (PenEvent)event;
if (!Settings.fingerTouch && event.target == this && pe.x < btnX && isInsideOrNear(pe.x,pe.y))
handleSelection(((pe.y- (simpleBorder?3:4)) / getItemHeight(0)) + offset); // guich@200b4: corrected line selection
break;
}
}
protected void leftReached()
{
}
protected void endSelection()
{
}
protected void handleSelection(int newSelection)
{
if (newSelection != selectedIndex && newSelection < itemCount)
{
if (transparentBackground) // guich@tc115_18: on transparent backgrounds, we must repaint everything
{
selectedIndex = newSelection;
Window.needsPaint = true;
}
else
{
//Graphics g = getGraphics();
//if (selectedIndex >= 0)
// drawCursor(g,selectedIndex,false);
selectedIndex = newSelection;
Window.needsPaint = true;
//drawCursor(g,selectedIndex,true);
}
}
}
public void setEnabled(boolean enabled)
{
if (internalSetEnabled(enabled,false))
{
sbar.setEnabled(enabled && visibleItems < itemCount);
if (btnLeft != null)
enableButtons();
}
}
protected void onColorsChanged(boolean colorsChanged)
{
npback = null;
fColor = getForeColor();
back0 = Color.brighter(getBackColor());
back1 = customCursorColor!=-1 ? customCursorColor : (back0 != Color.WHITE) ? backColor : Color.getCursorColor(back0);//guich@300_20: use backColor instead of: back0.getCursorColor(); // guich@210_19
if (fColor == back1) // guich@200b4_206: ops! same color?
fColor = foreColor;
if (!uiAndroid) Graphics.compute3dColors(isEnabled(),backColor,foreColor,fourColors);
if (btnRight != null)
{
btnRight.setBackForeColors(uiVista?back0:backColor, foreColor);
btnLeft.setBackForeColors(uiVista?back0:backColor, foreColor);
}
if (colorsChanged)
sbar.setBackForeColors(uiVista?back0:backColor,foreColor);
}
public void onPaint(Graphics g)
{
// Draw background and borders
g.backColor = uiAndroid ? parent.backColor : back0;
if (!transparentBackground) // guich@tc115_18
g.fillRect(0,0,width,height); // guich@tc115_77: fill till end because the scrollbar may not being shown
if (drawBorder)
{
if (uiAndroid)
{
if (npback == null)
try
{
npback = NinePatch.getInstance().getNormalInstance(NinePatch.LISTBOX, width, height, isEnabled() ? back0 : Color.interpolate(back0,parent.backColor), false);
}
catch (ImageException e) {}
NinePatch.tryDrawImage(g,npback,0,0);
}
g.foreColor = foreColor;
if (!uiAndroid)
g.draw3dRect(0,0,width,height,Graphics.R3D_CHECK,false,false,fourColors);
}
g.foreColor = fColor;
int dx = 2; // guich@580_41: changed from 3 to 2
int dy = 3;
if (uiFlat) dy--;
if (simpleBorder) {dx--; dy--;}
setTextAreaClip(g,dx,dy); // guich@tc100b4_5
dx += xOffset;
int greatestVisibleItemIndex = Math.min(itemCount, visibleItems+offset); // code corrected by Bjoem Knafla
dx++;
drawItems(g, dx, dy, greatestVisibleItemIndex);
}
protected void drawItems(Graphics g, int dx, int dy, int greatestVisibleItemIndex)
{
for (int i = offset; i < greatestVisibleItemIndex; dy += getItemHeight(i++))
drawItem(g,i,dx,dy); // guich@200b4: let the user extend ListBox and draw the items himself
drawSelectedItem(g, offset, greatestVisibleItemIndex);
}
protected int getItemHeight(int i)
{
return Settings.fingerTouch ? (int)(fmH*ihFactor) : fmH;
}
protected void setTextAreaClip(Graphics g, int dx, int dy) // guich@tc100b4_5: use a common routine to prevent errors
{
int yy = dy;
g.setClip(dx+1,yy,btnX-dx-2,Math.min(height-(uiAndroid?2:1)-yy,getItemHeight(0)*visibleItems + (uiAndroid?1:2))); // guich@tc100b5_20: don't let get over the border - guich@tc115_77: if scrollbar is not shown, use the whole area
}
protected void drawSelectedItem(Graphics g, int from, int to)
{
if (selectedIndex >= 0) drawCursor(g,selectedIndex,true);
}
/** Sets the cursor color for this ListBox. The default is equal to the background slightly darker. */
public void setCursorColor(int color)
{
this.customCursorColor = color;
onColorsChanged(true);
}
/** You can extend ListBox and overide this method to draw the items */
protected void drawItem(Graphics g, int index, int dx, int dy)
{
Object obj = items.items[index];
if (obj instanceof CustomDrawingItem)
{
((CustomDrawingItem)obj).onItemPaint(g, dx, dy, width, getItemHeight(index));
return;
}
if (obj instanceof IconItem)
{
g.drawImage(((IconItem)obj).icon,dx,dy);
dx += iconGap;
}
String s = obj.toString();
// guich@tc100b4: allow change of back/fore colors
int f = g.foreColor;
if (ihtForeColors != null)
g.foreColor = ihtForeColors.get(index,f);
if (ihtBackColors != null)
{
int b = g.backColor;
g.backColor = ihtBackColors.get(index,b);
g.fillRect(dx,dy,useFullWidthOnSelection ? btnX : fm.stringWidth(s), getItemHeight(index));
g.backColor = b;
}
g.drawText(s,dx,dy+(!uiAndroid?0:(getItemHeight(index)-fmH)/2), textShadowColor != -1, textShadowColor); // guich@402_31: don't test for index out of bounds. this will be catched in the caller
g.foreColor = f;
}
/** You can extend ListBox and overide this method to draw the items */
protected void drawSelectedItem(Graphics g, int index, int dx, int dy)
{
g.drawText(getText(),dx,dy, textShadowColor != -1, textShadowColor);
}
/** Returns the width of the given item index with the current fontmetrics. Note: if you overide this class you must implement this method. */
protected int getItemWidth(int index)
{
Object obj = items.items[index];
return obj == null ? 0 : fm.stringWidth(obj.toString()) + (obj instanceof IconItem ? iconGap : 0);
}
int getIndexY(int sel)
{
int dy = 3;
if (uiFlat) dy--;
if (simpleBorder) dy--;
int ih = getItemHeight(sel);
dy += (sel-offset) * ih;
return dy + ih + fm.descent;
}
/** This method is used to draw the cursor around the desired item */
protected void drawCursor(Graphics g, int sel, boolean on)
{
if (offset <= sel && sel < visibleItems+offset && sel < itemCount) // guich@555_10: fixed in all ui styles
{
g.foreColor = fColor; // guich@520_15: by using fillRect we must set to the textcolor
g.backColor = on?getCursorColor(sel):back0;
int dx = 3; // guich@580_41: cursor must be drawn at 3 or will overwrite the border on a combobox with PalmOS style
int dy = 3;
if (uiFlat) dy--;
if (simpleBorder) {dx--; dy--;}
setTextAreaClip(g,dx-1,dy); // guich@tc100b4_5
int ih = getItemHeight(sel);
dx += xOffset; // guich@552_24: added this to make scroll apply to the item
dy += (sel-offset) * ih;
int sw;
if (useFullWidthOnSelection || (sw = getItemWidth(sel)) == 0) // guich@500_16: use the full text width due to the horizontal scroll - guich@200final_5: if item is a zero length string, invert the complete row.
sw = btnX-4;
g.fillRect(dx-1, dy-1, sw+2, ih+2); // pgr@520_4: if this is an image or an antialiased font, using eraseRect will make it ugly. - guich@552_7: added -1 to fix cursor not overwriting border.
drawItem(g, sel, dx, dy); // pgr@520_4
//if (on && getParentWindow() instanceof ComboBoxDropDown && !(this instanceof MultiListBox)) Window.updateScreen(); // guich@tc114_80: update screen before the combobox closes. not comparing with ComboBoxDropDown results in screen FLICKERing - guich@tc115_89: prevent flicker in MultiListBox
}
}
protected int getCursorColor(int index)
{
return back1;
}
/** Sets the border of the listbox to be not 3d if flag is true. */
public void setSimpleBorder(boolean simpleBorder) // guich@200b4_93
{
this.simpleBorder = simpleBorder;
}
/** Sorts the elements of this ListBox. The current selection is cleared. */
public void qsort() // guich@220_35
{
items.qsort();
setSelectedIndex(-1);
}
/** Sorts the elements of this ListBox. The current selection is cleared.
* @param caseless Pass true to make a caseless sort, if the items are Strings.
*/
public void qsort(boolean caseless) // guich@tc113_5
{
if (size() > 0)
{
Convert.qsort(items.items, 0, items.size()-1, (caseless && items.items[0] instanceof String) ? Convert.SORT_STRING_NOCASE : Convert.SORT_AUTODETECT, true);
setSelectedIndex(-1);
}
}
/** Check if it is needed to change the xOffsetMin based in the given item's text width */
private void verifyItemWidth(int w)
{
int newxOffsetMin = -Math.max(w-width+sbar.width+5,0);
if (newxOffsetMin < xOffsetMin)
{
xOffsetMin = newxOffsetMin;
enableButtons();
}
}
/** Scrolls the grid horizontaly as needed */
private boolean horizontalScroll(boolean toLeft)
{
int step = this.width >> 1;
int newOffset = toLeft ? Math.min(xOffset + step, 0) : Math.max(xOffset - step, xOffsetMin);
if (newOffset != xOffset)
{
xOffset = newOffset;
enableButtons();
Window.needsPaint = true;
return true;
}
return false;
}
/** Clears this control, selecting index clearValueInt. */
public void clear() // guich@572_19
{
setSelectedIndex(clearValueInt);
}
public void getFocusableControls(Vector v)
{
if (visible && isEnabled()) v.addElement(this);
}
public Control handleGeographicalFocusChangeKeys(KeyEvent ke) // any change here must synchronize with MultiListBox'
{
if ((ke.isPrevKey() && !ke.isUpKey()) || (ke.isNextKey() && !ke.isDownKey()))
{
int oldXOffset = xOffset;
_onEvent(ke);
return (oldXOffset != xOffset) ? this : null;
}
if ((ke.isUpKey() && selectedIndex <= 0) || (ke.isDownKey() && selectedIndex == itemCount -1))
return null;
_onEvent(ke);
return this;
}
/** Returns the string of the selected item or "" if none is selected. */
public String getText()
{
return selectedIndex < 0 ? "" : getSelectedItem().toString();
}
/** Selects the item that starts with the given text
* @param text The text string to search for
* @param caseInsensitive If true, the text and all searched strings are first converted to lowercase.
* @return If an item was found and selected.
* @since TotalCross 1.13
*/
public boolean setSelectedItemStartingWith(String text, boolean caseInsensitive) // guich@tc113_2
{
if (caseInsensitive) text = text.toLowerCase();
for (int i =0; i < itemCount; i++)
{
String s = items.items[i].toString();
if (caseInsensitive) s = s.toLowerCase();
if (s.startsWith(text))
{
setSelectedIndex(i);
return true;
}
}
return false;
}
/** This method hides the scrollbar if its not needed, i.e., if horizontal scroll is disabled and the preferred
* height is smaller than the actual height. You may have to call <code>reposition</code> if this method returns true.
* You can call this method after all items were added.
* @return True if the scrollbar was hidden.
* @since TotalCross 1.15
*/
public boolean hideScrollBarIfNotNeeded() // guich@tc115_77
{
boolean showSB = ivWidths != null || getPreferredHeight() > getHeight();
if (sbar.isVisible() != showSB)
{
sbar.setVisible(showSB);
btnX = showSB ? btnX0 : width-1;
return true;
}
return false;
}
public Flick getFlick()
{
return flick;
}
public boolean wasScrolled()
{
return scScrolled;
}
}