/*********************************************************************************
* 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.sys.*;
import totalcross.ui.event.*;
import totalcross.ui.gfx.*;
import totalcross.ui.image.*;
/** A control that can show an image bigger than its area and that can be dragged using a pen to show the hidden parts.
Note that, by default, events (and dragging) are disabled. You must call setEventsEnabled to allow dragging.
*/
public class ImageControl extends Control
{
/** The amount to scroll when in penless mode. Defaults to 10. */
public static int scrollValue = 10;
private Image img,img0,imgBack;
private int startX,startY;
private Coord c = new Coord();
private boolean isEventEnabled, canDrag;
/** Set to true to center the image in the control when it is loaded */
public boolean centerImage;
/** The last position used for X and Y. */
public int lastX,lastY;
/** Change this member to set the border color.
* You may also set it to -1 if you don't want a border color.
* Note: starting on TotalCross 3.1, the border is drawn around the color and no longer around the image,
* because it was not working on OpenGL devices.
*/
public int borderColor = -1;
/** Set to true to let the image be dragged beyond container limits.
* Should be false for open gl. */
public boolean allowBeyondLimits;
/** Dumb field to keep compilation compatibility with TC 1 */
public int drawOp;
/** Set to true to enable zooming in open gl devices. */
public boolean hwScale = Settings.isOpenGL || Settings.onJavaSE;
private static final double NOTEMP = Convert.MIN_DOUBLE_VALUE;
/** Temporary values to set the hwScaleW/hwScaleH to during draw. */
public double tempHwScale=NOTEMP;
/** Set to true to scale the image to fit the bounds. */
public boolean scaleToFit;
/** Constructs an ImageControl using the given image. */
public ImageControl(Image img)
{
this();
setImage(img);
}
/** Constructs with no initial image. You must set the image with the setImage method. */
public ImageControl()
{
isEventEnabled = false;
focusTraversable = true;
}
/** Change this to true to enable dragging and events on the image. */
public void setEventsEnabled(boolean enabled)
{
focusTraversable = isEventEnabled = enabled;
}
/** Sets the image to the given one. If the image size is different, you must explicitly call
* setRect again if you want to resize the control.
*/
public void setImage(Image img)
{
this.img = this.img0 = img;
c.x = c.y = lastX = lastY = 0;
tempHwScale=NOTEMP;
// test if it is really loaded.
if (img != null && getImageWidth() > 0)
{
if (scaleToFit)
try
{
if (width < height)
this.img = Settings.enableWindowTransitionEffects ? img0.smoothScaledFixedAspectRatio(this.width,false) : img0.hwScaledFixedAspectRatio(this.width,false);
else
this.img = Settings.enableWindowTransitionEffects ? img0.smoothScaledFixedAspectRatio(this.height,true) : img0.hwScaledFixedAspectRatio(this.height,true);
}
catch (ImageException e)
{
// keep original image
}
if (centerImage)
{
lastX = (width-getImageWidth())/2;
lastY = (height-getImageHeight())/2;
}
}
Window.needsPaint = true;
}
public void onEvent(Event event)
{
if (img == null || !isEventEnabled) // no images found, nothing to do!
return;
PenEvent pe;
switch (event.type)
{
case MultiTouchEvent.SCALE:
if (hwScale)
{
if (tempHwScale == NOTEMP)
tempHwScale = 1;
double step = ((MultiTouchEvent)event).scale;
double newScale = tempHwScale * step;
if (newScale > 0)
{
tempHwScale = newScale;
// always centers on screen
lastX = (width-getImageWidth())/2;
lastY = (height-getImageHeight())/2;
repaintNow();
}
}
break;
case KeyEvent.ACTION_KEY_PRESS:
canDrag = !canDrag;
break;
case KeyEvent.SPECIAL_KEY_PRESS:
KeyEvent ke = (KeyEvent)event;
if (ke.isDownKey())
moveTo(lastX, lastY - scrollValue);
else
if (ke.isUpKey())
moveTo(lastX, lastY + scrollValue);
else
if (ke.key == SpecialKeys.RIGHT)
moveTo(lastX - scrollValue, lastY);
else
if (ke.key == SpecialKeys.LEFT)
moveTo(lastX + scrollValue, lastY);
else
if (ke.isActionKey())
parent.setHighlighting();
break;
case PenEvent.PEN_DOWN:
pe = (PenEvent)event;
startX = pe.x-lastX; // save the start relative to the last point clicked
startY = pe.y-lastY;
break;
case PenEvent.PEN_DRAG:
if (getImageWidth() > this.width || getImageHeight() > this.height || allowBeyondLimits)
{
pe = (PenEvent)event;
if (moveTo(pe.x-startX,pe.y-startY))
pe.consumed = true;
}
break;
case ControlEvent.FOCUS_OUT:
canDrag = false;
break;
}
}
/** Moves to the given coordinates, respecting the current moving policy regarding <i>allowBeyondLimits</i>.
* @return True if the image's position was changed.
*/
public boolean moveTo(int newX, int newY)
{
int lx = lastX;
int ly = lastY;
if (allowBeyondLimits)
{
lastX = newX;
lastY = newY;
}
else
{
if (getImageWidth() > width)
lastX = Math.max(width-getImageWidth(),Math.min(newX, 0)); // don't let it move the image beyond its bounds
if (getImageHeight() > height)
lastY = Math.max(height-getImageHeight(),Math.min(newY,0));
}
if (lx != lastX || ly != lastY)
{
repaintNow();
return true;
}
return false;
}
protected void onBoundsChanged(boolean screenChanged)
{
translateFromOrigin(c);
if (scaleToFit)
try
{
if (width < height)
this.img = Settings.enableWindowTransitionEffects ? img0.smoothScaledFixedAspectRatio(this.width,false) : img0.hwScaledFixedAspectRatio(this.width,false);
else
this.img = Settings.enableWindowTransitionEffects ? img0.smoothScaledFixedAspectRatio(this.height,true) : img0.hwScaledFixedAspectRatio(this.height,true);
}
catch (ImageException e)
{
// keep original image
}
if (centerImage && img != null) // guich@100_1: reset the image's position if bounds changed
{
lastX = (width-getImageWidth())/2;
lastY = (height-getImageHeight())/2;
}
else lastX = lastY = 0;
}
private void fillBack(Graphics g)
{
if (imgBack != null)
g.drawImage(imgBack,0,0, true);
}
public void onPaint(Graphics g)
{
paint(g, true);
}
private void paint(Graphics g, boolean drawBack)
{
g.backColor = isEnabled() ? backColor : Color.interpolate(backColor,parent.backColor);
if (!transparentBackground) // guich@tc115_41
g.fillRect(0,0,width,height);
if (img != null) // images found?
{
double dw = img.hwScaleW, dh = img.hwScaleH;
if (tempHwScale != NOTEMP)
img.hwScaleW = img.hwScaleH = tempHwScale;
if (allowBeyondLimits)
g.drawImage(img, lastX,lastY, true);
else
g.copyRect(img,0,0,img.getWidth(),img.getHeight(),lastX,lastY);
img.hwScaleW = dw; img.hwScaleH = dh;
}
if (drawBack)
fillBack(g);
if (borderColor != -1)
{
g.foreColor = borderColor;
g.drawRect(0,0,width,height);
}
}
/** Returns the image's width; when scaling, returns the scaled width. */
public int getImageWidth()
{
return img == null ? 0 : tempHwScale != NOTEMP ? (int)(img.getWidth()*tempHwScale) : img.getWidth();
}
/** Returns the image's height; when scaling, returns the scaled height. */
public int getImageHeight()
{
return img == null ? 0 : tempHwScale != NOTEMP ? (int)(img.getHeight()*tempHwScale) : img.getHeight();
}
public int getPreferredWidth()
{
return img != null ? Math.min(getImageWidth(),Settings.screenWidth) : imgBack != null ? imgBack.getWidth() : Settings.screenWidth; // guich@tc115_35
}
public int getPreferredHeight()
{
return img != null ? Math.min(getImageHeight(),Settings.screenHeight) : imgBack != null ? imgBack.getHeight() : Settings.screenHeight; // guich@tc115_35
}
/** Returns the current image assigned to this ImageControl. */
public Image getImage()
{
return img;
}
/** Sets the given image as a freezed background of this image control. */
public void setBackground(Image img)
{
imgBack = img;
}
/** Gets an image representing the portion being shown. If all image is being shown,
* returns the currently assigned image. */
public Image getVisibleImage(boolean includeBackground) throws ImageException
{
Rect rImg = new Rect(lastX, lastY, getImageWidth(), getImageHeight());
Rect rArea = getRect();
rArea.x = rArea.y = 0;
Image ret = img;
if (!rArea.contains(rImg.x, rImg.y) || !rArea.contains(rImg.x2(), rImg.y2()))
{
rImg.intersectWith(rArea);
ret = new Image(rImg.width, rImg.height);
Graphics g = ret.getGraphics();
if (!includeBackground) paint(getGraphics(), false); // remove the background image
g.copyRect(this, rImg.x, rImg.y, rImg.width, rImg.height, 0,0);
if (!includeBackground) paint(getGraphics(), true);
}
return ret;
}
public Control handleGeographicalFocusChangeKeys(KeyEvent ke) // guich@tc111_9
{
if (!canDrag)
return null;
if (ke.isDownKey())
moveTo(lastX, lastY - scrollValue);
else
if (ke.isUpKey())
moveTo(lastX, lastY + scrollValue);
else
if (ke.key == SpecialKeys.RIGHT)
moveTo(lastX - scrollValue, lastY);
else
if (ke.key == SpecialKeys.LEFT)
moveTo(lastX + scrollValue, lastY);
return this;
}
}