/* * Copyright � 2008, 2010, Oracle and/or its affiliates. All rights reserved */ package com.sun.lwuit.browser; import com.sun.lwuit.Command; import com.sun.lwuit.Component; import com.sun.lwuit.Dialog; import com.sun.lwuit.Font; import com.sun.lwuit.Form; import com.sun.lwuit.Graphics; import com.sun.lwuit.Label; import com.sun.lwuit.List; import com.sun.lwuit.TextField; import com.sun.lwuit.events.ActionEvent; import com.sun.lwuit.geom.Rectangle; import com.sun.lwuit.html.DocumentRequestHandler; import com.sun.lwuit.html.HTMLCallback; import com.sun.lwuit.html.HTMLComponent; import com.sun.lwuit.layouts.BorderLayout; import java.util.Hashtable; import com.sun.lwuit.Display; import com.sun.lwuit.Image; import com.sun.lwuit.Painter; import com.sun.lwuit.TextArea; import com.sun.lwuit.animations.Animation; import com.sun.lwuit.events.ActionListener; import com.sun.lwuit.html.HTMLElement; import com.sun.lwuit.layouts.BoxLayout; import com.sun.lwuit.list.DefaultListCellRenderer; import com.sun.lwuit.xml.Element; import com.sun.lwuit.plaf.Border; import com.sun.lwuit.plaf.Style; import com.sun.lwuit.plaf.UIManager; import java.io.IOException; import java.util.Vector; import javax.microedition.lcdui.Canvas; /** * Uses an HTMLComponent to create an XHTML-MP 1.0 browser. The BrowserForm i scomposed mainly of a toolbar (BrowserToolbar) and an HTMLComponent. * It also implements the HTMLCallback interface to receive events from the HTMLComponent and change the UI acordingly. * * @author Ofir Leitner */ public class BrowserForm extends Form implements HTMLCallback,ActionListener { /** * Set to 'true' to use bitmap fonts * This can be modified using the JAD property use_bitmap_fonts */ static boolean useBitmapFonts=false; /** * If useBitmapFonts is set to true, the following list of fonts will be loaded * Note that too many fonts can cause memory problems */ static String fonts[] = {"arial.12","arial.12.bold","arial.12.bold.italic","arial.12.italic","arial.14","courier.10", "arial.10","h1.arial.16.bold","small-caps.12"}; /** * Set to 'true' to turn on toolbar sliding animation (Doesn't work too good on some devices) */ static boolean animateToolbar=true; Label titleLabel; Label dummyLabel = new Label(); ExtHTMLComponent htmlC; static Hashtable autoCompleteCache = Storage.getFormData(); LoadingGlassPane progressPane; HTMLElement scrollTo; /** * This address will be used as the homepage and as the first page accessed * This can be modified using the JAD property homepage */ static String PAGE_HOME = "file:///index.html"; /** * The address of the help file */ static final String PAGE_HELP = "file:///help.html"; BrowserToolbar toolBar; Command exitCmd=new Command("Exit"); Command helpCmd=new Command("Help"); Command clearCmd=new Command("Clear Cache"); Command toolbarCmd=new Command("Toggle Navbar"); Command backCmd=new Command("Back"); Command forwardCmd=new Command("Forward"); Command stopCmd=new Command("Stop"); Command homeCmd=new Command("Home"); Command refreshCmd=new Command("Refresh"); Command imagesCmd=new Command("Toggle Images"); Command toggleCSSCmd=new Command("Toggle CSS"); Command searchCmd=new Command("Search"); Command pointerCmd=new Command("Toggle Pointer"); /** * If true the navigation bar will be shown on application start. * This can be modified using the JAD property navbar_display */ static boolean displayNavBarOnInit = true; /** * If true the navigation bar will be either shown or not shown (according to displayNavBarOnInit) without the option to toggle it. * This can be modified using the JAD property navbar_lock */ static boolean lockNavBar = false; /** * The menu font name, if null then a proportional,plain,medium system font will be used. * This can be modified using the JAD property menu_font */ static String menuFontName; boolean showImages=true; boolean loadCSS=true; BrowserMIDlet midlet; DocumentRequestHandler handler; public BrowserForm(BrowserMIDlet midlet) { this.midlet=midlet; setMenuCellRenderer(new DefaultListCellRenderer(false)); setCyclicFocus(false); setMenuFont(); HTMLComponent.addSpecialKey("send", 'z'); setLayout(new BorderLayout()); setScrollable(false); // The HTMLComponent itself will be scrollable, not the form setScrollableX(false); // The HTMLComponent itself will be scrollable, not the form // Creating the HTMLComponent and its handler handler=new HttpRequestHandler(); htmlC = new ExtHTMLComponent(handler); htmlC.getStyle().setPadding(0, 0, 3, 3); // Following commented code enables pointer mode by default and loads icons (if they exist) //htmlC.setPointerEnabled(true); /*try { htmlC.setPointerIcon(Image.createImage("/icon1.png"), Image.createImage("/icon2.png")); } catch (IOException ex) { ex.printStackTrace(); }*/ htmlC.setHTMLCallback(this); // If using bitmap fonts, load the fonts and make the first the default if (useBitmapFonts) { for(int i=0;i<fonts.length;i++) { System.out.println("Loading font "+fonts[i]); HTMLComponent.addFont(fonts[i], Font.getBitmapFont(fonts[i])); } htmlC.setDefaultFont(fonts[0], Font.getBitmapFont(fonts[0])); } // Creating the toolbar and editing it to the upper segment of the form toolBar=new BrowserToolbar(htmlC,this); toolBar.setHomePage(PAGE_HOME); if (displayNavBarOnInit) { addComponent(BorderLayout.NORTH, toolBar); } addComponent(BorderLayout.CENTER,htmlC); // Creation and setup of the title titleLabel = new Label("Loading..."); titleLabel.setUnselectedStyle(getTitleStyle()); titleLabel.setText(htmlC.getTitle()); titleLabel.setTickerEnabled(false); setTitleComponent(titleLabel); // Adds the command to the form addCommands(false); // Sets the form as the command listener addCommandListener(this); // Loads the page htmlC.setPage(PAGE_HOME); // See below commented code for examples of setBodyText and setHTML: // Example #1: Set Body without title //htmlC.setBodyText("Testing HTML 123 <a href=\"test\">Test link</a><hr>"); // Example #2: Set Body with title and an active //htmlC.setHTML("Testing HTML 123<br><a href=\"http://m.google.com\">Absolute link - Active</a><br><br><a href=\"test\">Relative link - Inactive</a><hr>",null,"Titletest",false); // Set body with title // Example #3: Set full HTML - relative links will not be active, only absolute ones /*InputStream is=getClass().getResourceAsStream("/index.html"); try { byte[] buf = HttpRequestHandler.getBuffer(is); String body=new String(buf); System.out.println("body="+body); htmlC.setHTML(body,null,null,true); } catch (IOException ex) { ex.printStackTrace(); }*/ } void addCommands(boolean addBack) { removeAllCommands(); if (addBack) { addCommand(backCmd); } addCommand(exitCmd); addCommand(clearCmd); addCommand(helpCmd); addCommand(toolbarCmd); addCommand(stopCmd); addCommand(forwardCmd); addCommand(refreshCmd); addCommand(homeCmd); addCommand(imagesCmd); addCommand(toggleCSSCmd); addCommand(searchCmd); addCommand(pointerCmd); } void setCancelCmdOnly() { removeAllCommands(); addCommand(stopCmd); } private void setMenuFont() { if (menuFontName!=null) { Font menuFont=null; menuFontName=menuFontName.toLowerCase(); if (menuFontName.startsWith("system")) { int size=Font.SIZE_MEDIUM; int style=Font.STYLE_PLAIN; int face=Font.FACE_PROPORTIONAL; menuFontName=menuFontName.substring(6); if (menuFontName.indexOf("small")!=-1) { size=Font.SIZE_SMALL; } else if (menuFontName.indexOf("large")!=-1) { size=Font.SIZE_LARGE; } if (menuFontName.indexOf("bold")!=-1) { style=Font.STYLE_BOLD; } else if (menuFontName.indexOf("italic")!=-1) { style=Font.STYLE_ITALIC; } if (menuFontName.indexOf("system")!=-1) { face=Font.FACE_SYSTEM; } else if (menuFontName.indexOf("mono")!=-1) { face=Font.FACE_MONOSPACE; } menuFont=Font.createSystemFont(face, style, size); } else { menuFont=Font.getBitmapFont(menuFontName); } if (menuFont!=null) { int count=getSoftButtonCount(); for(int i=0;i<count;i++) { Style style=getSoftButton(i).getStyle(); style.setFont(menuFont); } getTitleStyle().setFont(menuFont); } } } /** * Overriden to catch the POUND, STAR and 0 keys that are used as shortcuts * @param keyCode */ public void keyReleased(int keyCode) { super.keyReleased(keyCode); if (!(getFocused() instanceof TextField)) { switch(keyCode) { case Display.KEY_POUND: toolBar.home(); break; case Canvas.KEY_STAR: if (!lockNavBar) { toggleToolbar(); } break; case Canvas.KEY_NUM0: toolBar.stop(); break; case Canvas.KEY_NUM2: // Focus on addressbar setFocused(toolBar.address); break; case Canvas.KEY_NUM9: //Page Down htmlC.scrollPages(1, true); break; case Canvas.KEY_NUM3 : //Page Up htmlC.scrollPages(-1, true); break; case Canvas.KEY_NUM1 : //Home htmlC.scrollPages(-1000, true); break; case Canvas.KEY_NUM7 : //End htmlC.scrollPages(1000, true); break; case Canvas.KEY_NUM4 : //Back toolBar.back(); break; case Canvas.KEY_NUM5 : //Refresh toolBar.refresh(); break; case Canvas.KEY_NUM6 : //Foward toolBar.forward(); break; case Canvas.KEY_NUM8: //Toggle pointer mode htmlC.setPointerEnabled(!htmlC.isPointerEnabled()); break; } } } // HTMLCallback implementation: /** * {@inheritDoc} */ public void titleUpdated(HTMLComponent htmlC, String title) { if ((title==null) || (title.equals(""))) { title="Untitled"; } titleLabel.setText(title); } /** * {@inheritDoc} */ public boolean parsingError(int errorId, String tag, String attribute, String value, String description) { System.out.println(description); return true; } /** * {@inheritDoc} */ public void pageStatusChanged(HTMLComponent htmlC, int phase, String url) { //System.out.println("page status="+phase); if (phase==HTMLCallback.STATUS_REQUESTED) { titleLabel.setText("Loading..."); toolBar.notifyLoading(); progressPane=new ProgressGlassPane(htmlC); progressPane.installPane(this); } else if ((phase==HTMLCallback.STATUS_DISPLAYED) || //(phase==HTMLCallback.STATUS_COMPLETED) || (phase==HTMLCallback.STATUS_ERROR) || (phase==HTMLCallback.STATUS_CANCELLED)) { if (scrollTo!=null) { htmlC.scrollToElement(scrollTo, true); scrollTo=null; } toolBar.notifyLoadCompleted(url); removeAllCommands(); addCommands(toolBar.navButtons[BrowserToolbar.BTN_BACK].isEnabled()); if (progressPane!=null) { progressPane.uninstallPane(this); progressPane=null; } if (phase==HTMLCallback.STATUS_DISPLAYED) { this.htmlC.pageDisplayed(); } } } /** * {@inheritDoc} */ public String fieldSubmitted(HTMLComponent htmlC, TextArea ta, String actionURL, String id, String value, int type,String errorMsg) { if (errorMsg!=null) { System.out.println("Malformed text"); Dialog.show(UIManager.getInstance().localize("html.format.errortitle", "Format error"), errorMsg, UIManager.getInstance().localize("ok", "OK"), null); htmlC.getComponentForm().scrollComponentToVisible(ta); ta.getUnselectedStyle().setBorder(Border.createLineBorder(2, 0xff0000)); ta.getSelectedStyle().setBorder(Border.createLineBorder(2, 0xff0000)); setFocused(ta); ta.repaint(); return null; } else { // Restore original look&feel in case there was an error before if (ta instanceof TextArea) { ta.setUIID("TextArea"); } else { ta.setUIID("TextField"); } } Hashtable urlCache = (Hashtable)autoCompleteCache.get(actionURL); if (urlCache==null) { urlCache=new Hashtable(); autoCompleteCache.put(actionURL,urlCache); } urlCache.put(id,value); Storage.addFormData(actionURL, id, value); return value; } /** * {@inheritDoc} */ public String getAutoComplete(HTMLComponent htmlC, String actionURL, String id) { if (actionURL==null) { return null; } Hashtable urlCache = (Hashtable)autoCompleteCache.get(actionURL); String auto=null; if (urlCache!=null) { auto=(String)urlCache.get(id); } return auto; //return (String)autoCompleteCache.get(id); } /** * {@inheritDoc} */ public int getLinkProperties(HTMLComponent htmlC, String url) { if (url==null) { System.out.println("NULL URL!!!!"); } String domain=HttpRequestHandler.getDomainForLinks(url); if (domain==null) { return LINK_FORBIDDEN; //unknown protocol } Vector visited=(Vector)HttpRequestHandler.visitedLinks.get(domain); if (visited!=null) { if (visited.contains(url)) { return HTMLCallback.LINK_VISTED; } } return HTMLCallback.LINK_REGULAR; } public void actionPerformed(ActionEvent evt) { Command cmd=evt.getCommand(); if (cmd==imagesCmd) { showImages=!showImages; htmlC.setShowImages(showImages); String status=showImages?"on":"off"; Dialog.show("Images "+status,"Images have been turned "+status,null,Dialog.TYPE_INFO,null,2000); } else if (cmd==searchCmd) { search(); } else if (cmd==toggleCSSCmd) { loadCSS=!loadCSS; htmlC.setIgnoreCSS(!loadCSS); String status=loadCSS?"on":"off"; Dialog.show("CSS "+status,"CSS has been turned "+status,null,Dialog.TYPE_INFO,null,2000); } else if (cmd==pointerCmd) { htmlC.setPointerEnabled(!htmlC.isPointerEnabled()); } else if (cmd==exitCmd) { midlet.saveToRMS(); midlet.notifyDestroyed(); } else if (cmd==helpCmd) { htmlC.setPage(PAGE_HELP); } else if (cmd==backCmd) { toolBar.back(); } else if (cmd==forwardCmd) { toolBar.forward(); } else if (cmd==refreshCmd) { toolBar.refresh(); } else if (cmd==stopCmd) { toolBar.stop(); } else if (cmd==homeCmd) { toolBar.home(); } else if (cmd==toolbarCmd) { toggleToolbar(); } else if (cmd==clearCmd) { HttpRequestHandler.cookies=new Hashtable(); Storage.clearCookies(); Storage.clearFormData(); HttpRequestHandler.visitedLinks=Storage.clearHistory(); } } /** * Performs a search of a user string in the current document */ private void search() { Dialog searchDialog = new Dialog("Search"); TextField searchField =new TextField(); searchDialog.setLayout(new BoxLayout((BoxLayout.Y_AXIS))); searchDialog.addComponent(searchField); Command cancelCmd = new Command("Cancel"); searchDialog.addCommand(cancelCmd); searchDialog.addCommand(searchCmd); Command result=searchDialog.showDialog(); if (result==searchCmd) { HTMLElement doc=htmlC.getDOM(); String searchTerm=searchField.getText().toLowerCase(); Vector texts=doc.getTextDescendants(searchTerm, false, Element.DEPTH_INFINITE); for (int i=0;i<texts.size();i++) { HTMLElement elem=(HTMLElement)texts.elementAt(i); String txt = elem.getText(); System.out.println("txt="+txt); int pos=txt.toLowerCase().indexOf(searchTerm); //if (pos!=-1) { // pos will not be -1 since we searched for texts containing the word String txt2=txt.substring(pos+searchTerm.length()); String space=""; if (txt2.startsWith(" ")) { // Spacing between words doesn't work well when the space is in the beginning of a words segment (Since in HTML all spaces before text are truncated), so we move the space to the element containing the search term space=" "; } HTMLElement elem2 = new HTMLElement(txt2,true); elem.setText(txt.substring(0, pos)); System.out.println("txt="+txt+"("+txt.length()+"), search="+searchTerm+", pos="+pos); HTMLElement termElem = new HTMLElement(txt.substring(pos, pos+searchTerm.length())+space,true); HTMLElement span = new HTMLElement("b"); span.setAttribute("style", "background-color: yellow"); span.addChild(termElem); Element parent=elem.getParent(); int index=parent.getChildIndex(elem); parent.insertChildAt(span, index+1); parent.insertChildAt(elem2, index+2); if (scrollTo==null) { scrollTo=termElem; } //} } htmlC.refreshDOM(); } } private void toggleToolbar() { if (toolBar.getParent()==null) { if (animateToolbar) { toolBar.setPreferredH(0); addComponent(BorderLayout.NORTH, toolBar); toolBar.slideIn(); } else { addComponent(BorderLayout.NORTH, toolBar); setFocused(toolBar); } } else { if (animateToolbar) { toolBar.slideOut(); } else { removeComponent(toolBar); } } revalidate(); } public boolean linkClicked(HTMLComponent htmlC, String url) { System.out.println("Link clicked: "+url); return true; } public void actionPerformed(ActionEvent evt, HTMLComponent htmlC, HTMLElement element) { // do nothing } public void focusGained(Component cmp, HTMLComponent htmlC, HTMLElement element) { // do nothing } public void focusLost(Component cmp, HTMLComponent htmlC, HTMLElement element) { // do nothing } public void selectionChanged(int oldSelected, int newSelected, HTMLComponent htmlC, List list, HTMLElement element) { // do nothing } public void dataChanged(int type, int index, HTMLComponent htmlC, TextField textField, HTMLElement element) { // do nothing } } /** * A simple glass pane with a loading message * * @author Ofir Leitner */ class LoadingGlassPane implements Painter { public void paint(Graphics g, Rectangle rect) { Font font=g.getFont(); int color=g.getColor(); g.setColor(0); g.setFont(Font.getDefaultFont()); g.drawString("Loading...", 20, 120); g.setColor(color); g.setFont(font); } void installPane(Form f) { f.setGlassPane(this); } void uninstallPane(Form f) { f.setGlassPane(null); } } /** * An advanced glass pane with a loading message and lodaing animation * * @author Ofir Leitner */ class ProgressGlassPane extends LoadingGlassPane implements Animation { int spacing = 20; int fontSpacing = 10; String loadMsg="Loading..."; HTMLComponent htmlC; public ProgressGlassPane(HTMLComponent htmlC) { this.htmlC=htmlC; } public void paint(Graphics g, Rectangle rect) { int color=g.getColor(); Font font=g.getFont(); int pos=(int)((System.currentTimeMillis()%2700)/300); Font f=Font.getDefaultFont(); int startX=htmlC.getAbsoluteX()+(htmlC.getWidth()/2)-spacing; int fontStartX=htmlC.getAbsoluteX()+(htmlC.getWidth()-f.stringWidth(loadMsg))/2; int startY=htmlC.getAbsoluteY()+(htmlC.getHeight()/2)-spacing-(f.getHeight()+fontSpacing)/2; int i=0; g.setColor(0xffffff); g.fillRect(Math.min(startX-3,fontStartX), startY-3, Math.max(spacing*2+7,f.stringWidth(loadMsg))+1, spacing*2+7+f.getHeight()+fontSpacing); g.setColor(0); g.setFont(f); g.drawString(loadMsg, fontStartX, startY); startY+=f.getHeight()+fontSpacing; for (int y=0;y<3;y++) { for(int x=0;x<3;x++) { int thickness=3; if (i==pos) { thickness=7; } else if (i==pos-1) { thickness=5; } g.fillRect(startX+x*spacing-(thickness/2), startY+y*spacing-(thickness/2), thickness, thickness); i++; } } g.setColor(color); g.setFont(font); } public boolean animate() { return true; } public void paint(Graphics g) { paint(g, null); } void installPane(Form f) { super.installPane(f); f.registerAnimated(this); } void uninstallPane(Form f) { super.uninstallPane(f); f.deregisterAnimated(this); } }