/*********************************************************************************
* 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.*;
/**
* ScrollBar is an implementation of a Scrollbar.
* The scrollbar orientation can be horizontal or vertical.
* It implements auto scroll when pressing and holding a button or the gap area of the scrollBar.
* Here is an example of how to use it.
* <pre>
* // declarations
* ScrollBar sb1;
* ScrollBar sb2;
* ScrollBar sb3;
* ScrollBar sb4;
* // init
* public void testScrollBars()
* {
* add(sb1 = new ScrollBar(ScrollBar.VERTICAL), RIGHT, CENTER, PREFERRED, totalcross.sys.Settings.screenHeight/2);
* add(sb2 = new ScrollBar(ScrollBar.VERTICAL), BEFORE, SAME, PREFERRED, SAME);
* sb2.setLiveScrolling(true);
* add(sb3 = new ScrollBar(ScrollBar.HORIZONTAL), LEFT,CENTER,totalcross.sys.Settings.screenWidth/2, PREFERRED);
* add(sb4 = new ScrollBar(ScrollBar.HORIZONTAL), SAME, AFTER, SAME, PREFERRED);
* }
*
* public void onEvent(Event event)
* {
* if (event.type == ControlEvent.PRESSED && event.target == sb2)
* {
* int value = sb2.getValue();
* sb1.setValue(value);
* sb3.setValue(value);
* sb4.setValue(value);
* }
* }
* </pre>
*/
public class ScrollBar extends Container
{
/** To be passed in the constructor */
public static final byte VERTICAL = 1;
/** To be passed in the constructor */
public static final byte HORIZONTAL = 2;
/** Set to true to disable block increments, which occurs when the user clicks outside the bar and buttons.
* @since TotalCross 1.3.4
*/
public boolean disableBlockIncrement;
protected int maximum = 100;
protected int minimum;
protected int blockIncrement = 50;
protected int unitIncrement = 1;
protected int value;
protected int visibleItems = 50;
protected double valuesPerPixel;
protected int dragBarSize;
protected int btnWH;
protected ArrowButton btnInc, btnDec;
protected int startDragPos=-1;
protected boolean verticalBar;
protected int dragBarPos;
protected boolean liveScrolling;
protected int size;
protected int dragBarMax,dragBarMin;
protected int bColor,sbColor,sfColor,sbColorDis;
protected int fourColors[] = new int[4];
int thumbSize; // guich@tc134: used in ScrollPosition
/** The minimum dragbar size in pixels. By default, 5. This has no effect if the ui style is Palm OS. */
public int minDragBarSize = 5*Settings.screenHeight/160; // guich@510_26
/* luciana@570_22: Implements auto scroll when pressing and holding button or scrollBar */
protected boolean enableAutoScroll = true; // guich@tc134
private TimerEvent autoScrollTimer;
private Control autoScrollTarget;
private boolean buttonScroll;
private int autoScrollBarPos;
/** The initial delay to start the automatic scroll. */
public static int INITIAL_DELAY = 600;
/** The frequency in which the scroll will be done. */
public static int AUTO_DELAY = 100;
/** The extra size (width for vertical ScrollBars or height for horizontal ones) used in all ScrollBars.
* Note that this member is static so it will affect all ScrollBars created afterwards, unless you reset it to 0.
* @since TotalCross 1.14
*/
public static int extraSize; // guich@tc114_48
/** Creates Scrollbar with default values:<br>
* maximum = 100 <br>
* minimum = 0<br>
* orientation = VERTICAL<br>
* blockIncrement = 50<br>
* unitIncrement = 1<br>
* value = 0<br>
* visibleItems = blockIncrement<br>
*/
public ScrollBar()
{
this(VERTICAL);
}
/** Creates Scrollbar with the given orientation and these default values:<br>
* maximum = 100 <br>
* minimum = 0<br>
* blockIncrement = 50<br>
* unitIncrement = 1<br>
* value = 0<br>
* visibleItems = blockIncrement<br>
*/
public ScrollBar(byte orientation)
{
ignoreOnAddAgain = ignoreOnRemove = true;
this.verticalBar = orientation == VERTICAL;
int extra = Settings.fingerTouch ? 3 : 0;
btnDec = new ArrowButton(verticalBar?Graphics.ARROW_UP :Graphics.ARROW_LEFT ,fmH*3/11+extra,Color.BLACK);
btnInc = new ArrowButton(verticalBar?Graphics.ARROW_DOWN:Graphics.ARROW_RIGHT,fmH*3/11+extra,Color.BLACK);
btnDec.focusTraversable = btnInc.focusTraversable = false;
add(btnDec);
add(btnInc);
btnDec.setBorder(Button.BORDER_3D);
btnInc.setBorder(Button.BORDER_3D);
started = true; // avoid calling the initUI method
this.focusTraversable = true; // kmeehl@tc100
}
/** Sets the value, visibleItems, minimum and maximum values */
public void setValues(int newValue, int newVisibleItems, int newMinimum, int newMaximum)
{
maximum = newMaximum;
minimum = newMinimum;
visibleItems = Math.max(newVisibleItems,1);
blockIncrement = visibleItems; // / 4
if (visibleItems+newValue+minimum <= maximum)
value = newValue;
else
value = maximum-visibleItems; // msicotte@502_5: don't let value get more than maximum
if (value < minimum) value = minimum; // neither less than minimum
recomputeParams(false);
Window.needsPaint = true;
}
/** Set the maximum value. Note that you must explicitly call repaint. */
public void setMaximum(int i)
{
if (i != maximum) // guich@320_30
{
maximum = i;
if (value > i) value = i; // guich@421_56
recomputeParams(false);
}
}
/** Get the maximum value */
public int getMaximum()
{
return maximum;
}
/** Set the minimum value */
public void setMinimum(int i)
{
if (i != minimum)
{
minimum = i;
if (value < i) value = i; // guich@521_56
recomputeParams(false);
Window.needsPaint = true;
}
}
/** Get the minimum value */
public int getMinimum()
{
return minimum;
}
/** Set the amount to increment the value when clicking above the bar.
This value is set as default to be equal to visibleItems. */
public void setBlockIncrement(int i)
{
blockIncrement = i;
}
/** Get the amount to increment the value when clicking above the bar. */
public int getBlockIncrement()
{
return blockIncrement;
}
/** Set the amount to increment the value when clicking the up or down buttons */
public void setUnitIncrement(int i)
{
unitIncrement = i;
Window.needsPaint = true;
}
/** Get the amount to increment the value when clicking the up or down buttons */
public int getUnitIncrement()
{
return unitIncrement;
}
/** Sets the value. */
public void setValue(int i)
{
if (i != value) // guich@320_30
{
value = (visibleItems+i/*+minimum*/ <= maximum) ? i : (maximum-visibleItems); // guich@510_25: if greater than the maximum, set to the max. guich@tc100: removed +minimum, because if min=6,max=22,vis.items=1,value=18, it would fail
if (value < minimum) value = minimum; // guich@tc100: don't let the value get under the minimum
recomputeParams(true); // can't remove this line.
Window.needsPaint = true;
}
}
/** Get the value. This is the value minus the visible items. */
public int getValue()
{
return value;
}
/** Set the count of visible items for the scrollbar. This value cannot be zero.
* It also sets the blockIncrement to be equal to the given value.
*/
public void setVisibleItems(int i)
{
visibleItems = i;
if (visibleItems <= 0) visibleItems = 1;
blockIncrement = visibleItems;
recomputeParams(false);
Window.needsPaint = true;
}
/** Get the count of visible items for the scrollbar */
public int getVisibleItems()
{
return visibleItems;
}
/** Set the live scrolling. If "true" an event is thrown during dragging or when the button is held. */
public void setLiveScrolling(boolean liveScrolling)
{
this.liveScrolling = liveScrolling;
}
protected void recomputeParams(boolean justValue)
{
if (size <= 0) return;
if (!justValue)
{
// Calculate and draw the slider button
int delta = Math.max(visibleItems, maximum-minimum);
int barArea = size-(btnWH<<1);
if (thumbSize != 0)
barArea -= thumbSize - (barArea * visibleItems / delta);
if (uiFlat) barArea+=2; // on the flat mode, the bar area starts over the buttons
valuesPerPixel = (double)barArea / (double)delta;
dragBarSize = thumbSize != 0 ? thumbSize : Math.max(minDragBarSize,Math.min(barArea,(int) (valuesPerPixel * visibleItems)+1)); // guich@300_13
dragBarMin = uiFlat ? (btnWH-1) : btnWH;
dragBarMax = size-dragBarMin-dragBarSize;
}
dragBarPos = Math.min(dragBarMax,dragBarMin+(int)(valuesPerPixel * (value-minimum) + 0.5d)); // round value - guich@512_12: subtract minimum from value
enableButtons();
}
public void onEvent(Event event)
{
int oldValue = value,pos;
boolean mustPostEvent = false;
switch (event.type)
{
case TimerEvent.TRIGGERED: // luciana@570_22
if (autoScrollTimer == null) break; // vinicius@584_7
if (autoScrollTarget == this)
{
autoScrollTimer.millis = AUTO_DELAY;
onAutoScroll();
}
else
{
autoScrollTimer.millis = AUTO_DELAY;
postEvent(getPressedEvent(autoScrollTarget));
}
break;
case ControlEvent.PRESSED:
if (event.target == btnDec)
{
value=Math.max(minimum,value-unitIncrement);
event.consumed = true;
if (!buttonScroll)
requestFocus();
} // guich@220_12: unitIncrement now is being used
else
if (event.target == btnInc)
{
value=Math.min(maximum,value+unitIncrement);
event.consumed = true;
if (!buttonScroll)
requestFocus();
}
//mustPostEvent = true; afarine@350_15: avoid extra 2 events when pressing a button - the event will be posted in the penUp event
break;
case PenEvent.PEN_DRAG_START:
case PenEvent.PEN_DRAG_END:
pos = verticalBar?((PenEvent)event).y:((PenEvent)event).x;
if (disableBlockIncrement && (pos < dragBarPos || pos > dragBarPos+dragBarSize))
event.consumed = true;
return;
case PenEvent.PEN_DOWN:
if (event.target == this) // can be the buttons
{
if (enableAutoScroll)
{
autoScrollTimer = addTimer(INITIAL_DELAY);
autoScrollTarget = this;
}
pos = verticalBar?((PenEvent)event).y:((PenEvent)event).x;
this.autoScrollBarPos = pos;
if (pos < dragBarPos)
{
if (!disableBlockIncrement)
value -= blockIncrement;
else
event.consumed = true;
}
else
if (pos > dragBarPos+dragBarSize)
{
if (!disableBlockIncrement)
value += blockIncrement;
else
event.consumed = true;
}
else
{
startDragPos = pos-dragBarPos; // point inside drag bar
Window.needsPaint = true;
}
}
else if (enableAutoScroll && (event.target == btnInc || event.target == btnDec)) // luciana@570_22
{
autoScrollTimer = addTimer(INITIAL_DELAY);
autoScrollTarget = (Control)event.target;
buttonScroll = true;
}
break;
case PenEvent.PEN_DRAG:
if (event.target == btnDec || event.target == btnInc) // kmeehl@tc100: cancel the scroll timer when the user drags off of the buttons
{
Control btn = (Control)event.target;
PenEvent pe = (PenEvent)event;
if (!btn.isInsideOrNear(pe.x, pe.y) && autoScrollTimer != null) // guich@tc114_51: must add control's coordinates
{
disableAutoScroll();
break;
}
}
pos = verticalBar?((PenEvent)event).y:((PenEvent)event).x;
if (disableBlockIncrement && (pos < dragBarPos || pos > dragBarPos+dragBarSize))
{
event.consumed = true;
return;
}
if (startDragPos != -1)
{
dragBarPos = pos - startDragPos;
if (dragBarPos < dragBarMin) dragBarPos = dragBarMin; else
if (dragBarPos > dragBarMax) dragBarPos = dragBarMax;
if (dragBarPos == dragBarMax) // guich@240_12: make sure that at the maximum bar pos we get the maximum value
value = Math.max(0,maximum-visibleItems); // guich@556_7: fixed the correct value, subtracting by visibleItems
else
{
value = (int) ((dragBarPos-dragBarMin)/valuesPerPixel);
if (unitIncrement != 1) // guich@340_45: snap the scrollbar to the nearest increment
value = ((int)value/unitIncrement)*unitIncrement;
value += minimum; // msicotte@502_5: fixes problem when minimum is different of zero
}
}
autoScrollBarPos = verticalBar?((PenEvent)event).y:((PenEvent)event).x;
event.consumed = true;
Event.clearQueue(PenEvent.PEN_DRAG); // guich@tc122_26: not for Palm OS
break;
case PenEvent.PEN_UP:
if (autoScrollTimer != null)
disableAutoScroll();
Window.needsPaint = true;
startDragPos = -1;
mustPostEvent = btnDec.isEnabled() || btnInc.isEnabled();
pos = verticalBar?((PenEvent)event).y:((PenEvent)event).x;
if (disableBlockIncrement && (pos < dragBarPos || pos > dragBarPos+dragBarSize))
{
event.consumed = true;
return;
}
recomputeParams(true); // guich@tc100: otherwise, Slider may fall into a wrong position on pen up
break;
case KeyEvent.SPECIAL_KEY_PRESS:
KeyEvent ke = (KeyEvent)event;
if (ke.isPrevKey()) // guich@330_45
{
if (btnDec.isEnabled())
value -= Settings.keyboardFocusTraversable ? unitIncrement : blockIncrement;
event.consumed = true;
mustPostEvent = true; // guich@240_21
}
else
if (ke.isNextKey()) // guich@330_45
{
if (btnInc.isEnabled())
value += Settings.keyboardFocusTraversable ? unitIncrement : blockIncrement;
event.consumed = true;
mustPostEvent = true; // guich@240_21
}
else
if (ke.isActionKey())
{
parent.requestFocus();
isHighlighting = true;
}
break;
}
// only repaint if necessary
if (value != oldValue)
setNewValue(oldValue, mustPostEvent);
else
if (mustPostEvent)
{
parent.postEvent(getPressedEvent(this));
isHighlighting = false; // don't let postEvent steal our focus!
}
}
private void setNewValue(int oldValue, boolean mustPostEvent)
{
if (value > maximum-visibleItems)
{
value = maximum-visibleItems;
removeTimer(autoScrollTimer);
}
// else - guich@tc113_16: commented out this else to make sure we're not below min if value is changed in the if above
if (value <= minimum)
{
value = minimum;
removeTimer(autoScrollTimer);
}
if (value != oldValue)
{
recomputeParams(true);
if (liveScrolling || mustPostEvent)
postPressedEvent();
Window.needsPaint = true;
}
}
/** Scrolls a block, and post the PRESSED event if the value changes. */
public void blockScroll(boolean inc)
{
int oldValue = value;
this.value = inc ? value + blockIncrement : value - blockIncrement;
setNewValue(oldValue, true);
}
private void disableAutoScroll() // luciana@570_22
{
removeTimer(autoScrollTimer);
autoScrollTimer = null;
autoScrollTarget = null;
if (buttonScroll)
{
buttonScroll = false;
enableButtons();
}
}
private void enableButtons() // luciana@570_22
{
boolean b = true;
Window win = getParentWindow();
if (!buttonScroll)
btnDec.setEnabled(b = (isEnabled() && value > minimum));
if (!b && win != null && win.getFocus() == btnDec) // if we're disabled now and we got the focus, move focus to other control
requestFocus();
if (!buttonScroll)
btnInc.setEnabled(b = (isEnabled() && value+visibleItems < maximum));
Control foc = win == null ? null : win.getFocus();
if (!b && (foc == btnDec || foc == btnInc)) // guich@572_16: only if the focus is one of the buttons - bruno@tc114: avoid auto-setting the focus to a multi-edit
{
requestFocus();
if (!b && win != null && win.getFocus() == btnInc) // if we're disabled now and we got the focus, move focus to other control - guich@450_37: replaced btnDec by btnInc
requestFocus();
}
}
private void onAutoScroll() // luciana@570_22
{
if (autoScrollBarPos < dragBarPos)
value -= blockIncrement/2;
else
if (autoScrollBarPos > dragBarPos+dragBarSize)
value += blockIncrement/2;
else
{
startDragPos = autoScrollBarPos-dragBarPos; // point inside drag bar
disableAutoScroll();
}
}
public void setEnabled(boolean enabled)
{
if (internalSetEnabled(enabled,false))
{
btnDec.setEnabled(isEnabled() && value > minimum);
btnInc.setEnabled(isEnabled() && value+visibleItems < maximum);
}
}
protected void onColorsChanged(boolean colorsChanged)
{
if (colorsChanged)
{
btnDec.setBackForeColors(backColor,foreColor);
btnInc.setBackForeColors(backColor,foreColor);
}
bColor = Color.brighter(getBackColor());
if (parent != null && bColor == parent.getBackColor())
bColor = Color.getCursorColor(bColor);
Graphics.compute3dColors(isEnabled(),backColor,foreColor,fourColors);
}
public void onPaint(Graphics g)
{
// Draw background and borders
int k = size - (btnWH<<1);
if (k <= 0) return;
g.backColor = bColor;
if (verticalBar)
g.fillRect(0,btnWH,btnWH,k);
else
g.fillRect(btnWH,0,k,btnWH);
g.backColor = backColor;
if (uiFlat)
{
g.foreColor = fourColors[2];
g.drawRect(0,0,width,height);
}
if (verticalBar)
{
if (uiVista && isEnabled()) // guich@573_6
g.fillVistaRect(0,dragBarPos,width,dragBarSize,backColor,startDragPos!=-1,true); // guich@tc110_51: press the bar if dragging.
else
g.fillRect(0,dragBarPos,width,dragBarSize);
g.draw3dRect(0,dragBarPos,width,dragBarSize,uiVista?Graphics.R3D_CHECK:Graphics.R3D_RAISED,false,false,fourColors); // guich@tc110_51: press the bar if dragging.
if (uiFlat || uiVista)
{
g.foreColor = fourColors[2];
k = dragBarPos+(dragBarSize>>1);
g.drawLine(3,k,width-4,k);
if (dragBarSize > minDragBarSize)
{
g.drawLine(3,k-2,width-4,k-2);
g.drawLine(3,k+2,width-4,k+2);
}
}
}
else
{
if (uiVista && isEnabled()) // guich@573_6
g.fillVistaRect(dragBarPos,0,dragBarSize,height,backColor,startDragPos!=-1,false); // guich@tc110_51: press the bar if dragging.
else
g.fillRect(dragBarPos,0,dragBarSize,height);
g.draw3dRect(dragBarPos,0,dragBarSize,height,uiVista?Graphics.R3D_CHECK:Graphics.R3D_RAISED,false,false,fourColors); // guich@tc110_51: press the bar if dragging.
if (uiFlat || uiVista)
{
g.foreColor = fourColors[2];
k = dragBarPos+(dragBarSize>>1);
g.drawLine(k,3,k,height-4);
if (dragBarSize > minDragBarSize)
{
g.drawLine(k-2,3,k-2,height-4);
g.drawLine(k+2,3,k+2,height-4);
}
}
}
}
/** If this is a vertical scroll bar, i strongly suggest you use PREFERRED in your control's width (with small adjustments). */
public int getPreferredWidth()
{
return (verticalBar?btnDec.getPreferredWidth()+extraSize:(btnDec.getPreferredHeight()<<1)) + insets.left+insets.right; // guich@240_18 - guich@300_70
}
/** If this is a horizontal scroll bar, i strongly suggest you use PREFERRED in your control's height (with small adjustments) */
public int getPreferredHeight()
{
return (verticalBar?(btnDec.getPreferredWidth()<<1):btnDec.getPreferredHeight()+extraSize) + insets.top+insets.bottom; // guich@300_70: vertical bar always use width; horizontal always use height
}
/* 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)
{
size = verticalBar?height:width;
btnWH = btnDec.isVisible() ? (verticalBar?width:height) : 0;
btnDec.setRect(0,0,btnWH,btnWH,null,screenChanged);
btnInc.setRect(verticalBar?0:(size-btnWH),verticalBar?(size-btnWH):0,btnWH,btnWH,null,screenChanged);
dragBarPos = dragBarMin;//btnWH;
recomputeParams(false);
}
public void setHighlighting()
{
}
/** Clears this control, setting the value to clearValueInt. */
public void clear() // guich@572_19
{
setValue(clearValueInt);
}
public Control handleGeographicalFocusChangeKeys(KeyEvent ke)
{
requestFocus();
if (verticalBar)
{
if ((!ke.isUpKey() && !ke.isDownKey()) ||
(ke.isUpKey() && value-unitIncrement < minimum) ||
(ke.isDownKey() && value+unitIncrement == maximum) )
return null;
}
else
{
if ((ke.isUpKey() || ke.isDownKey()) ||
(ke.isPrevKey() && value-unitIncrement < minimum) ||
(ke.isNextKey() && value+unitIncrement == maximum))
return null;
}
_onEvent(ke);
return this;
}
public void tempShow()
{
}
}