/*********************************************************************************
* TotalCross Software Development Kit *
* 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.res.Resources;
import totalcross.sys.Settings;
import totalcross.ui.event.*;
import totalcross.ui.gfx.Graphics;
import totalcross.ui.image.Image;
import totalcross.ui.image.ImageException;
/**
* ScrollPosition implements the auto-hide scrollbar that exists in
* finger-touched devices. This special scrollbar is just a small position indicator
* that appears when the area is dragged. The ScrollPosition does not take
* an area of the control, since it appears and disappears automatically.
*
* All Scrollable controls change their ScrollBar by the ScrollPosition when
* Settings.fingerTouch is true.
*
* If the back color and the bar color are the same, the bar is not drawn; this is how
* the ButtonMenu class hides this control.
*
* @see totalcross.sys.Settings#fingerTouch
* @see totalcross.ui.Scrollable
* @see totalcross.ui.UIColors#positionbarColor
* @see totalcross.ui.UIColors#positionbarBackgroundColor
*/
public class ScrollPosition extends ScrollBar implements Scrollable, PenListener, TimerListener
{
private boolean verticalScroll,isPenDown,autoHide = AUTO_HIDE;
private Image npback,handle;
private TimerEvent timer;
private int visibleCount;
/** Set to false to make the PositionBar always show (instead of the default auto-hide behaviour). */
public static boolean AUTO_HIDE = true;
/** By default, the ScrollPosition is shown during 1 second to let the user know that he can scroll.
* This value is a number that will be multiplied by 500. Defaults to 2, set to 0 to disable this feature.
* @since TotalCross 3.07
*/
public static int VISIBLE_COUNT = 2;
/** The bar color. Defaults to UIColors.positionbarColor but can be changed to something else. */
public int barColor = UIColors.positionbarColor;
/** Defines the height multiplier that must be reached to show the handle while scrolling.
* The handle speeds up scrolling since the user can drag it (like the bar in a ProgressBar).
* It is very useful for long lists.
*
* Setting this to 10 will make the handle appear when the height of the item's list exceeds 10 times
* the height of the ScrollContainer.
*
* Set it to 0 will always show the handle, set to something very big (like Convert.MAX_INT) to never show
* the handle.
*
* Defaults to 7.
*
* @since TotalCross 1.3
*/
public static int heightMultiplierToShowHandle = 7;
/** Constructs a vertical ScrollPosition. */
public ScrollPosition()
{
this(VERTICAL);
}
/** Constructs a ScrollPosition with the given orientation.
* @see #VERTICAL
* @see #HORIZONTAL
*/
public ScrollPosition(byte orientation)
{
super(orientation);
btnInc.setVisible(false);
btnDec.setVisible(false);
disableBlockIncrement = true;
enableAutoScroll = false;
tempShow();
}
public void tempShow()
{
visible = true;//!autoHide;
if (autoHide)
{
visibleCount = VISIBLE_COUNT;
if (timer == null)
{
timer = addTimer(500);
addTimerListener(this);
}
Window.needsPaint = true;
}
}
public void onBoundsChanged(boolean b)
{
super.onBoundsChanged(b);
npback = null;
if (parent instanceof Scrollable)
{
Flick f = ((Scrollable)parent).getFlick();
if (f != null)
f.addScrollableListener(this);
parent.addPenListener(this);
}
}
public void onColorsChanged(boolean b)
{
super.onColorsChanged(b);
npback = null;
}
/** Don't allow change the visibility. This is done automatically. */
public void setVisible(boolean b)
{
}
private Image getHandleImage()
{
Image img = null;
try
{
img = Resources.progressHandle.getSmoothScaledInstance(fmH*2,dragBarSize);
img.applyColor(barColor);
}
catch (ImageException e)
{
if (Settings.onJavaSE)
e.printStackTrace();
}
return img;
}
public void onPaint(Graphics g)
{
if (barColor == backColor)
return;
if (UIColors.positionbarBackgroundColor != -1)
{
g.backColor = UIColors.positionbarBackgroundColor;
g.fillRect(0,0,width,height);
}
if (isEnabled() || !autoHide)
{
g.backColor = barColor;
if (uiAndroid)
{
// change to a handle instead of the position bar?
if (verticalBar)
{
if ((Flick.currentFlick != null || startDragPos != -1) && (maximum-minimum) >= heightMultiplierToShowHandle*height)
{
if (dragBarSize == minDragBarSize)
{
thumbSize = fmH*3;
setRect(RIGHT,KEEP,fmH*2,KEEP); // parameters will be recomputed
return;
}
if (handle == null || handle.getHeight() != dragBarSize)
handle = getHandleImage();
if (handle != null)
{
int w = handle.getWidth();
if (this.width != w)
{
thumbSize = fmH*3;
setRect(RIGHT,KEEP,fmH*2,KEEP); // parameters will be recomputed
}
}
}
/* this was disabled due to two problems:
* 1. makes the drag of a ListBox cause a repaint even if the drag is below the height of a line
* 2. makes the ScrollPosition flick on screen
else
if (resetHandle())
return;
*/ }
if (npback == null || ((verticalBar ? npback.getHeight() : npback.getWidth()) != dragBarSize))
try
{
if (verticalBar)
npback = NinePatch.getInstance().getNormalInstance(NinePatch.SCROLLPOSV,width,dragBarSize,barColor,false);
else
npback = NinePatch.getInstance().getNormalInstance(NinePatch.SCROLLPOSH,dragBarSize,height,barColor,false);
}
catch (Exception e) {e.printStackTrace();}
if (isHandle())
NinePatch.tryDrawImage(getGraphics(), handle, 0, dragBarPos); // when the button is pressed, the graphic's clip becomes invalid
else
if (npback != null)
NinePatch.tryDrawImage(g,npback,verticalBar ? 0 : dragBarPos, verticalBar ? dragBarPos : 0);
}
else
{
if (verticalBar)
g.fillRect(0,dragBarPos,width,dragBarSize);
else
g.fillRect(dragBarPos,0,dragBarSize,height);
}
}
}
private boolean isHandle()
{
return handle != null && this.width == handle.getWidth();
}
private boolean resetHandle()
{
boolean ret = false;
int w = getPreferredWidth();
if (verticalBar && this.width != w)
{
ret = true;
thumbSize = 0;
setRect(RIGHT,KEEP,w,KEEP);
}
if (autoHide && visible && Flick.currentFlick == null)
super.setVisible(false);
Window.needsPaint = true;
return ret;
}
public void onEvent(Event e)
{
super.onEvent(e);
switch (e.type)
{
case PenEvent.PEN_DRAG:
if (e.target == this)
{
Event.clearQueue(PenEvent.PEN_DRAG);
penDrag((DragEvent)e);
e.consumed = true;
}
break;
case PenEvent.PEN_UP:
resetHandle();
break;
}
}
public int getPreferredWidth()
{
return verticalBar ? uiAndroid ? Math.max(7,fmH/4) : fmH/4 : fmH;
}
public int getPreferredHeight()
{
return !verticalBar ? uiAndroid ? Math.max(7,fmH/4) : fmH/4 : fmH;
}
public boolean flickStarted()
{
if (!visible && autoHide && verticalBar == verticalScroll)
super.setVisible(true);
return true;
}
public void flickEnded(boolean atPenDown)
{
if (!atPenDown && visible && autoHide && verticalBar == verticalScroll)
super.setVisible(false);
else
if (!autoHide && isHandle())
{
resetHandle();
Window.needsPaint = true;
}
}
// none of these methods are called
public boolean canScrollContent(int direction, Object target)
{
return false;
}
public boolean scrollContent(int xDelta, int yDelta, boolean fromFlick)
{
return false;
}
public int getScrollPosition(int direction)
{
return 0;
}
public Flick getFlick()
{
return null;
}
public void penDown(PenEvent e)
{
isPenDown = true;
Window.topMost.cancelPenUpListeners.addElement(this);
}
public void penUp(PenEvent e)
{
isPenDown = false;
if (Flick.currentFlick == null || e == null)
resetHandle();
try
{
Window.topMost.cancelPenUpListeners.removeElement(this);
}
catch (Exception ee) {}
}
public void penDrag(DragEvent e)
{
verticalScroll = e.direction == DragEvent.DOWN || e.direction == DragEvent.UP;
if (autoHide && !visible && verticalBar == verticalScroll)
super.setVisible(true);
}
public void penDragStart(DragEvent e)
{
}
public void penDragEnd(DragEvent e)
{
isPenDown = false;
if (autoHide && visible && Flick.currentFlick == null)
super.setVisible(false);
}
public void timerTriggered(TimerEvent e)
{
if (timer != null && timer.triggered)
{
if (visible && autoHide && visibleCount >= 0)
{
if (--visibleCount == 0)
{
visible = false;
Window.needsPaint = true;
}
return;
}
if (visible && autoHide && Flick.currentFlick == null && !isPenDown)
resetHandle();
if (!isDisplayed())
{
removeTimer(timer);
removeTimerListener(this);
timer = null;
}
}
}
public boolean wasScrolled()
{
return false;
}
/** Resets position to 0 and posts a pressed event. */
public void clear()
{
super.clear();
postPressedEvent();
}
}