I made this Xlet when I did Xlet programming as I feel there should be something like debug console in STB. So, I find a AWT scrollableText and modify it to be an Xlet. So, I have a debug console that can be shown on big TV screen. I can know what happen in the Xlet in real time and real STB environment. This code is a little bit rough. But it is a handy tool I like. However, I must emphasize to MHP programmer who want to use this. That is, MHP specification does not require MHP environment supports AWT API although many MHP vendor ported a JVM with AWT package into their STB. So, you don't need to be surprised if this code does not work in certain STB. But, I know this will rarely happen. Enjoy it.
/*
* ScrollableText.java
*
* Created on September 22, 2005
*
*/
import java.awt.Font;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Insets;
import java.awt.AWTEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Vector;
import org.havi.ui.*;
/**
* ScrollableText.java
*
* A text component that can function as a stand-alone
* scrollable test displayer. Contains the neccessary
* methods for processing user input.
*
*/
public class ScrollableText extends HComponent{
/** The alignment styles */
public static final int ALIGN_LEFT = 0;
public static final int ALIGN_RIGHT = 1;
public static final int ALIGN_CENTER = 2;
public static final int ALIGN_TOP = 3;
public static final int ALIGN_BOTTOM = 4;
/** the keys used for navigating */
public static final int UP_KEY = KeyEvent.VK_UP;
public static final int DOWN_KEY = KeyEvent.VK_DOWN;
public static final int PGUP_KEY = KeyEvent.VK_LEFT;
public static final int PGDOWN_KEY = KeyEvent.VK_RIGHT;
/** Directional consts */
public static final int DIR_UP = 1;
public static final int DIR_DOWN = 2;
/** Position tokens */
public static final int POS_BEGINNING = 0;
public static final int POS_END = -1;
/** The width of the sidescroller */
public static final int SIDESCROLLER_WIDTH = 20;
/** The sidescroller show- policy */
public static final int SIDESCROLLER_ALWAYS = 0;
public static final int SIDESCROLLER_NEVER = 1;
public static final int SIDESCROLLER_AS_NEEDED = 2;
/** Default color scheme */
private Color SCROLLER_BG = new Color(24, 93, 184);
private Color SCROLLER_TAB_BG = new Color(110, 171, 102);
private Color SCROLLER_ACTIVE_ARROW = new Color(110, 171, 102);
private Color SCROLLER_INACTIVE_ARROW = new Color(24, 93, 184);
private Color SCROLLER_ARROW_OUTLINE = Color.black;
private Color SCROLLER_HIGHLIGHT = Color.white;
private Color SCROLLER_SHADOW = Color.black;
//private Color foreground = Color.black;
//private Color background = Color.white;
/** The sidescroller policy in use */
private int sidescrollerPolicy;
/** Indicates if the sidescroller should be painted */
private boolean sidescroller;
/* The size of the scrollbar */
private int scrollerHeight;
/** The positional multiplier */
private float scrollerUnit;
private static final double[] UP_ARROW_X = { 0.5, 0, 0.3, 0.3, 0.7, 0.7, 1 };
private static final double[] UP_ARROW_Y = { 0, 0.7, 0.7, 1, 1, 0.7, 0.7 };
private static final double[] DOWN_ARROW_X = { 0.3, 0.3, 0, 0.5, 1, 0.7, 0.7 };
private static final double[] DOWN_ARROW_Y = { 0, 0.3, 0.3, 1, 0.3, 0.3, 0 };
private static final int UP_ARROW = 0;
private static final int DOWN_ARROW = 1;
/** Variables related to the Scrollable interface */
private int scrollUnit;
private int scrollBlock;
private int scrollBy;
private int totalHeight;
/** Presentation- related values */
private String text;
private int lineSpace;
/** Margins */
private int rMarg;
private int lMarg;
private int tMarg;
private int bMarg;
/** The alignation style */
private int align;
private int valign;
/** The y- positions of the rows */
private int rowPos[];
/** The x- positions of the individual rows */
private int rowIndent[];
/** The row that is displayed on the top of the area */
private int currentRow;
private int displayRows;
/** The splitup of the text to paint */
private String rows[];
/** Supports transparent background */
private boolean transparent;
/** Creates a new instance of ScrollableText */
public ScrollableText() {
this.enableEvents(AWTEvent.KEY_EVENT_MASK);
this.scrollBy = 1;
}
/**
* The complete constructors.
* Additional information should be set using the default java.awt.Component
* methods; setFont(), setForeground(), setBackground(), setSize(), setLocation();
*
* @param insets The margins
* @param align The horizontal alignment of the text
* @param valign The vertical alignment of the text
* @param sidescroller The sidescroller policy
*/
public ScrollableText(Insets insets, int align, int valign, int sidescroller) {
super();
this.enableEvents(AWTEvent.KEY_EVENT_MASK);
this.align = align;
this.valign = valign;
this.text = text;
this.sidescrollerPolicy = sidescroller;
this.scrollBy = 1;
this.lMarg = insets.left;
this.rMarg = insets.right;
this.tMarg = insets.top;
this.bMarg = insets.bottom;
this.transparent = true;
text = "";
}
/*
public void setForeground(org.dvb.ui.DVBColor foreground){
this.foreground = foreground;
super.setForeground(foreground);
}
public void setBackground(org.dvb.ui.DVBColor background){
this.background = background;
super.setBackground(background);
}
*/
/**
* Changes the text within this component
* @param text The new contents
*/
public void setText(String text) {
this.currentRow = 0;
this.text = text;
int fontSize = 0;
Font font = this.getFont();
int width = this.getSize().width;
int height = this.getSize().height;
//FontMetrics fm = this.getFontMetrics(new Font("SansSerif", Font.PLAIN, 24) );
FontMetrics fm = null;
if (font != null) {
fm = this.getFontMetrics(font);
fontSize = font.getSize();
if (this.sidescrollerPolicy != SIDESCROLLER_NEVER)
this.rows = convertText(text, width - (this.rMarg + this.lMarg +
SIDESCROLLER_WIDTH),
fm);
else
this.rows = convertText(text, width - (this.rMarg + this.lMarg), fm);
} else {
this.rows = new String[1];
this.rows[0] = text;
}
if (fm != null)
this.lineSpace = fm.getDescent();
else
this.lineSpace = (int)(fontSize/4);
this.totalHeight = (fontSize + this.lineSpace) * this.rows.length;
this.scrollBlock = fontSize + this.lineSpace;
this.scrollUnit = fontSize + this.lineSpace;
int usableHeight = height - this.tMarg - this.bMarg;
if ((usableHeight < 1) || (this.scrollBlock < 1)) {
this.displayRows = 0;
} else {
this.displayRows = usableHeight / this.scrollBlock;
if (this.displayRows > this.rows.length)
this.displayRows = this.rows.length;
/* vertical alignment */
int valignOfs = 0;
if (this.valign != ALIGN_TOP) {
valignOfs = height - this.displayRows * this.scrollBlock;
/* valignOfs is currently suitable for ALIGN_BOTTOM */
if (this.valign == ALIGN_CENTER)
valignOfs = (int)(valignOfs/2);
}
/* Calculate the positions of the rows beforehand */
this.rowPos = new int[ this.displayRows ];
int posCount = fm.getAscent() + this.tMarg + valignOfs;
for(int i = 0; i < this.displayRows; i++) {
this.rowPos[i] = posCount;
posCount += this.scrollBlock;
}
}
float sHeight = height - (2 * SIDESCROLLER_WIDTH) - 2;
/* Calculate sidescroller data */
if (this.rows.length <= this.displayRows) {
this.scrollerUnit = 0;
this.scrollerHeight = (int)sHeight;
} else {
this.scrollerUnit = (sHeight / (float)this.rows.length);
this.scrollerHeight = Math.round(((float)this.displayRows * this.scrollerUnit));
}
/* Make sure the scroller has some size */
if (this.scrollerHeight < 3)
this.scrollerHeight = 4;
switch(this.sidescrollerPolicy) {
case SIDESCROLLER_ALWAYS:
this.sidescroller = true;
break;
case SIDESCROLLER_NEVER:
this.sidescroller = false;
break;
case SIDESCROLLER_AS_NEEDED:
default:
if (this.scrollerUnit > 0)
this.sidescroller = true;
else
this.sidescroller = false;
}
}
/**
* Returns the start of a line if using the set alignation style
* and the given width
*/
private int getIndent(int strWidth, int width) {
switch(this.align) {
case ALIGN_RIGHT:
return width - strWidth;
case ALIGN_CENTER:
return (width - strWidth) / 2;
default:
return 0;
}
}
/**
* Small method to add a string to one Vector and it's
* indentation information to the other
*/
private void addStr(String str, Vector strings, Vector indents,
FontMetrics fm, int width) {
strings.addElement(str);
indents.addElement(new Integer(getIndent(fm.stringWidth(str), width)));
}
/**
* Converts the given string into an array of String with each entry
* having the width of width or less.
*
*/
private String[] convertText(String text, int width, FontMetrics fm) {
/* Check one line at a time, add it to the final */
Vector al = new Vector();
Vector inList = new Vector();
String tmp = "";
for(int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
/* Check for control chars */
switch(ch) {
case '\n':
addStr(tmp, al, inList, fm, width);
tmp = "";
break;
case '\t':
tmp += " ";
break;
default:
tmp += ch;
try {
if (fm.stringWidth(tmp) > width) {
int pos = ScrollableText.getBreakPos(tmp);
if (pos > 0) {
addStr(tmp.substring(0, pos), al, inList, fm, width);
tmp = tmp.substring(pos+1, tmp.length());
} else {
/* No breaking possible; remove last character */
addStr(tmp.substring(0, tmp.length()-1), al, inList, fm, width);
tmp = tmp.charAt(tmp.length()-1) + "";
}
}
} catch(Exception e) {
System.out.println("ScrollableText.convertText exception: " + e);
e.printStackTrace(System.out);
String ret[] = new String[1];
ret[0] = text;
return ret;
}
}
}
if (tmp.length() > 0)
addStr(tmp, al, inList, fm, width);
String[] ret = new String[ al.size() ];
rowIndent = new int[ inList.size() ];
for(int i = 0; i < al.size(); i++) {
ret[i] = (String)al.elementAt(i);
rowIndent[i] = ((Integer)inList.elementAt(i)).intValue();
}
return ret;
}
/**
* Returns the position where the given String can be
* broken up according to spaces and tabs, starting from
* the end of the String.
* If such is not found before a newline or at all, the
* resulting value will be negative. If found, the resulting
* value is the position of the breaking character, which
* can safely be substituted for a newline, eg.
* String new = old.substring(0, ret_value) + '\n'
* + old.substring(ret_value+1, old.length());
*
*/
private static int getBreakPos(String str) {
for(int i = str.length() - 1; (i > -1) && (str.charAt(i) != '\n'); i--) {
if ((str.charAt(i) == ' ') ||
(str.charAt(i) == '\t')) {
/* A suitable position is found */
return i;
}
}
/* Not found */
return -1;
}
/**
* Changes the size of this component
*/
public void setSize(int width, int height) {
super.setSize(width, height);
setText(this.text);
}
/**
* Changes the size of this component
*/
public void setSize(Dimension dim) {
this.setSize(dim.width, dim.height);
}
/**
* Changes the font of this component
*/
public void setFont(Font font) {
super.setFont(font);
this.setText(this.text);
}
/**
* Sets the background of the text - supports transparent
* by providing null
*/
public void setBackground(Color bg) {
super.setBackground(bg);
this.transparent = (bg == null);
}
/**
* Sets the color scheme for the sidescroller
*
*/
public void setScrollerColors(Color bg, Color tabBg,
Color activeArrow, Color inactiveArrow,
Color arrowOutline, Color highlight,
Color shadow) {
SCROLLER_BG = bg;
SCROLLER_TAB_BG = tabBg;
SCROLLER_ACTIVE_ARROW = activeArrow;
SCROLLER_INACTIVE_ARROW = inactiveArrow;
SCROLLER_ARROW_OUTLINE = arrowOutline;
SCROLLER_HIGHLIGHT = highlight;
SCROLLER_SHADOW = shadow;
this.repaint();
}
/**
* Changes multiple properties of this component
*
* @param text The text to be contained within the
* component. If null, the old text will
* be preserved.
*/
public void setPresentation(int x, int y, int width, int height,
int align, int valign, String text) {
this.setBounds(x, y, width, height);
this.align = align;
this.valign = valign;
if (text != null)
this.text = text;
setText(this.text);
}
/**
* Changes the horizontal alignment of this component
*
* @param alignment One of the Horizontal alignment
* constants; ALIGN_LEFT ALIGN_RIGHT ALIGN_CENTER
*/
public void setHorizontalAlignment(int alignment) {
this.align = alignment;
setText(this.text);
}
/**
* Changes the vertical alignment of this component
* @param alignment One of the Vertical alignment
* constants; ALIGN_TOP ALIGN_BOTTOM ALIGN_CENTER
*/
public void setVerticalAlignment(int alignment) {
this.valign = alignment;
setText(this.text);
}
/**
* Sets the number or rows the component should
* be scrolled by with each scroll
* @param rows The number of rows
*/
public void setScrollRows(int rows) {
this.scrollUnit = rows * this.scrollBlock;
this.scrollBy = rows;
}
/**
* Scrolls the component one scrollunit in the
* given direction
* @param direction The direction; DIR_UP, DIR_DOWN
*/
public void scrollContents(int direction) {
if (direction == DIR_DOWN)
this.currentRow -= this.scrollBy;
else
this.currentRow += scrollBy;
checkState();
}
/**
* Scrolls the component one visible page (if available)
* in the given direction
* @param direction The direction of the scroll
*/
public void scrollPage(int direction) {
if (direction == DIR_DOWN)
this.currentRow -= this.displayRows;
else
this.currentRow += this.displayRows;
checkState();
}
/**
* Jumps to the given position (in rows) within
* the contents.
* @param position The position, or a special code:
* POS_BEGINNING, POS_END
*/
public void jumpTo(int position) {
switch(position) {
case POS_BEGINNING:
this.currentRow = 0;
break;
case POS_END:
this.currentRow = this.rows.length;
break;
default:
this.currentRow = position;
}
checkState();
}
/**
* Returns the total number of rows contained within
* the object
* @return The total number of rows
*/
public int getTotalScrollBlocks() {
return this.rows.length;
}
/**
* Returns the total number of rows that fits
* within the components boundaries
* @return The number of visible rows
*/
public int getVisibleScrollBlocks() {
return this.displayRows;
}
/**
* Returns the number of hidden rows above the
* visible area
* @return The number of not- visible, scrolled by rows
*/
public int getHiddenTopScrollBlocks() {
return this.currentRow;
}
/**
* Returns the number of hidden rows below the
* visible area
* @return The number of hidden, unviewed rows
*/
public int getHiddenBottomScrollBlocks() {
int ret = this.rows.length - this.displayRows - this.currentRow;
if (ret < 0)
ret = 0;
return ret;
}
/**
* Method that does sanity- checking on presentation- related
* conditions before calling paint
*/
private void checkState() {
/* Check position */
if (this.currentRow > (this.rows.length - this.displayRows))
this.currentRow = this.rows.length - this.displayRows;
if (this.currentRow < 0)
this.currentRow = 0;
this.repaint();
}
/**
* Paints the text
*/
public void paint(Graphics g) {
if (this.isVisible()) {
if (!this.transparent) {
g.setColor(this.getBackground());
g.fillRect(0, 0, this.getSize().width, this.getSize().height);
}
g.setFont(this.getFont());
//this.setForeground(Color.b)
g.setColor(this.getForeground());
//g.setColor(Color.BLACK);
for(int i = 0; i < this.displayRows; i++) {
g.drawString(this.rows[i+this.currentRow],
this.lMarg + this.rowIndent[this.currentRow+i], this.rowPos[i]);
}
if (this.sidescroller) {
drawScroller(g);
}
}
}
/**
* Draws the scrollbar
*/
private void drawScroller(Graphics g) {
int width = this.getSize().width;
int height = this.getSize().height;
int startx = width - SIDESCROLLER_WIDTH;
int barSY = SIDESCROLLER_WIDTH + 2 + (int)((float)this.currentRow * this.scrollerUnit);
int barEY = barSY + this.scrollerHeight-2;
/* Make sure the scrollbar has some height, and is within the boundaries */
int dfix = barEY - (height - SIDESCROLLER_WIDTH - 2);
if (dfix > 0) {
if ((barSY - dfix) < SIDESCROLLER_WIDTH)
barSY = SIDESCROLLER_WIDTH +1;
else
barSY = barSY - dfix;
barEY = barEY - dfix;
}
g.setColor(SCROLLER_BG);
g.fillRect(startx, 0, this.SIDESCROLLER_WIDTH, height);
g.setColor(SCROLLER_TAB_BG);
g.fillRect(startx+1, barSY, SIDESCROLLER_WIDTH - 2, (barEY-barSY));
g.setColor(SCROLLER_SHADOW);
g.drawLine(startx, this.SIDESCROLLER_WIDTH + 1, width-1, this.SIDESCROLLER_WIDTH + 1);
g.drawLine(startx, this.SIDESCROLLER_WIDTH + 1, startx, height - this.SIDESCROLLER_WIDTH - 1);
g.drawLine(width-2, barSY+1, width-2, barEY);
g.drawLine(startx+1, barEY, width-2, barEY);
g.setColor(SCROLLER_HIGHLIGHT);
g.drawLine(width-1, this.SIDESCROLLER_WIDTH + 1,
width-1, height - this.SIDESCROLLER_WIDTH - 1 );
g.drawLine(startx, height - this.SIDESCROLLER_WIDTH - 1,
width-1, height - this.SIDESCROLLER_WIDTH - 1);
g.drawLine(startx+1, barSY, width-2, barSY);
g.drawLine(startx+1, barSY, startx+1, barEY);
if (getHiddenTopScrollBlocks() > 0) {
drawArrow(UP_ARROW, startx, 0,
SIDESCROLLER_WIDTH, SIDESCROLLER_WIDTH, SCROLLER_ACTIVE_ARROW,
g);
} else {
drawArrow(UP_ARROW, startx, 0,
SIDESCROLLER_WIDTH, SIDESCROLLER_WIDTH, SCROLLER_INACTIVE_ARROW,
g);
}
if (getHiddenBottomScrollBlocks() > 0) {
drawArrow(DOWN_ARROW, startx, height - SIDESCROLLER_WIDTH,
SIDESCROLLER_WIDTH, SIDESCROLLER_WIDTH, SCROLLER_ACTIVE_ARROW,
g);
} else {
drawArrow(DOWN_ARROW, startx, height - SIDESCROLLER_WIDTH,
SIDESCROLLER_WIDTH, SIDESCROLLER_WIDTH, SCROLLER_INACTIVE_ARROW,
g);
}
}
/**
* Draws an arrow in the given direction
*
*/
private void drawArrow(int arrow, int x, int y, int width, int length,
Color color, Graphics g) {
int[] realX = new int[ 7 ];
int[] realY = new int[ 7 ];
double[] srcX;
double[] srcY;
switch(arrow){
case UP_ARROW:
srcX = UP_ARROW_X;
srcY = UP_ARROW_Y;
break;
case DOWN_ARROW:
default:
srcX = DOWN_ARROW_X;
srcY = DOWN_ARROW_Y;
break;
}
for(int i = 0; i < srcX.length; i++) {
realX[i] = x + (int)(width * srcX[i]);
}
for(int i = 0; i < srcY.length; i++) {
realY[i] = y + (int)(length * srcY[i]);
}
g.setColor(color);
g.fillPolygon(realX, realY, realX.length);
/* draw outline */
g.setColor(SCROLLER_ARROW_OUTLINE);
g.drawPolygon(realX, realY, realX.length);
}
/**
* Declears the scrollpanel to be focusable
*
*/
public boolean isFocusable() {
return true;
}
/**
* Handles user input
*/
public void processKeyEvent(KeyEvent e) {
if (this.isVisible()) {
switch(e.getKeyCode()) {
case UP_KEY:
this.scrollContents(DIR_DOWN);
break;
case DOWN_KEY:
this.scrollContents(DIR_UP);
break;
case PGUP_KEY:
this.scrollPage(DIR_DOWN);
break;
case PGDOWN_KEY:
this.scrollPage(DIR_UP);
break;
default:
}
}
}
}