/* * Copyright 2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package com.sun.lwuit.html; import com.sun.lwuit.Display; import com.sun.lwuit.Image; import com.sun.lwuit.Label; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; import java.util.Vector; /** * ImageThreadQueue is a thread queue used to create and manage threads that download images that were referred from HTML pages * * @author Ofir Leitner */ class ImageThreadQueue { /** * The default number of maximum threads used for image download */ private static int DEFAULT_MAX_THREADS = 2; HTMLComponent htmlC; Vector queue = new Vector(); Vector running = new Vector(); Vector urls = new Vector(); static int maxThreads = DEFAULT_MAX_THREADS; int threadCount; boolean started; /** * Constructs the queue * * @param htmlC The HTMLComponent this queue belongs to */ ImageThreadQueue(HTMLComponent htmlC) { this.htmlC=htmlC; } /** * Sets the maximum number of threads to use for image download * If startRunning was already called, this will takes effect in the next page loaded. * * @param threadsNum the maximum number of threads to use for image download */ static void setMaxThreads(int threadsNum) { maxThreads=threadsNum; } /** * Adds the image to the queue * * @param imgLabel The label in which the image should be contained after loaded * @param imageUrl The URL this image should be fetched from */ synchronized void add(Label imgLabel,String imageUrl) { if (started) { throw new IllegalStateException("ImageThreadQueue alreadey started! stop/cancel first"); } int index=urls.indexOf(imageUrl); if (index!=-1) { ImageThread t=(ImageThread)queue.elementAt(index); t.addLabel(imgLabel); } else { ImageThread t = new ImageThread(imageUrl, imgLabel, htmlC, this); queue.addElement(t); urls.addElement(imageUrl); } } /** * Returns the queue size * * @return the queue size */ int getQueueSize() { return queue.size(); } /** * Notifies the queue that all images have been queues and it can start dequeuing and download the images. * The queue isn't started before that to prevent multiple downloads of the same image */ synchronized void startRunning() { urls=new Vector(); //reset URL vector int threads=Math.min(queue.size(), maxThreads); started=(threads>0); for(int i=0;i<threads;i++) { ImageThread t=(ImageThread)queue.firstElement(); queue.removeElementAt(0); running.addElement(t); threadCount++; new Thread(t).start(); } if (!started) { htmlC.setPageStatus(HTMLCallback.STATUS_COMPLETED); } } /** * Called by the ImageThread when it finishes downloading and setting the image. * This in turns starts another thread if the queue is not empty * * @param finishedThread The calling thread * @param success true if the image download was successful, false otherwise */ synchronized void threadFinished(ImageThread finishedThread,boolean success) { running.removeElement(finishedThread); if (queue.size()>0) { ImageThread t=(ImageThread)queue.firstElement(); queue.removeElementAt(0); running.addElement(t); new Thread(t).start(); } else { threadCount--; } started=(threadCount>0); if (!started) { htmlC.setPageStatus(HTMLCallback.STATUS_COMPLETED); } } /** * Discards the entire queue and signals the running threads to cancel. * THis will be triggered if the user cancelled the page or moved to another page. */ synchronized void discardQueue() { queue.removeAllElements(); for(Enumeration e=running.elements();e.hasMoreElements();) { ImageThread t = (ImageThread)e.nextElement(); t.cancel(); } running.removeAllElements(); threadCount=0; started=false; } /** * Returns a printout of the threads queue, can be used for debugging * * @return a printout of the threads queue */ public String toString() { String str=("---- Running ----\n"); int i=1; for(Enumeration e=running.elements();e.hasMoreElements();) { ImageThread t = (ImageThread)e.nextElement(); str+="#"+i+": "+t.imageUrl+"\n"; i++; } i=1; str+="Queue:\n"; for(Enumeration e=queue.elements();e.hasMoreElements();) { ImageThread t = (ImageThread)e.nextElement(); str+="#"+i+": "+t.imageUrl+"\n"; i++; } str+="---- count:"+threadCount+" ----\n"; return str; } } /** * An ImageThread downloads an Image as requested * * @author Ofir Leitner */ class ImageThread implements Runnable { Label imgLabel; Vector labels; String imageUrl; DocumentRequestHandler handler; ImageThreadQueue threadQueue; boolean cancelled; HTMLComponent htmlC; Image img; /** * Constructs the ImageThread * * @param imgLabel The label in which the image should be contained after loaded * @param imageUrl The URL this image should be fetched from * @param handler The RequestHandler through which to retrieve the image * @param threadQueue The main queue, for callback purposes */ ImageThread(String imageUrl, Label imgLabel,HTMLComponent htmlC,ImageThreadQueue threadQueue) { this.imageUrl=imageUrl; this.imgLabel=imgLabel; this.handler=htmlC.getRequestHandler(); this.threadQueue=threadQueue; this.htmlC=htmlC; } /** * Cancels this thread */ void cancel() { cancelled=true; } /** * Adds a label which has the same URL, useful for duplicate images in the same page * * @param label A label which has the same image URL */ void addLabel(Label label) { if (labels==null) { labels=new Vector(); } labels.addElement(label); } /** * {@inheritDoc} */ public void run() { try { InputStream is = handler.resourceRequested(new DocumentInfo(imageUrl,DocumentInfo.TYPE_IMAGE)); if (is==null) { if (htmlC.getHTMLCallback()!=null) { htmlC.getHTMLCallback().parsingError(HTMLCallback.ERROR_IMAGE_NOT_FOUND, null, null, null, "Image not found at "+imageUrl); } } else { img=Image.createImage(is); if (img==null) { if (htmlC.getHTMLCallback()!=null) { htmlC.getHTMLCallback().parsingError(HTMLCallback.ERROR_IMAGE_BAD_FORMAT, null, null, null, "Image could not be created from "+imageUrl); } } } if (img==null) { threadQueue.threadFinished(this,false); return; } if (!cancelled) { Display.getInstance().callSerially(new Runnable() { public void run() { handleImage(img,imgLabel); if (labels!=null) { for(Enumeration e=labels.elements();e.hasMoreElements();) { Label label=(Label)e.nextElement(); handleImage(img,label); } } } }); threadQueue.threadFinished(this,true); } } catch (IOException ioe) { htmlC.getHTMLCallback().parsingError(HTMLCallback.ERROR_IMAGE_BAD_FORMAT, null, null, null, "Image could not be created from "+imageUrl+": "+ioe.getMessage()); if(!cancelled) { threadQueue.threadFinished(this,false); } //threadQueue.threadFinished(imgLabel,imageUrl,false); } } /** * After a successful download, this handles placing the image on the label and resizing if necessary * * @param img The image * @param label The label to apply the image on */ private void handleImage(Image img,Label label) { int width=label.getPreferredW(); // Was set in HTMLComponent.handleImage if the width attribute was in the tag int height=label.getPreferredH(); if (width==0) { // Width wasn't specified - get from image width=img.getWidth(); label.setPreferredW(width); label.setWidth(width); htmlC.revalidate(); } if (height==0) { // Height wasn't specified - get from image height=img.getHeight(); label.setPreferredH(height); label.setHeight(height); if (label.getParent().getPreferredH()<height) { // An empty newline, or one with 0 height is set to the height of the font height. If one of the components is an image that "grew" it has to be adapted label.getParent().setPreferredH(height); } htmlC.revalidate(); } label.setIcon(img.scaled(width, height)); // If width+height are the same no processing will be done (checked in Image.scaled) label.getUnselectedStyle().setBorder(null); //remove the border which is a sign the image is loading } }