/*
* Copyright � 2008, 2010, Oracle and/or its affiliates. All rights reserved
*/
package com.sun.lwuit.browser;
import com.sun.lwuit.Button;
import com.sun.lwuit.Component;
import com.sun.lwuit.Display;
import com.sun.lwuit.Font;
import com.sun.lwuit.Graphics;
import com.sun.lwuit.Image;
import com.sun.lwuit.Painter;
import com.sun.lwuit.geom.Rectangle;
import com.sun.lwuit.html.DocumentRequestHandler;
import com.sun.lwuit.html.HTMLComponent;
import com.sun.lwuit.html.HTMLElement;
/**
* This extension of HTMLComponent adds a cursor/pointer to allow easier navigation in non-touch screens
*
* @author Ofir Leitner
*/
public class ExtHTMLComponent extends HTMLComponent{
PointerGlassPane pointerPane = null;
Image pointerIcon;
Image pointerIconLink;
ExtHTMLComponent(DocumentRequestHandler handler) {
super(handler);
}
/**
* Checks whether pointer mode is enabled
*
* @return true if pointer mode is on, false otherwise
*/
public boolean isPointerEnabled() {
return (pointerPane!=null);
}
/**
* Toggles pointer mode
*
* @param enable true to enable pointer mode, false to disabled it
*/
public void setPointerEnabled(boolean enable) {
if ((enable) && (pointerPane==null)) {
pointerPane=new PointerGlassPane(this);
if (getComponentForm()!=null) {
pointerPane.y=getScrollY();
getComponentForm().setGlassPane(pointerPane);
getComponentForm().setFocused(this);
}
} else if ((!enable) && (pointerPane!=null)) {
if (getComponentForm()!=null) {
getComponentForm().setGlassPane(null);
if (pointerPane.lastFocused!=null) {
getComponentForm().setFocused(pointerPane.lastFocused);
}
}
pointerPane=null;
}
}
/**
* Sets the icons to use as the pointer in regular mode and when it's over a link
* If the linkIcon is null, the regularIcon will be used instead all the time
* If both icons are null, a default painted triangle icon will be displayed.
*
* @param regularIcon The icon to use for the pointer when it is not hovering above a link
* @param linkIcon The icon to use for the pointer when it is hovering above a link
*/
public void setPointerIcon(Image regularIcon,Image linkIcon) {
pointerIcon=regularIcon;
pointerIconLink=linkIcon;
if (pointerPane!=null) {
repaint();
}
}
/**
* Should be called when the page status is PAGE_DISPLAYED, to enable reinstating the pointer glass pane if it's turned on
*/
public void pageDisplayed() {
if (getComponentForm()!=null) {
if (pointerPane!=null) {
getComponentForm().setGlassPane(pointerPane);
getComponentForm().setFocused(this);
pointerPane.x=0;
pointerPane.y=0;
}
}
}
// ************
// Component initialization and deinitialization
// ************
/**
* Overrides initComponent to reinstate the pointer glass pane when needed
*/
protected void initComponent() {
super.initComponent();
if (pointerPane!=null) {
getComponentForm().setGlassPane(pointerPane);
getComponentForm().setFocused(this);
}
}
/**
* Overrides deinitialize to remove the pointer glass pane
*/
protected void deinitialize() {
super.deinitialize();
if (pointerPane!=null) {
getComponentForm().setGlassPane(null);
}
}
// ************
// Input management - Overriding keys to relay them to the pointer glass pane
// ************
/**
* {@inheritDoc}
*/
public boolean handlesInput() {
return (pointerPane!=null);
}
/**
* {@inheritDoc}
*/
public void keyPressed(int k) {
if (pointerPane!=null) {
int action=Display.getInstance().getGameAction(k);
if (pointerPane.move(action)) {
return;
}
}
super.keyPressed(k);
}
/**
* {@inheritDoc}
*/
public void keyReleased(int k) {
if (pointerPane!=null) {
int action=Display.getInstance().getGameAction(k);
if ((action==Display.GAME_DOWN) || (action==Display.GAME_UP) || (action==Display.GAME_LEFT) || (action==Display.GAME_RIGHT) ||
(action==Display.GAME_FIRE)) {
return;
}
}
super.keyReleased(k);
}
// ************
// Scroll management
// ************
/** The following overrides the various public scroll methodsto update the pointer glass pane that the page was scrolled
* And also to disable animated scrolling which is currently not supported in pointer mode
*/
/**
* {@inheritDoc}
*/
public void scrollPages(int pages, boolean animate) {
super.scrollPages(pages, ((animate) && (pointerPane==null)));
scrollPane();
}
/**
* {@inheritDoc}
*/
public void scrollPixels(int pixels, boolean animate) {
super.scrollPixels(pixels, ((animate) && (pointerPane==null)));
scrollPane();
}
/**
* {@inheritDoc}
*/
public void scrollToElement(HTMLElement element, boolean animate) {
super.scrollToElement(element, ((animate) && (pointerPane==null)));
scrollPane();
}
/**
* Notifies the pointer pane that the scrollY location changed
*/
private void scrollPane() {
if (pointerPane!=null) {
pointerPane.pageScrolled(getScrollY());
}
}
// ************
// The PointerGlassPane inner class
// ************
/**
* PointerGlassPane is a glass pane that draws the pointer and handles its movements, link highlighting and more.
*
* @author Ofir Leitner
*/
class PointerGlassPane implements Painter {
/**
* The default pointer size in pixels
*/
int step=Font.getDefaultFont().charWidth('W'); // The pointer's movement step (determined relatively to the system default font size)
int defaultPointerSize = step;
ExtHTMLComponent htmlC; // The associated ExtHTMLComponent
Component lastFocused; // Last focused component
int x,y; // pointer coordinates
public PointerGlassPane(ExtHTMLComponent cmp) {
this.htmlC=cmp;
}
/**
* Updates the pointer according to a new scrollY poisition
*
* @param newY The new scrollY
*/
void pageScrolled(int newY) {
y=newY;
checkLinks();
}
/**
* Moves the pointer according to the key action (if applicable)
*
* @param action The key action
* @return true if the key was handled by the pointer glass pane, false otherwise
*/
boolean move(int action) {
boolean actionKey=false;
boolean moved=false;
if (action==Display.GAME_LEFT) {
actionKey=true;
if (x>0) {
x=Math.max(0, x-step);
moved=true;
}
} else if (action==Display.GAME_RIGHT) {
actionKey=true;
if (x<Display.getInstance().getDisplayWidth()) {
x=Math.min(Display.getInstance().getDisplayWidth(), x+step);
moved=true;
}
} else if (action==Display.GAME_DOWN) {
actionKey=true;
if (y<htmlC.getPreferredH()) {
y=Math.min(htmlC.getPreferredH(), y+step);
moved=true;
}
htmlC.scrollRectToVisible(x, y, step, step*3, null);
} else if (action==Display.GAME_UP) {
actionKey=true;
if (y>0) {
y=Math.max(0, y-step);
moved=true;
}
htmlC.scrollRectToVisible(x, Math.max(0,y-step*2), step, step, null);
} else if (action==Display.GAME_FIRE) {
actionKey=true;
System.out.println("Pointer FIRE at "+x+","+y);
int cx=htmlC.getAbsoluteX()+x;
int cy=htmlC.getAbsoluteY()+y;
htmlC.getComponentForm().pointerPressed(cx, cy);
htmlC.getComponentForm().pointerReleased(cx,cy);
getComponentForm().setFocused(htmlC);
}
if (moved) {
checkLinks();
}
return actionKey;
}
/**
* Checks if the pointer is hovering over a link and handles focus of the links accordingly
*/
private void checkLinks() {
int cx=htmlC.getAbsoluteX()+x;
int cy=htmlC.getAbsoluteY()+y;
Component cmp=getComponentForm().getComponentAt(cx,cy);
// Note that HTMLLink (which is a Button) can also be non-focusable (aside from the first word) - This relies on an implementation detail which is not ideal
if ((cmp!=null) && ((cmp.isFocusable()) || (cmp instanceof Button))) {
if (cmp!=lastFocused) {
if (lastFocused!=null) {
lastFocused.setFocus(false);
}
cmp.setFocus(true);
lastFocused=cmp;
}
} else if (lastFocused!=null) {
lastFocused.setFocus(false);
lastFocused=null;
}
repaint();
}
public void paint(Graphics g, Rectangle rect) {
int cx=htmlC.getAbsoluteX()+x;
int cy=htmlC.getAbsoluteY()+y;
if (htmlC.pointerIcon!=null) {
if ((lastFocused!=null) && (htmlC.pointerIconLink!=null)) {
g.drawImage(htmlC.pointerIconLink, cx, cy);
} else {
g.drawImage(htmlC.pointerIcon, cx, cy);
}
} else { // default pointer (painted)
if (lastFocused!=null) {
g.setColor(0x555555);
} else {
g.setColor(0xaaaaaa);
}
g.fillTriangle(cx, cy, cx+defaultPointerSize, cy, cx, cy+defaultPointerSize);
g.setColor(0);
g.drawLine(cx, cy, cx+defaultPointerSize, cy);
g.drawLine(cx+defaultPointerSize, cy, cx, cy+defaultPointerSize);
g.drawLine(cx, cy, cx, cy+defaultPointerSize);
}
}
}
}