[Htmlparser-cvs] htmlparser/src/org/htmlparser/lexerapplications/thumbelina Thumbelina.java,NONE,1.1
Brought to you by:
derrickoswald
Update of /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina In directory sc8-pr-cvs1:/tmp/cvs-serv16611/src/org/htmlparser/lexerapplications/thumbelina Added Files: Thumbelina.java package.html TileSet.java PicturePanel.java ThumbelinaFrame.java Picture.java Sequencer.java Log Message: Thumbelina Created a lexer GUI application to extract images behind thumbnails. Added a task in the ant build script - thumbelina - to create the jar file. You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. Usage: java -Xmx256M thumbelina.jar [URL] --- NEW FILE: Thumbelina.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/Thumbelina.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 [...1395 lines suppressed...] } return (!done); } } } /* * Revision Control Modification History * * $Log: Thumbelina.java,v $ * Revision 1.1 2003/09/21 18:20:56 derrickoswald * Thumbelina * Created a lexer GUI application to extract images behind thumbnails. * Added a task in the ant build script - thumbelina - to create the jar file. * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. * Usage: java -Xmx256M thumbelina.jar [URL] * * */ --- NEW FILE: package.html --- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <HTML> <HEAD> <TITLE>Thumbelina</TITLE> </HEAD> <BODY> Extract the images behind thumbnail images. This package is a demonstration of filtering the tags that are produced by the Lexer package. In this case the idea is to find links to known types of image file (.gif, .png and .jpg) that have as the link text a reference to a smaller or lower resolution image, often called a thumbnail image; hence the name. <p> Besides a lot of support code to provide a user interface, the heart of the process is found in <code>Thumbelina.extractImageLinks()</code>, which has a wee state machine that notes when an <IMG> tag is discovered within the body of an <A></A> tag pair. This triggers a fetch of the <code>HREF</code> (image file). <p> The fetch is performed in the background by the <code>ToolKit</code> image loading code which runs 4 threads (on my machine). When an image is received it is added to the list of pending images. This list is drained by the <code>Sequencer</code> as it presents images at fixed intervals. <p> The <code>TileSet</code> and <code>Picture</code> classes provide a framework for displaying the various sizes of image that arrive in a random way, while still being able to repaint the panel when required. <p> The images are only retained in memory long enough to get covered over by subsequent images, but in general, the manipulation of images is a memory intensive task which requires a higher than normal limit on the maximum heap memory, i.e. use the <code>-Xms256M</code> command line switch to avoid <code>java.lang.OutOfMemoryError</code> messages. <p> The rest is just the UI code, that can be altered by intrepid programmers as they see fit. <p> <b>TODO</b> <li>Fix race condition that background thread adds new URL's after a reset.</li> <li>Send output to log window instead of URL's in titlebar.</li> <li>Add pending list items as greyed out items to the history list.</li> <li>Make status bar a pipeline with valves and limit switches (better on/off buttons).</li> <li>Fix race condition that sometimes doesn't resize PicturePanel with frame.</li> <li>Tree view.</li> <li>Drag and drop support.</li> <li>JavaHelp.</li> <li>Allow filter configuration.</li> <li>Handle OutOfMemoryError more gracefully (trap System.err?).</li> <li>Add more background threads.</li> <li>Find out how to honour reset on the image fetcher threads.</li> </BODY> </HTML> --- NEW FILE: TileSet.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/TileSet.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package org.htmlparser.lexerapplications.thumbelina; import java.awt.Rectangle; import java.util.Enumeration; import java.util.Vector; /** * Class to track picture regions. */ public class TileSet /* extends java.awt.Canvas implements java.awt.event.ActionListener, java.awt.event.MouseListener, java.awt.event.WindowListener */ { /** * The list of Pictures. */ protected Vector mRegions; /** * Construct a tile set. */ public TileSet () { mRegions = new Vector (); } /** * Get the number of tiles in this collection. * @return The number of pictures showing. * Note that the same image and URL may be showing * (different pieces) in several locations. */ public int getSize () { return (mRegions.size ()); } /** * Get the list of pictures. * @return An enumeration over the picture objects in this set. */ public Enumeration getPictures () { return (mRegions.elements ()); } /** * Add a single picture to the list. * @param r The picture to add. */ public void add (final Picture r) { Vector regions; // this will be the new set Enumeration e; Picture rover; Rectangle intersection; Vector splits; Enumeration frags; regions = new Vector (); for (e = getPictures (); e.hasMoreElements (); ) { rover = (Picture)e.nextElement (); if (rover.intersects (r)) { intersection = rover.intersection (r); if (!intersection.equals (rover)) { // incoming lies completely within the existing picture // or touches the existing picture somehow splits = split (r, rover, false); for (frags = splits.elements (); frags.hasMoreElements (); ) regions.addElement ((Picture)frags.nextElement ()); } else // incoming covers existing... drop the existing picture // but be sure to release the image memory rover.setImage (null); } else // no conflict, keep the existing regions.addElement (rover); } regions.addElement (r); mRegions = regions; } /** * Split the large picture. * Strategy: split horizontally (full width strips top and bottom). * NOTE: top and bottom make sense only in terms of AWT coordinates. * @param small The incoming picture. * @param large The encompassing picture. The attributes of this one * are propagated to the fragments. * @param keep If <code>true</code>, the center area is kept, * otherwise discarded. * @return The fragments from the large picture. */ private Vector split ( final Picture small, final Picture large, final boolean keep) { Picture m; Vector ret; ret = new Vector (); if (large.intersects (small)) { Rectangle intersection = large.intersection (small); // if tops don't match split off the top if ((intersection.y + intersection.height) != (large.y + large.height)) { m = new Picture (large); m.y = (intersection.y + intersection.height); m.height = (large.y + large.height) - m.y; ret.addElement (m); } // if left sides don't match make a left fragment if (intersection.x != large.x) { m = new Picture (large); m.y = intersection.y; m.width = intersection.x - large.x; m.height = intersection.height; ret.addElement (m); } // the center bit if (keep) { m = new Picture (large); m.x = intersection.x; m.y = intersection.y; m.width = intersection.width; m.height = intersection.height; ret.addElement (m); } // if right sides don't match make a right fragment if ((intersection.x + intersection.width) != (large.x + large.width)) { m = new Picture (large); m.x = intersection.x + intersection.width; m.y = intersection.y; m.width = (large.x + large.width) - m.x; m.height = intersection.height; ret.addElement (m); } // if bottoms don't match split off the bottom if (intersection.y != large.y) { m = new Picture (large); m.height = (intersection.y - large.y); ret.addElement (m); } } return (ret); } /** * Find the Picture at position x,y * @param x The x coordinate of the point to examine. * @param y The y coordinate of the point to examine. * @return The picture at that point, or <code>null</code> * if there are none. */ public Picture pictureAt (final int x, final int y) { Picture m; Picture ret; ret = null; for (int i = 0; (null == ret) && (i < mRegions.size ()); i++) { m = (Picture)mRegions.elementAt (i); if (m.contains (x, y)) ret = m; } return (ret); } /** * Move the given picture to the top of the Z order. * @param picture The picture to add. */ public void bringToTop (final Picture picture) { Picture m; Picture ret; ret = null; for (int i = 0; (null == ret) && (i < mRegions.size ()); ) { m = (Picture)mRegions.elementAt (i); if (picture.same (m)) mRegions.removeElementAt (i); else i++; } add (picture); } // // // // Unit test. // // // // // and need to add: // extends // java.awt.Canvas // implements // java.awt.event.ActionListener, // java.awt.event.MouseListener, // java.awt.event.WindowListener // // to the class definition // // boolean mVerbose; // int mCounter; // java.awt.Point origin; // Rectangle last; // int type; // // static java.awt.MenuBar menuMain; // static java.awt.Menu Options; // static java.awt.MenuItem repeat; // static java.awt.MenuItem clear; // static java.awt.TextField status; // // // checks if adding the rectangle causes an overlap // boolean checkAdd (Rectangle r, Vector v) // { // Enumeration e; // boolean ret; // ret = false; // // for (e = v.elements (); !ret && e.hasMoreElements (); ) // ret = r.intersects ((Rectangle)e.nextElement ()); // // return (ret); // } // // void paintwait () // { // java.awt.Graphics g = getGraphics (); // if (null != g) // paint (g); // Thread.yield (); // try // { // Thread.sleep (1000); // } // catch (Exception exception) // { // } // } // // void add () // { // if (null != last) // { // Picture m = new Picture (last); // try // { // m.setURL (new URL ("http://localhost/image#" + mCounter++)); // } // catch (java.net.MalformedURLException murle) // { // murle.printStackTrace (); // } // this.add (m); // repaint (); // } // } // // // // // WindowListener interface // // // public void windowOpened (java.awt.event.WindowEvent e) {} // public void windowClosing (java.awt.event.WindowEvent e) // { // System.exit (0); // } // public void windowClosed (java.awt.event.WindowEvent e) {} // public void windowIconified (java.awt.event.WindowEvent e) {} // public void windowDeiconified (java.awt.event.WindowEvent e) {} // public void windowActivated (java.awt.event.WindowEvent e) {} // public void windowDeactivated (java.awt.event.WindowEvent e) {} // // // // // ActionListener interface // // // public void actionPerformed (java.awt.event.ActionEvent event) // { // Object object = event.getSource(); // if (object == repeat) // add (); // else if (object == clear) // { // mRegions = new Vector (); // repaint (); // } // } // // // // // MouseListener Interface // // // // public void mouseClicked (java.awt.event.MouseEvent event) // { // if (mVerbose) // System.out.println ("DrawTarget.mouseClicked " + event); // } // // public void mouseReleased (java.awt.event.MouseEvent event) // { // if (mVerbose) // System.out.println ("DrawTarget.mouseReleased " + event); // if (null != origin) // { // last = new Rectangle ( // Math.min (origin.x, event.getX ()), // Math.min (origin.y, event.getY ()), // Math.abs (event.getX () - origin.x), // Math.abs (event.getY () - origin.y)); // add (); // origin = null; // } // } // // public void mouseEntered (java.awt.event.MouseEvent event) // { // if (mVerbose) // System.out.println ("DrawTarget.mouseEntered " + event); // } // // public void mouseExited (java.awt.event.MouseEvent event) // { // if (mVerbose) // System.out.println ("DrawTarget.mouseExited " + event); // } // // public void mousePressed (java.awt.event.MouseEvent event) // { // if (mVerbose) // System.out.println ("DrawTarget.mousePressed " + event); // if (event.isMetaDown ()) // { // status.setText (getDetails (event.getX (), event.getY ())); // } // else // origin = new java.awt.Point (event.getX (), event.getY ()); // } // // public void update (java.awt.Graphics graphics) // { // paint (graphics); // } // // static final java.awt.Color[] mColours = // { // java.awt.Color.blue, // java.awt.Color.cyan, // java.awt.Color.gray, // java.awt.Color.green, // java.awt.Color.orange, // java.awt.Color.pink, // java.awt.Color.red, // java.awt.Color.yellow, // java.awt.Color.lightGray, // java.awt.Color.darkGray, // }; // // public void paint (java.awt.Graphics graphics) // { // java.awt.Dimension size = getSize (); // graphics.setColor (getBackground ()); // graphics.fillRect (0, 0, size.width + 1, size.height + 1); // // if (0 == mRegions.size ()) // { // graphics.setColor (getForeground ()); // graphics.drawString ( // "Click and drag to create a picture.", 10, 20); // graphics.drawString ( // "Right click a picture for details.", 10, 40); // } // else // { // Enumeration e = getPictures (); // while (e.hasMoreElements ()) // { // Picture m = (Picture)e.nextElement (); // String url = m.getURL ().toExternalForm (); // int n = url.indexOf ('#'); // n = Integer.parseInt (url.substring (n + 1)) // java.awt.Color colour = mColours[n % mColours.length]; // graphics.setColor (colour); // graphics.fillRect (m.x, m.y, m.width + 1, m.height + 1); // graphics.setColor (java.awt.Color.black); // graphics.drawRect (m.x, m.y, m.width, m.height); // } // checkOverlap (graphics); // } // } // // void checkOverlap (java.awt.Graphics graphics) // { // Picture m; // Picture _m; // Rectangle r; // // graphics.setColor (java.awt.Color.magenta); // for (int i = 0; i < mRegions.size (); i++) // { // m = (Picture)mRegions.elementAt (i); // for (int j = i + 1; j < mRegions.size (); j++) // { // _m = (Picture)mRegions.elementAt (j); // if (m.intersects (_m)) // { // r = m.intersection (_m); // System.out.println ( // "overlap (" // + r.x // + "," // + r.y // + ") (" // + (r.x + r.width) // + "," // + (r.y + r.height) // + ")"); // graphics.fillRect (r.x, r.y, r.width + 1, r.height + 1); // } // } // } // } // // String getDetails (int x, int y) // { // Picture m; // String ret; // // ret = null; // // // find the Picture // for (int i = 0; (null == ret) && (i < mRegions.size ()); i++) // { // m = (Picture)mRegions.elementAt (i); // if (m.contains (x, y)) // ret = m.toString (); // } // if (null == ret) // ret = ""; // // return (ret); // } // // public static void main (String[] args) // { // java.awt.Frame frame; // // frame = new java.awt.Frame (); // frame.setSize (400,400); // menuMain = new java.awt.MenuBar(); // Options = new java.awt.Menu ("Options"); // repeat = new java.awt.MenuItem("Repeat"); // Options.add (repeat); // clear = new java.awt.MenuItem("Clear"); // Options.add (clear); // // menuMain.add (Options); // frame.setMenuBar (menuMain); // // java.awt.Insets insets = frame.getInsets (); // // TileSet buffy = new TileSet (); // buffy.setLocation (insets.left + 10, insets.top + 10); // buffy.setBackground (java.awt.Color.lightGray.brighter ()); // buffy.setVisible (true); // // frame.add (buffy, "Center"); // status = new java.awt.TextField (); // frame.add (status, "South"); // // frame.addWindowListener (buffy); // buffy.addMouseListener (buffy); // repeat.addActionListener (buffy); // clear.addActionListener (buffy); // // frame.setVisible (true); // // } } /* * Revision Control Modification History * * $Log: TileSet.java,v $ * Revision 1.1 2003/09/21 18:20:56 derrickoswald * Thumbelina * Created a lexer GUI application to extract images behind thumbnails. * Added a task in the ant build script - thumbelina - to create the jar file. * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. * Usage: java -Xmx256M thumbelina.jar [URL] * * */ --- NEW FILE: PicturePanel.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/PicturePanel.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package org.htmlparser.lexerapplications.thumbelina; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.util.Enumeration; import java.util.HashSet; import javax.swing.JPanel; import javax.swing.JViewport; import javax.swing.Scrollable; import javax.swing.border.BevelBorder; /** * Hold and display a group of pictures. * @author derrick */ public class PicturePanel extends JPanel implements MouseListener, Scrollable, ComponentListener, HierarchyListener { /** * Scrolling unit increment (both directions). */ protected static final int UNIT_INCREMENT = 10; /** * Scrolling block increment (both directions). */ protected static final int BLOCK_INCREMENT = 100; /** * The thumbelina object in use. */ protected Thumbelina mThumbelina; /** * The display mosaic. */ protected TileSet mMosaic; /** * The preferred size of this component. * <code>null</code> initially, caches the results of * <code>calculatePreferredSize ()</code>. */ protected Dimension mPreferredSize; /** * Creates a new instance of PicturePanel * @param thumbelina The <code>Thumeblina</code> this panel is associated * with. */ public PicturePanel (final Thumbelina thumbelina) { mThumbelina = thumbelina; mMosaic = new TileSet (); mPreferredSize = null; setBorder (new BevelBorder (BevelBorder.LOWERED)); addMouseListener (this); addHierarchyListener (this); } /** * Clears the panel, discarding any existing images. */ public void reset () { mMosaic = new TileSet (); repaint (); } /** * Move the given picture to the top of the Z order. * Adds it, even it if it doesn't exist. * Also puts the URL in the url text of the status bar. * @param picture The picture being brought forward. */ public void bringToTop (final Picture picture) { picture.reset (); mMosaic.bringToTop (picture); repaint (picture.x, picture.y, picture.width, picture.height); mThumbelina.mUrlText.setText (picture.getURL ().toExternalForm ()); } /** * Find a picture with the given URL in the panel. * This should really only be used to discover if the picture is still * visible. There could be more than one picture with the given URL * because it may be partially obscured by another picture, in which * case the pieces are each given their own picture object, but all * point at the same <code>URL</code> and <code>Image</code>. * @param url The url to locate. * @return The first picture encountered in the panel, * or null if the picture was not found. */ public Picture find (final String url) { Enumeration enumeration; Picture picture; Picture ret; ret = null; enumeration = mMosaic.getPictures (); while ((null == ret) && enumeration.hasMoreElements ()) { picture = (Picture)enumeration.nextElement (); if (url.equals (picture.getURL ().toExternalForm ())) ret = picture; } return (ret); } /** * Draw an image on screen. * @param picture The picture to draw. * @param add If <code>true</code>, the picture is added to the history. */ protected void draw (final Picture picture, final boolean add) { Component parent; boolean dolayout; Dimension before; Dimension after; parent = getParent (); dolayout = false; synchronized (mMosaic) { if (parent instanceof JViewport) { before = getPreferredSize (); mMosaic.add (picture); after = calculatePreferredSize (); if (after.width > before.width) dolayout = true; else after.width = before.width; if (after.height > before.height) dolayout = true; else after.height = before.height; if (dolayout) mPreferredSize = after; } else mMosaic.add (picture); } if (dolayout) revalidate (); repaint (picture.x, picture.y, picture.width, picture.height); if (add) mThumbelina.addHistory (picture.getURL ().toExternalForm ()); } /** * Updates this component. * @param graphics The graphics context in which to update the component. */ public void update (final Graphics graphics) { paint (graphics); } /** * Adjust the graphics clip region to account for insets. * @param graphics The graphics object to set the clip region for. */ public void adjustClipForInsets (final Graphics graphics) { Dimension dim; Insets insets; Rectangle clip; dim = getSize (); insets = getInsets (); clip = graphics.getClipBounds (); if (clip.x < insets.left) clip.x = insets.left; if (clip.y < insets.top) clip.y = insets.top; if (clip.x + clip.width > dim.width - insets.right) clip.width = dim.width - insets.right - clip.x; if (clip.y + clip.height > dim.height - insets.bottom) clip.height = dim.height - insets.bottom - clip.y; graphics.setClip (clip.x, clip.y, clip.width, clip.height); } /** * Paints this component. * Runs through the list of tiles and for every one that intersects * the clip region performs a <code>drawImage()</code>. */ public void paint (final Graphics graphics) { Rectangle clip; Enumeration enumeration; HashSet set; // just so we don't draw things twice Picture picture; Image image; Point origin; int width; int height; adjustClipForInsets (graphics); clip = graphics.getClipBounds (); synchronized (mMosaic) { if (0 == mMosaic.getSize ()) super.paint (graphics); else { super.paint (graphics); enumeration = mMosaic.getPictures (); set = new HashSet (); while (enumeration.hasMoreElements ()) { picture = (Picture)enumeration.nextElement (); if ((null == clip) || (clip.intersects (picture))) { image = picture.getImage (); if (!set.contains (image)) { origin = picture.getOrigin (); width = image.getWidth (this); height = image.getHeight (this); graphics.drawImage (picture.getImage (), origin.x, origin.y, origin.x + width, origin.y + height, 0, 0, width, height, this); set.add (image); } } } } } } /** * Get the preferred size of the component. * @return The dimension of this component. */ public Dimension getPreferredSize () { if (null == mPreferredSize) setPreferredSize (calculatePreferredSize ()); else if ((0 == mPreferredSize.width) || (0 == mPreferredSize.height)) setPreferredSize (calculatePreferredSize ()); return (mPreferredSize); } /** * Sets the preferred size of this component. * @param dimension The new value to use for * <code>getPreferredSize()</code> until recalculated. */ public void setPreferredSize (final Dimension dimension) { mPreferredSize = dimension; } /** * Compute the preferred size of the component. * Computes the minimum bounding rectangle covering all the pictures in * the panel. It then does some funky stuff to handle * embedding in the view port of a scroll pane, basically asking * up the ancestor heirarchy what size is available, and filling it. * @return The optimal dimension for this component. */ protected Dimension calculatePreferredSize () { Enumeration enumeration; int x; int y; Picture picture; Component parent; Insets insets; Dimension ret; enumeration = mMosaic.getPictures (); x = 0; y = 0; picture = null; while (enumeration.hasMoreElements ()) { picture = (Picture)enumeration.nextElement (); if (picture.x + picture.width > x) x = picture.x + picture.width; if (picture.y + picture.height > y) y = picture.y + picture.height; } parent = getParent (); if (parent instanceof JViewport) { ret = parent.getSize (); insets = ((JViewport)parent).getInsets (); ret.width -= insets.left + insets.right; ret.height -= insets.top + insets.bottom; if ((0 != ret.width) || (0 != ret.height)) ret.width -= 2; // ... I dunno why, it just needs it if (ret.width < x) ret.width = x; if (ret.height < y) ret.height = y; } else { insets = getInsets (); x += insets.left + insets.right; y += insets.top + insets.bottom; ret = new Dimension (x, y); } return (ret); } // // MouseListener Interface // /** * Invoked when the mouse button has been clicked * (pressed and released) on a component. * <i>Not used.</i> * @param event The object providing details of the mouse event. */ public void mouseClicked (final MouseEvent event) { } /** *Invoked when a mouse button has been released on a component. * <i>Not used.</i> * @param event The object providing details of the mouse event. */ public void mouseReleased (final MouseEvent event) { } /** * Invoked when the mouse enters a component. * <i>Not used.</i> * @param event The object providing details of the mouse event. */ public void mouseEntered (final MouseEvent event) { } /** * Invoked when the mouse exits a component. * <i>Not used.</i> * @param event The object providing details of the mouse event. */ public void mouseExited (final MouseEvent event) { } /** * Handle left click on a picture by bringing it to the top. * @param event The object providing details of the mouse event. */ public void mousePressed (final MouseEvent event) { Picture picture; if (!event.isMetaDown ()) { picture = mMosaic.pictureAt (event.getX (), event.getY ()); if (null != picture) bringToTop (picture); } } // // Scrollable interface // /** * Returns the preferred size of the viewport for a view component. * For example the preferredSize of a JList component is the size * required to accommodate all of the cells in its list however the * value of preferredScrollableViewportSize is the size required for * JList.getVisibleRowCount() rows. A component without any properties * that would effect the viewport size should just return * getPreferredSize() here. * * @return The preferredSize of a JViewport whose view is this Scrollable. * @see JViewport#getPreferredSize */ public Dimension getPreferredScrollableViewportSize () { return (getPreferredSize ()); } /** * Components that display logical rows or columns should compute * the scroll increment that will completely expose one new row * or column, depending on the value of orientation. Ideally, * components should handle a partially exposed row or column by * returning the distance required to completely expose the item. * <p> * Scrolling containers, like JScrollPane, will use this method * each time the user requests a unit scroll. * * @param visibleRect The view area visible within the viewport * @param orientation Either SwingConstants.VERTICAL or * SwingConstants.HORIZONTAL. * @param direction Less than zero to scroll up/left, * greater than zero for down/right. * @return The "unit" increment for scrolling in the specified direction. * This value should always be positive. */ public int getScrollableUnitIncrement ( final Rectangle visibleRect, final int orientation, final int direction) { return (UNIT_INCREMENT); } /** * Components that display logical rows or columns should compute * the scroll increment that will completely expose one block * of rows or columns, depending on the value of orientation. * <p> * Scrolling containers, like JScrollPane, will use this method * each time the user requests a block scroll. * * @param visibleRect The view area visible within the viewport * @param orientation Either SwingConstants.VERTICAL or * SwingConstants.HORIZONTAL. * @param direction Less than zero to scroll up/left, * greater than zero for down/right. * @return The "block" increment for scrolling in the specified direction. * This value should always be positive. */ public int getScrollableBlockIncrement ( final Rectangle visibleRect, final int orientation, final int direction) { return (BLOCK_INCREMENT); } /** * Return true if a viewport should always force the width of this * <code>Scrollable</code> to match the width of the viewport. * For example a normal * text view that supported line wrapping would return true here, since it * would be undesirable for wrapped lines to disappear beyond the right * edge of the viewport. Note that returning true for a Scrollable * whose ancestor is a JScrollPane effectively disables horizontal * scrolling. * <p> * Scrolling containers, like JViewport, will use this method each * time they are validated. * * @return <code>true</code> if a viewport should force the Scrollables * width to match its own. */ public boolean getScrollableTracksViewportWidth () { return (false); } /** * Return true if a viewport should always force the height of this * Scrollable to match the height of the viewport. For example a * columnar text view that flowed text in left to right columns * could effectively disable vertical scrolling by returning * true here. * <p> * Scrolling containers, like JViewport, will use this method each * time they are validated. * * @return <code>true</code> if a viewport should force the Scrollables * height to match its own. */ public boolean getScrollableTracksViewportHeight () { return (false); } // // ComponentListener interface // /** * Invoked when the container's size changes. * Un-caches the preferred size. * @param event The resize event. */ public void componentResized (final ComponentEvent event) { setPreferredSize (null); } /** * Invoked when the component's position changes. * <i>Not used.</I> * @param event The component event. */ public void componentMoved (final ComponentEvent event) { } /** * Invoked when the component has been made visible. * <i>Not used.</I> * @param event The component event. */ public void componentShown (final ComponentEvent event) { } /** * Invoked when the component has been made invisible. * <i>Not used.</I> * @param event The component event. */ public void componentHidden (final ComponentEvent event) { } // // HierarchyListener interface // /** * Handles this components ancestor being added to a container. * Registers this component as a listener for size changes on the * ancestor so that we may un-cache the prefereed size and force * a recalculation. * @param event The heirarchy event. */ public void hierarchyChanged (final HierarchyEvent event) { if (0 != (event.getChangeFlags () & HierarchyEvent.PARENT_CHANGED)) { Component dad = event.getChanged (); Component parent = getParent (); if ((null != parent) && (parent.getParent () == dad)) dad.addComponentListener (this); } } } /* * Revision Control Modification History * * $Log: PicturePanel.java,v $ * Revision 1.1 2003/09/21 18:20:56 derrickoswald * Thumbelina * Created a lexer GUI application to extract images behind thumbnails. * Added a task in the ant build script - thumbelina - to create the jar file. * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. * Usage: java -Xmx256M thumbelina.jar [URL] * * */ --- NEW FILE: ThumbelinaFrame.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/ThumbelinaFrame.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 [...1054 lines suppressed...] || args[0].equalsIgnoreCase ("-help") || args[0].equalsIgnoreCase ("-h") || args[0].equalsIgnoreCase ("?") || args[0].equalsIgnoreCase ("-?")) Thumbelina.help (); else url = args[0]; try { frame = new ThumbelinaFrame (url); frame.setVisible (true); } catch (MalformedURLException murle) { System.err.println (murle.getMessage ()); Thumbelina.help (); } } } --- NEW FILE: Picture.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/Picture.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package org.htmlparser.lexerapplications.thumbelina; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.net.URL; /** * Class to track pictures within the frame. * Maintains an image, an area and the URL for it. */ public class Picture extends Rectangle { /** * The origin for new points from the zero args constructor. */ public static final Point ORIGIN = new Point (0, 0); /** * The URL for the picture. */ protected URL mURL; /** * The image for the picture. */ protected Image mImage; /** * The upper left hand corner of the image. * This doesn't change, even if the image is cropped. * For example, if the left half of the image is obscured by another, * the <code>Rectangle</code> fields <code>x</code>, <code>y</code>, * <code>width</code> and <code>height</code> will change, but the * origin remains the same. */ protected Point mOrigin; /** * Construct a Picture. */ public Picture () { setURL (null); setImage (null); setOrigin (ORIGIN); } /** * Construct a Picture over the area given. * @param x The x coordinate. * @param y The y coordinate. * @param width The width of the picture. * @param height The height of the picture. */ public Picture (final int x, final int y, final int width, final int height) { super (x, y, width, height); setURL (null); setImage (null); setOrigin (new Point (x, y)); } /** * Construct a picture over the rectangle given. * @param r The coordinates of the area. */ public Picture (final Rectangle r) { super (r); setURL (null); setImage (null); setOrigin (new Point (r.x, r.y)); } /** * Construct a picture from the one given. * @param picture The picture to copy. */ public Picture (final Picture picture) { super (picture); setURL (picture.getURL ()); setImage (picture.getImage ()); setOrigin (picture.getOrigin ()); } /** * Getter for property URL. * @return Value of property URL. */ public URL getURL () { return (mURL); } /** * Setter for property URL. * @param url New value of property URL. */ public void setURL (final URL url) { mURL = url; } /** Getter for property image. * @return Value of property image. */ public Image getImage () { return (mImage); } /** Setter for property image. * @param image New value of property image. */ public void setImage (final Image image) { mImage = image; if (null != image) { width = image.getWidth (null); height = image.getHeight (null); } } /** Getter for property origin. * @return Value of property origin. */ public Point getOrigin () { return (mOrigin); } /** Setter for property origin. * @param origin New value of property origin. */ public void setOrigin (final Point origin) { mOrigin = origin; } /** * Return <code>true</code> if that picture is the same as this one. * @param picture The picture to check. * @return <code>true</code> if the images match. */ public boolean same (final Picture picture) { return (mImage == picture.mImage); } /** * Reset the picture to uncropped size. */ public void reset () { setBounds (mOrigin.x, mOrigin.y, mImage.getWidth (null), mImage.getHeight (null)); } /** * Create a string representation of the modifcation. * @return A string that shows this picture. */ public String toString () { StringBuffer ret; ret = new StringBuffer (); ret.append (getURL ().toString ()); ret.append ("[x="); ret.append (Integer.toString (x)); ret.append (",y="); ret.append (Integer.toString (y)); ret.append (",width="); ret.append (Integer.toString (width)); ret.append (",height="); ret.append (Integer.toString (height)); ret.append ("]"); return (ret.toString ()); } } /* * Revision Control Modification History * * $Log: Picture.java,v $ * Revision 1.1 2003/09/21 18:20:56 derrickoswald * Thumbelina * Created a lexer GUI application to extract images behind thumbnails. * Added a task in the ant build script - thumbelina - to create the jar file. * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. * Usage: java -Xmx256M thumbelina.jar [URL] * * */ --- NEW FILE: Sequencer.java --- // HTMLParser Library $Name: $ - A java-based parser for HTML // http://sourceforge.org/projects/htmlparser // Copyright (C) 2003 Derrick Oswald // // Revision Control Information // // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/Sequencer.java,v $ // $Author: derrickoswald $ // $Date: 2003/09/21 18:20:56 $ // $Revision: 1.1 $ // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package org.htmlparser.lexerapplications.thumbelina; import java.awt.Component; import java.awt.Dimension; import java.awt.Image; import java.awt.Insets; import java.awt.Point; import java.net.URL; import java.util.ArrayList; import java.util.Random; import javax.swing.JViewport; /** * Display received images at a constant rate. */ public class Sequencer extends Thread { /** * The default delay time, {@value} milliseconds. */ protected static final int DEFAULT_DELAY = 500; /** * The thumbelina object to drive. */ protected Thumbelina mThumbelina; /** * Pictures awaiting display. */ protected ArrayList mPending; /** * Activity state. * <code>true</code> means fetching and displaying, <code>false</code> not. */ protected boolean mActive; /** * Delay between picture displays. */ protected int mDelay; /** * Random number generator for picture placement. */ protected Random mRandom; /** * Creates a new instance of a Sequencer. * @param thumbelina The object to push images to. */ public Sequencer (final Thumbelina thumbelina) { mThumbelina = thumbelina; mPending = new ArrayList (); mActive = true; setDelay (DEFAULT_DELAY); mRandom = new Random (); setName ("Sequencer"); // only good if there's just one of these start (); } /** * Clears the pending images list. */ public void reset () { synchronized (mPending) { mPending.clear (); mThumbelina.mReadyProgress.setValue (0); mPending.notify (); } } /** * Compute a random point to load the image. * Generate a random point for one of the corners of the image and * then condition the numbers so the image is on screen. * @param url The url this picture was fetched from. * Used in computing the random position, so the picture is always * placed in the same location, even when refetched. * @param width The width of the image. * @param height The height of the image. * @return The random point to use. */ protected Point random (final String url, final int width, final int height) { Component parent; Component grandparent; Dimension dim; Insets insets; int minx; int miny; int maxx; int maxy; int rndx; int rndy; int corner; Point ret; parent = mThumbelina.getPicturePanel ().getParent (); if (parent instanceof JViewport) { grandparent = parent.getParent (); // JScrollPane dim = grandparent.getSize (); } else dim = mThumbelina.getPicturePanel ().getSize (); insets = mThumbelina.getPicturePanel ().getInsets (); dim.width -= (insets.left + insets.right); dim.height -= (insets.top + insets.bottom); minx = insets.left; miny = insets.top; maxx = minx + dim.width; maxy = miny + dim.height; mRandom.setSeed ((((long)(width + height)) << 32) + url.hashCode ()); rndx = (int)(mRandom.nextDouble () * dim.width); rndy = (int)(mRandom.nextDouble () * dim.height); corner = (int)(mRandom.nextDouble () * 4); // the panel has four corners ret = new Point (0, 0); switch (corner) { case 0: // upper left if (rndx + width >= maxx) ret.x = maxx - width; else ret.x = rndx; if (rndy + height >= maxy) ret.y = maxy - height; else ret.y = rndy; break; case 1: // upper right if (rndx - width < minx) ret.x = minx; else ret.x = rndx - width; if (rndy + height >= maxy) ret.y = maxy - height; else ret.y = rndy; break; case 2: // lower right if (rndx - width < minx) ret.x = minx; else ret.x = rndx - width; if (rndy - height < miny) ret.y = miny; else ret.y = rndy - height; break; case 3: // lower left if (rndx + width >= maxx) ret.x = maxx - width; else ret.x = rndx; if (rndy - height < miny) ret.y = miny; else ret.y = rndy - height; break; default: throw new IllegalStateException ("random corner = " + corner); } // if it's really large stuff it in the upper left hand corner if (ret.x < 0) ret.x = 0; if (ret.y < 0) ret.y = 0; return (ret); } /** * Add an image to the pending list. * @param image The image to add. * @param url The url the image came from. */ public void add (final Image image, final URL url) { add (image, url, true); } /** * Add an image to the panel. * @param image The image to add. * @param url The url the image came from. * @param background If <code>true</code>, just add to pending list. */ public void add (final Image image, final URL url, final boolean background) { int x; int y; Point p; Picture picture; int size; x = image.getWidth (null); y = image.getHeight (null); picture = new Picture (); picture.setImage (image); picture.setURL (url); if (background) synchronized (mPending) { mPending.add (picture); size = mPending.size (); if (mThumbelina.mReadyProgress.getMaximum () < size) mThumbelina.mReadyProgress.setMaximum (size); mThumbelina.mReadyProgress.setValue (size); mPending.notify (); } else place (picture, false); } /** * Place a picture in the display area. * Places the picture at a random location on screen. * @param picture The picture to place on screen. * @param add If <code>true</code>, the picture is added to the history. */ protected void place (final Picture picture, final boolean add) { Point p; if (Picture.ORIGIN == picture.getOrigin ()) { // never been placed before p = random ( picture.getURL ().toExternalForm (), picture.width, picture.height); picture.x = p.x; picture.y = p.y; picture.setOrigin (p); } mThumbelina.getPicturePanel ().draw (picture, add); } // // Runnable interface // /** * Display pictures from pending list with delay between. * If the list is empty it waits on the pending list for new pictures. */ public void run () { Picture picture; int size; Point p; while (true) { try { picture = null; synchronized (mPending) { if (mActive && !mPending.isEmpty ()) picture = (Picture)mPending.remove (0); else try { mPending.wait (); } catch (InterruptedException ie) { ie.printStackTrace (); } size = mPending.size (); if (mThumbelina.mReadyProgress.getMaximum () < size) mThumbelina.mReadyProgress.setMaximum (size); mThumbelina.mReadyProgress.setValue (size); } if (null != picture) { place (picture, true); if (0 != getDelay ()) try { sleep (getDelay ()); } catch (InterruptedException ie) { ie.printStackTrace (); } } } catch (Throwable t) { t.printStackTrace (); } } } /** * Getter for property delay. * @return Value of property delay. */ public int getDelay () { return (mDelay); } /** * Setter for property delay. * @param delay New value of property delay. */ public void setDelay (final int delay) { mDelay = delay; } } /* * Revision Control Modification History * * $Log: Sequencer.java,v $ * Revision 1.1 2003/09/21 18:20:56 derrickoswald * Thumbelina * Created a lexer GUI application to extract images behind thumbnails. * Added a task in the ant build script - thumbelina - to create the jar file. * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. * Usage: java -Xmx256M thumbelina.jar [URL] * * */ |