Wednesday, November 18, 2009

A simple Xlet console for debugging log

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:
            }
            
        }
    }
    
}

No comments:

Post a Comment