/* License * * Copyright 1994-2004 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistribution of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistribution in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MICROSYSTEMS, INC. ("SUN") * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed, licensed or intended * for use in the design, construction, operation or maintenance of any * nuclear facility. */ import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import java.util.Vector; import java.util.Enumeration; /** * A simple outlining midlet. */ public class Outliner extends MIDlet implements CommandListener, ItemCommandListener { private static Display DISPLAY; private static Command exitCommand, editCommand, insertCommand, indentCommand, outdentCommand, expandCommand, collapseCommand, deleteCommand, okCommand, cancelCommand, upCommand, downCommand; private Form form; private TextBox textBox; private OutlineItem editedItem; // user-visible strings private final static String EXIT = "Exit"; private final static String EDIT = "Edit"; private final static String UP = "Move Up"; private final static String DOWN = "Move Down"; private final static String INDENT = "Indent"; private final static String OUTDENT = "Outdent"; private final static String EXPAND = "Expand"; private final static String COLLAPSE = "Collapse"; private final static String INSERT = "Insert"; private final static String DELETE = "Delete"; private final static String OK = "OK"; private final static String CANCEL = "Cancel"; // font for rendering items public static Font FONT = Font.getFont( Font.FONT_STATIC_TEXT ); public static int FONT_HEIGHT = Math.max( FONT.getHeight(), 9 ); public Outliner() { DISPLAY = null; editedItem = null; exitCommand = new Command( EXIT, Command.EXIT, 0 ); okCommand = new Command( OK, Command.OK, 1 ); cancelCommand = new Command( CANCEL, Command.CANCEL, 2 ); editCommand = new Command( EDIT, Command.ITEM, 1 ); insertCommand = new Command( INSERT, Command.ITEM, 2 ); indentCommand = new Command( INDENT, Command.ITEM, 3 ); outdentCommand = new Command( OUTDENT, Command.ITEM, 4 ); upCommand = new Command( UP, Command.ITEM, 5 ); downCommand = new Command( DOWN, Command.ITEM, 6 ); expandCommand = new Command( EXPAND, Command.ITEM, 7 ); collapseCommand = new Command( COLLAPSE, Command.ITEM, 8 ); deleteCommand = new Command( DELETE, Command.ITEM, 9 ); // set up outline form form = new Form( "Outliner" ); form.addCommand( exitCommand ); form.setCommandListener( this ); // setup input form textBox = new TextBox( null, "", 255, TextField.ANY ); textBox.addCommand( okCommand ); textBox.addCommand( cancelCommand ); textBox.setCommandListener( this ); OutlineItem item; item = new OutlineItem( 0, "This is an outline" ); item.setItemCommandListener( this ); item.appendToForm( form ); item = new OutlineItem( 1, "This is a nested item" ); item.setItemCommandListener( this ); item.appendToForm( form ); item = new OutlineItem( 2, "This is a double-nested item" ); item.setItemCommandListener( this ); item.appendToForm( form ); item = new OutlineItem( 1, "This is another nested item" ); item.setItemCommandListener( this ); item.appendToForm( form ); } public void startApp() { if ( DISPLAY == null ) { DISPLAY = Display.getDisplay( this ); DISPLAY.setCurrent( form ); } } public void pauseApp() { } public void destroyApp( boolean unconditional ) { } public void onShowInput() { if ( editedItem != null ) { textBox.setString( editedItem.getString() ); } DISPLAY.setCurrent( textBox ); } public void onDoInput() { if ( editedItem != null ) { editedItem.setString( textBox.getString() ); editedItem = null; } } public void onHideInput() { DISPLAY.setCurrent( form ); textBox.setString( "" ); // clear last command if any editedItem = null; } // interface CommandListener public void commandAction( Command aCommand, Displayable aDisplayable ) { if ( aCommand == exitCommand ) { destroyApp( true ); notifyDestroyed(); } else if ( aCommand == okCommand ) { onDoInput(); onHideInput(); } else if ( aCommand == cancelCommand ) { onHideInput(); } } // interface ItemCommandListener public void commandAction( Command aCommand, Item anItem ) { OutlineItem item = (OutlineItem) anItem; if ( aCommand == expandCommand ) { item.expand(); } else if ( aCommand == collapseCommand ) { item.collapse(); } else if ( aCommand == indentCommand ) { item.indent(); } else if ( aCommand == outdentCommand ) { item.outdent(); } else if ( aCommand == upCommand ) { item.moveUp(); } else if ( aCommand == downCommand ) { item.moveDown(); } else if ( aCommand == editCommand ) { editedItem = item; onShowInput(); } else if ( aCommand == insertCommand ) { OutlineItem newItem = new OutlineItem( item.indent, "" ); newItem.setItemCommandListener( this ); newItem.insertAfterItem( form, item ); } else if ( aCommand == deleteCommand ) { item.removeFromForm(); } } public static class OutlineItem extends CustomItem { /** * The number of pixels to shift for each level of indent. */ private static final int INDENT_MARGIN = 8; /** * The number of levels to indent. */ private int indent; /** * The text to display when painted. */ private String text; /** * The parent form of this item. Needed to * perform expand and collapse operations. */ private Form parentForm; /** * A list of OutlineItems that are collapsed under this one. * If this item is expanded, this vector is null. */ private Vector children; /** * The traversing item. */ private static OutlineItem traversingItem; /** * Creates an OutlineItem with the specified initial indent and text. */ public OutlineItem( int inIndent, String inText ) { // we don't want a system-supplied label super( null ); indent = inIndent; text = inText; children = null; // define layout constraitns setLayout( LAYOUT_2 | LAYOUT_LEFT | LAYOUT_TOP | LAYOUT_EXPAND | LAYOUT_NEWLINE_AFTER ); // add the commands that always apply addCommand( editCommand ); addCommand( insertCommand ); } /** * Returns the indent for this item. */ public int getIndent() { return indent; } /** * Sets the indent for this item. */ public void setIndent( int inIndent ) { indent = inIndent; updateCommands(); } /** * Returns the text for this item. */ public String getString() { return text; } /** * Sets the text for this item. */ public void setString( String inText ) { text = inText; invalidate(); } /** * Add this item to the end of the form. */ public void appendToForm( Form inForm ) { insertBeforeItem( inForm, null ); } /** * Add this item to the form before the specified item. */ public void insertBeforeItem( Form inForm, OutlineItem inItem ) { if ( parentForm != null ) { removeFromForm(); } parentForm = inForm; int i; int size = parentForm.size(); for ( i = 0; i < size; i++ ) { if ( parentForm.get( i ) == inItem ) { break; } } parentForm.insert( i, this ); if ( inItem != null ) inItem.updateCommands(); updateCommands(); } /** * Add this item to the form before the specified item. */ public void insertAfterItem( Form inForm, OutlineItem inItem ) { if ( parentForm != null ) { removeFromForm(); } parentForm = inForm; int i; int size = parentForm.size(); for ( i = 0; i < size; i++ ) { if ( parentForm.get( i ) == inItem ) { break; } } parentForm.insert( i+1, this ); if ( inItem != null ) inItem.updateCommands(); updateCommands(); } /** * Remove this item from the form. */ public void removeFromForm() { int size = parentForm.size(); for ( int i = 0; i < size; i++ ) { if ( parentForm.get( i ) == this ) { parentForm.delete( i ); // delete this break; } } } /** * Returns the index of this item on the form. * Returns -1 if the item is not on the form. */ public int getIndex() { if ( parentForm != null ) { int size = parentForm.size(); for ( int i = 0; i < size; i++ ) { if ( parentForm.get( i ) == this ) { return i; } } } return -1; } /** * Returns whether this item can be indented further. */ public boolean isIndentable() { int index = getIndex(); // root cannot be indented if ( index == 0 ) return false; OutlineItem parent = (OutlineItem)parentForm.get( index - 1 ); if ( this.indent < parent.indent ) return true; return ( !parent.isCollapsed() && this.indent <= parent.indent); } /** * Indent the specified node by one unit. */ public void indent() { indentChildren(); setIndent( indent+1 ); } private void indentChildren() { if ( isCollapsed() ) { Enumeration e = getHiddenChildren().elements(); while ( e.hasMoreElements() ) { ((OutlineItem)e.nextElement()).indent++; } } else { OutlineItem item; for ( int index = getIndex() + 1; index < parentForm.size(); index++ ) { item = (OutlineItem) parentForm.get( index ); if ( item.getIndent() > getIndent() ) // our indent hasn't changed yet { item.indent++; } else // end of children { break; } } } } /** * Returns whether this item can be outdented further. */ public boolean isOutdentable() { return ( indent > 0 ); } /** * Outdent the specified node by one unit. */ public void outdent() { outdentChildren(); setIndent( indent-1 ); } private void outdentChildren() { if ( isCollapsed() ) { Enumeration e = getHiddenChildren().elements(); while ( e.hasMoreElements() ) { ((OutlineItem)e.nextElement()).indent--; } } else { OutlineItem item; for ( int index = getIndex() + 1; index < parentForm.size(); index++ ) { item = (OutlineItem) parentForm.get( index ); if ( item.getIndent() > getIndent() ) // our indent hasn't changed yet { item.indent--; } else // end of children { break; } } } } /** * Returns whether this item can be moved up. */ public boolean canMoveUp() { if ( parentForm == null ) return false; OutlineItem item; int index = getIndex(); for ( int i = getIndex(); i > 0; i = i-1 ) { item = (OutlineItem) parentForm.get( i ); if ( item.indent == this.indent ) { return true; } if ( item.indent < this.indent ) { return false; } } return false; } /** * Outdent the specified node by one unit. */ public void moveUp() { OutlineItem item; for ( int i = getIndex()-1; i > 0; i = i-1 ) { item = (OutlineItem) parentForm.get( i ); if ( item.indent == this.indent ) { // collapse children before moving node Form f = this.parentForm; boolean collapsed = this.isCollapsed(); if ( !collapsed ) this.collapse(); this.removeFromForm(); this.insertBeforeItem( f, item ); if ( !collapsed ) this.expand(); break; } if ( item.indent < this.indent ) { break; } } updateCommands(); } /** * Returns whether this item can be moved down. */ public boolean canMoveDown() { if ( parentForm == null ) return false; OutlineItem item; int size = parentForm.size(); for ( int i = getIndex(); i < size-1; i = i+1 ) { item = (OutlineItem) parentForm.get( i ); if ( item.indent == this.indent ) { return true; } if ( item.indent < this.indent ) { return false; } } return false; } /** * Outdent the specified node by one unit. */ public void moveDown() { OutlineItem item; int size = parentForm.size(); for ( int i = getIndex(); i < size; i = i+1 ) { item = (OutlineItem) parentForm.get( i ); if ( item.indent == this.indent ) { i = i+1; Form f = this.parentForm; boolean collapsed = this.isCollapsed(); if ( !collapsed ) this.collapse(); if ( i > size ) { this.removeFromForm(); this.appendToForm( f ); } else { // collapse children before moving node item = (OutlineItem) parentForm.get( i ); this.removeFromForm(); boolean targetCollapsed = item.isCollapsed(); if ( !targetCollapsed ) item.collapse(); this.insertAfterItem( f, item ); if ( !targetCollapsed ) item.expand(); } if ( !collapsed ) this.expand(); break; } } updateCommands(); } /** * Returns whether this item has hidden children. */ public boolean isCollapsed() { return ( children != null ); } /** * Hide all subsequent items with greater indent * and add them to the hidden children list. */ public void collapse() { if ( children != null ) return; Vector hidden = new Vector(); for ( int i = 0; i < parentForm.size(); i++ ) { if ( parentForm.get( i ) == this ) { OutlineItem item; i = i + 1; while ( i < parentForm.size() ) { item = (OutlineItem)parentForm.get( i ); if ( item.indent > this.indent ) { parentForm.delete( i ); // delete item hidden.addElement( item ); } else { break; } } break; } } if ( hidden.size() > 0 ) { setHiddenChildren( hidden ); updateCommands(); } } /** * Show this nodes hidden children, if any. */ public void expand() { if ( children == null ) return; int i; int size = parentForm.size(); for ( i = 0; i < size; i++ ) { if ( parentForm.get( i ) == this ) { i = i + 1; break; } } OutlineItem item; Enumeration e = children.elements(); while ( e.hasMoreElements() ) { item = (OutlineItem) e.nextElement(); parentForm.insert( i, item ); // insert item i = i + 1; } setHiddenChildren( null ); updateCommands(); } /** * Returns the children that are currently collapsed * and are therefore not referenced by the parent form. */ protected Vector getHiddenChildren() { return children; } /** * Set the children that are currently collapsed * so that we retain a reference to them when they * are removed from the form. */ protected void setHiddenChildren( Vector inChildren ) { children = inChildren; } private boolean hasPointerPress() { return ( getInteractionModes() & POINTER_PRESS ) != 0; } private boolean hasHorizontalTraversal() { return ( getInteractionModes() & TRAVERSE_HORIZONTAL ) != 0; } private void updateCommands() { removeCommand( expandCommand ); removeCommand( collapseCommand ); if ( !hasPointerPress() ) { // fall back on commands to expand/collapse if ( isCollapsed() ) addCommand( expandCommand ); else addCommand( collapseCommand ); } removeCommand( indentCommand ); removeCommand( outdentCommand ); if ( !hasHorizontalTraversal() ) { // fall back on commands to indent/outdent if ( isIndentable() ) addCommand( indentCommand ); if ( isOutdentable() ) addCommand( outdentCommand ); } removeCommand( upCommand ); removeCommand( downCommand ); if ( canMoveUp() ) addCommand( upCommand ); if ( canMoveDown() ) addCommand( downCommand ); removeCommand( deleteCommand ); if ( getIndex() > 0 ) { // if not root addCommand( deleteCommand ); } // force a repaint if ( parentForm != null ) { invalidate(); //repaint(); } } // event handler overrides /** * Overridden to grab or drop the current item * when the FIRE key is pressed. */ protected void keyPressed( int keyCode ) { if ( getGameAction( keyCode ) == Canvas.FIRE ) { if ( isCollapsed() ) expand(); else collapse(); } } /** * Overridden to expand or collapse an item * if the 'expansion widget' is clicked. * Note that the reference implementation only * delivers this event if the item is "focused". */ protected void pointerPressed( int x, int y ) { // if in widget area if ( x < FONT_HEIGHT ) { if ( isCollapsed() ) expand(); else collapse(); } } /** * Used to indent and outdent the item when possible. */ protected boolean traverse( int dir, int viewportWidth, int viewportHeight, int[] visRect_inout ) { // this flag distinguishes between // traversing INTO this item and // traversing WITHIN this item: if ( traversingItem != this ) { // traversing INTO: mark self and return true traversingItem = this; return true; } // handle traversing WITHIN this item switch ( dir ) { case Canvas.RIGHT: if ( isIndentable() ) { indent(); } return true; case Canvas.LEFT: if ( isOutdentable() ) { outdent(); } return true; case NONE: // do nothing: just a form layout reflow return true; default: // break out } return false; } // implementation of abstract methods public int getMinContentHeight() { return FONT_HEIGHT; } public int getMinContentWidth() { return indent * INDENT_MARGIN + FONT_HEIGHT; // we might not have text to calculate font width } public int getPrefContentWidth( int height ) { return indent * INDENT_MARGIN + FONT.stringWidth( text ) + FONT_HEIGHT; } public int getPrefContentHeight( int width ) { return FONT_HEIGHT; } public void paint( Graphics g, int w, int h ) { // clear all with background color g.setColor( DISPLAY.getColor( DISPLAY.COLOR_BACKGROUND ) ); g.fillRect( 0, 0, w, h ); // now use foreground color for drawing g.setColor( DISPLAY.getColor( DISPLAY.COLOR_FOREGROUND ) ); if ( isCollapsed() ) { // draw a filled circle to represent hidden items g.fillArc( indent * INDENT_MARGIN + 2, 2, FONT_HEIGHT-7, FONT_HEIGHT-7, 0, 360 ); } else { // no hidden items so draw an empty circle g.drawArc( indent * INDENT_MARGIN + 2, 2, FONT_HEIGHT-7, FONT_HEIGHT-7, 0, 360 ); } // draw the text: word-wrap is an exercise for the reader g.drawString( text, indent * INDENT_MARGIN + FONT_HEIGHT, 0, g.TOP | g.LEFT ); } } }