PicturePanel.java
1 // HTMLParser Library $Name: v1_6_20060319 $ - A java-based parser for HTML 2 // http://sourceforge.org/projects/htmlparser 3 // Copyright (C) 2003 Derrick Oswald 4 // 5 // Revision Control Information 6 // 7 // $Source: /cvsroot/htmlparser/htmlparser/src/org/htmlparser/lexerapplications/thumbelina/PicturePanel.java,v $ 8 // $Author: derrickoswald $ 9 // $Date: 2005/04/12 11:27:41 $ 10 // $Revision: 1.2 $ 11 // 12 // This library is free software; you can redistribute it and/or 13 // modify it under the terms of the GNU Lesser General Public 14 // License as published by the Free Software Foundation; either 15 // version 2.1 of the License, or (at your option) any later version. 16 // 17 // This library is distributed in the hope that it will be useful, 18 // but WITHOUT ANY WARRANTY; without even the implied warranty of 19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 20 // Lesser General Public License for more details. 21 // 22 // You should have received a copy of the GNU Lesser General Public 23 // License along with this library; if not, write to the Free Software 24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 25 // 26 27 package org.htmlparser.lexerapplications.thumbelina; 28 29 import java.awt.Component; 30 import java.awt.Dimension; 31 import java.awt.Graphics; 32 import java.awt.Image; 33 import java.awt.Insets; 34 import java.awt.Point; 35 import java.awt.Rectangle; 36 import java.awt.event.ComponentEvent; 37 import java.awt.event.ComponentListener; 38 import java.awt.event.HierarchyEvent; 39 import java.awt.event.HierarchyListener; 40 import java.awt.event.MouseEvent; 41 import java.awt.event.MouseListener; 42 import java.util.Enumeration; 43 import java.util.HashSet; 44 import javax.swing.JPanel; 45 import javax.swing.JViewport; 46 import javax.swing.Scrollable; 47 import javax.swing.border.BevelBorder; 48 49 /** 50 * Hold and display a group of pictures. 51 * @author derrick 52 */ 53 public class PicturePanel 54 extends 55 JPanel 56 implements 57 MouseListener, 58 Scrollable, 59 ComponentListener, 60 HierarchyListener 61 { 62 /** 63 * Scrolling unit increment (both directions). 64 */ 65 protected static final int UNIT_INCREMENT = 10; 66 67 /** 68 * Scrolling block increment (both directions). 69 */ 70 protected static final int BLOCK_INCREMENT = 100; 71 72 /** 73 * The thumbelina object in use. 74 */ 75 protected Thumbelina mThumbelina; 76 77 /** 78 * The display mosaic. 79 */ 80 protected TileSet mMosaic; 81 82 /** 83 * The preferred size of this component. 84 * <code>null</code> initially, caches the results of 85 * <code>calculatePreferredSize ()</code>. 86 */ 87 protected Dimension mPreferredSize; 88 89 /** 90 * Creates a new instance of PicturePanel 91 * @param thumbelina The <code>Thumeblina</code> this panel is associated 92 * with. 93 */ 94 public PicturePanel (final Thumbelina thumbelina) 95 { 96 mThumbelina = thumbelina; 97 mMosaic = new TileSet (); 98 mPreferredSize = null; 99 setBorder (new BevelBorder (BevelBorder.LOWERED)); 100 addMouseListener (this); 101 addHierarchyListener (this); 102 } 103 104 /** 105 * Clears the panel, discarding any existing images. 106 */ 107 public void reset () 108 { 109 mMosaic = new TileSet (); 110 repaint (); 111 } 112 113 /** 114 * Move the given picture to the top of the Z order. 115 * Adds it, even it if it doesn't exist. 116 * Also puts the URL in the url text of the status bar. 117 * @param picture The picture being brought forward. 118 */ 119 public void bringToTop (final Picture picture) 120 { 121 picture.reset (); 122 mMosaic.bringToTop (picture); 123 repaint (picture.x, picture.y, picture.width, picture.height); 124 mThumbelina.mUrlText.setText (picture.getURL ().toExternalForm ()); 125 } 126 127 /** 128 * Find a picture with the given URL in the panel. 129 * This should really only be used to discover if the picture is still 130 * visible. There could be more than one picture with the given URL 131 * because it may be partially obscured by another picture, in which 132 * case the pieces are each given their own picture object, but all 133 * point at the same <code>URL</code> and <code>Image</code>. 134 * @param url The url to locate. 135 * @return The first picture encountered in the panel, 136 * or null if the picture was not found. 137 */ 138 public Picture find (final String url) 139 { 140 Enumeration enumeration; 141 Picture picture; 142 Picture ret; 143 144 ret = null; 145 enumeration = mMosaic.getPictures (); 146 while ((null == ret) && enumeration.hasMoreElements ()) 147 { 148 picture = (Picture)enumeration.nextElement (); 149 if (url.equals (picture.getURL ().toExternalForm ())) 150 ret = picture; 151 } 152 153 return (ret); 154 } 155 156 /** 157 * Draw an image on screen. 158 * @param picture The picture to draw. 159 * @param add If <code>true</code>, the picture is added to the history. 160 */ 161 protected void draw (final Picture picture, final boolean add) 162 { 163 Component parent; 164 boolean dolayout; 165 Dimension before; 166 Dimension after; 167 168 parent = getParent (); 169 dolayout = false; 170 synchronized (mMosaic) 171 { 172 if (parent instanceof JViewport) 173 { 174 before = getPreferredSize (); 175 mMosaic.add (picture); 176 after = calculatePreferredSize (); 177 if (after.width > before.width) 178 dolayout = true; 179 else 180 after.width = before.width; 181 if (after.height > before.height) 182 dolayout = true; 183 else 184 after.height = before.height; 185 if (dolayout) 186 mPreferredSize = after; 187 } 188 else 189 mMosaic.add (picture); 190 } 191 if (dolayout) 192 revalidate (); 193 repaint (picture.x, picture.y, picture.width, picture.height); 194 if (add) 195 mThumbelina.addHistory (picture.getURL ().toExternalForm ()); 196 } 197 198 /** 199 * Updates this component. 200 * @param graphics The graphics context in which to update the component. 201 */ 202 public void update (final Graphics graphics) 203 { 204 paint (graphics); 205 } 206 207 /** 208 * Adjust the graphics clip region to account for insets. 209 * @param graphics The graphics object to set the clip region for. 210 */ 211 public void adjustClipForInsets (final Graphics graphics) 212 { 213 Dimension dim; 214 Insets insets; 215 Rectangle clip; 216 217 dim = getSize (); 218 insets = getInsets (); 219 clip = graphics.getClipBounds (); 220 if (clip.x < insets.left) 221 clip.x = insets.left; 222 if (clip.y < insets.top) 223 clip.y = insets.top; 224 if (clip.x + clip.width > dim.width - insets.right) 225 clip.width = dim.width - insets.right - clip.x; 226 if (clip.y + clip.height > dim.height - insets.bottom) 227 clip.height = dim.height - insets.bottom - clip.y; 228 graphics.setClip (clip.x, clip.y, clip.width, clip.height); 229 } 230 231 /** 232 * Paints this component. 233 * Runs through the list of tiles and for every one that intersects 234 * the clip region performs a <code>drawImage()</code>. 235 * @param graphics The graphics context used to paint with. 236 */ 237 public void paint (final Graphics graphics) 238 { 239 Rectangle clip; 240 Enumeration enumeration; 241 HashSet set; // just so we don't draw things twice 242 Picture picture; 243 Image image; 244 Point origin; 245 int width; 246 int height; 247 248 adjustClipForInsets (graphics); 249 clip = graphics.getClipBounds (); 250 synchronized (mMosaic) 251 { 252 if (0 == mMosaic.getSize ()) 253 super.paint (graphics); 254 else 255 { 256 super.paint (graphics); 257 enumeration = mMosaic.getPictures (); 258 set = new HashSet (); 259 while (enumeration.hasMoreElements ()) 260 { 261 picture = (Picture)enumeration.nextElement (); 262 if ((null == clip) || (clip.intersects (picture))) 263 { 264 image = picture.getImage (); 265 if (!set.contains (image)) 266 { 267 origin = picture.getOrigin (); 268 width = image.getWidth (this); 269 height = image.getHeight (this); 270 graphics.drawImage (picture.getImage (), 271 origin.x, origin.y, 272 origin.x + width, origin.y + height, 273 0, 0, width, height, 274 this); 275 set.add (image); 276 } 277 } 278 } 279 } 280 } 281 } 282 283 284 /** 285 * Get the preferred size of the component. 286 * @return The dimension of this component. 287 */ 288 public Dimension getPreferredSize () 289 { 290 if (null == mPreferredSize) 291 setPreferredSize (calculatePreferredSize ()); 292 else 293 if ((0 == mPreferredSize.width) || (0 == mPreferredSize.height)) 294 setPreferredSize (calculatePreferredSize ()); 295 return (mPreferredSize); 296 } 297 298 /** 299 * Sets the preferred size of this component. 300 * @param dimension The new value to use for 301 * <code>getPreferredSize()</code> until recalculated. 302 */ 303 public void setPreferredSize (final Dimension dimension) 304 { 305 mPreferredSize = dimension; 306 } 307 308 /** 309 * Compute the preferred size of the component. 310 * Computes the minimum bounding rectangle covering all the pictures in 311 * the panel. It then does some funky stuff to handle 312 * embedding in the view port of a scroll pane, basically asking 313 * up the ancestor heirarchy what size is available, and filling it. 314 * @return The optimal dimension for this component. 315 */ 316 protected Dimension calculatePreferredSize () 317 { 318 Enumeration enumeration; 319 int x; 320 int y; 321 Picture picture; 322 Component parent; 323 Insets insets; 324 Dimension ret; 325 326 enumeration = mMosaic.getPictures (); 327 x = 0; 328 y = 0; 329 picture = null; 330 while (enumeration.hasMoreElements ()) 331 { 332 picture = (Picture)enumeration.nextElement (); 333 if (picture.x + picture.width > x) 334 x = picture.x + picture.width; 335 if (picture.y + picture.height > y) 336 y = picture.y + picture.height; 337 } 338 parent = getParent (); 339 if (parent instanceof JViewport) 340 { 341 ret = parent.getSize (); 342 insets = ((JViewport)parent).getInsets (); 343 ret.width -= insets.left + insets.right; 344 ret.height -= insets.top + insets.bottom; 345 if ((0 != ret.width) || (0 != ret.height)) 346 ret.width -= 2; // ... I dunno why, it just needs it 347 if (ret.width < x) 348 ret.width = x; 349 if (ret.height < y) 350 ret.height = y; 351 } 352 else 353 { 354 insets = getInsets (); 355 x += insets.left + insets.right; 356 y += insets.top + insets.bottom; 357 ret = new Dimension (x, y); 358 } 359 360 return (ret); 361 } 362 363 // 364 // MouseListener Interface 365 // 366 367 /** 368 * Invoked when the mouse button has been clicked 369 * (pressed and released) on a component. 370 * <i>Not used.</i> 371 * @param event The object providing details of the mouse event. 372 */ 373 public void mouseClicked (final MouseEvent event) 374 { 375 } 376 377 /** 378 *Invoked when a mouse button has been released on a component. 379 * <i>Not used.</i> 380 * @param event The object providing details of the mouse event. 381 */ 382 public void mouseReleased (final MouseEvent event) 383 { 384 } 385 386 /** 387 * Invoked when the mouse enters a component. 388 * <i>Not used.</i> 389 * @param event The object providing details of the mouse event. 390 */ 391 public void mouseEntered (final MouseEvent event) 392 { 393 } 394 395 /** 396 * Invoked when the mouse exits a component. 397 * <i>Not used.</i> 398 * @param event The object providing details of the mouse event. 399 */ 400 public void mouseExited (final MouseEvent event) 401 { 402 } 403 404 /** 405 * Handle left click on a picture by bringing it to the top. 406 * @param event The object providing details of the mouse event. 407 */ 408 public void mousePressed (final MouseEvent event) 409 { 410 Picture picture; 411 412 if (!event.isMetaDown ()) 413 { 414 picture = mMosaic.pictureAt (event.getX (), event.getY ()); 415 if (null != picture) 416 bringToTop (picture); 417 } 418 } 419 420 // 421 // Scrollable interface 422 // 423 424 /** 425 * Returns the preferred size of the viewport for a view component. 426 * For example the preferredSize of a JList component is the size 427 * required to accommodate all of the cells in its list however the 428 * value of preferredScrollableViewportSize is the size required for 429 * JList.getVisibleRowCount() rows. A component without any properties 430 * that would effect the viewport size should just return 431 * getPreferredSize() here. 432 * 433 * @return The preferredSize of a JViewport whose view is this Scrollable. 434 * @see JViewport#getPreferredSize 435 */ 436 public Dimension getPreferredScrollableViewportSize () 437 { 438 return (getPreferredSize ()); 439 } 440 441 442 /** 443 * Components that display logical rows or columns should compute 444 * the scroll increment that will completely expose one new row 445 * or column, depending on the value of orientation. Ideally, 446 * components should handle a partially exposed row or column by 447 * returning the distance required to completely expose the item. 448 * <p> 449 * Scrolling containers, like JScrollPane, will use this method 450 * each time the user requests a unit scroll. 451 * 452 * @param visibleRect The view area visible within the viewport 453 * @param orientation Either SwingConstants.VERTICAL or 454 * SwingConstants.HORIZONTAL. 455 * @param direction Less than zero to scroll up/left, 456 * greater than zero for down/right. 457 * @return The "unit" increment for scrolling in the specified direction. 458 * This value should always be positive. 459 */ 460 public int getScrollableUnitIncrement ( 461 final Rectangle visibleRect, 462 final int orientation, 463 final int direction) 464 { 465 return (UNIT_INCREMENT); 466 } 467 468 469 /** 470 * Components that display logical rows or columns should compute 471 * the scroll increment that will completely expose one block 472 * of rows or columns, depending on the value of orientation. 473 * <p> 474 * Scrolling containers, like JScrollPane, will use this method 475 * each time the user requests a block scroll. 476 * 477 * @param visibleRect The view area visible within the viewport 478 * @param orientation Either SwingConstants.VERTICAL or 479 * SwingConstants.HORIZONTAL. 480 * @param direction Less than zero to scroll up/left, 481 * greater than zero for down/right. 482 * @return The "block" increment for scrolling in the specified direction. 483 * This value should always be positive. 484 */ 485 public int getScrollableBlockIncrement ( 486 final Rectangle visibleRect, 487 final int orientation, 488 final int direction) 489 { 490 return (BLOCK_INCREMENT); 491 } 492 493 494 /** 495 * Return true if a viewport should always force the width of this 496 * <code>Scrollable</code> to match the width of the viewport. 497 * For example a normal 498 * text view that supported line wrapping would return true here, since it 499 * would be undesirable for wrapped lines to disappear beyond the right 500 * edge of the viewport. Note that returning true for a Scrollable 501 * whose ancestor is a JScrollPane effectively disables horizontal 502 * scrolling. 503 * <p> 504 * Scrolling containers, like JViewport, will use this method each 505 * time they are validated. 506 * 507 * @return <code>true</code> if a viewport should force the Scrollables 508 * width to match its own. 509 */ 510 public boolean getScrollableTracksViewportWidth () 511 { 512 return (false); 513 } 514 515 /** 516 * Return true if a viewport should always force the height of this 517 * Scrollable to match the height of the viewport. For example a 518 * columnar text view that flowed text in left to right columns 519 * could effectively disable vertical scrolling by returning 520 * true here. 521 * <p> 522 * Scrolling containers, like JViewport, will use this method each 523 * time they are validated. 524 * 525 * @return <code>true</code> if a viewport should force the Scrollables 526 * height to match its own. 527 */ 528 public boolean getScrollableTracksViewportHeight () 529 { 530 return (false); 531 } 532 533 // 534 // ComponentListener interface 535 // 536 537 /** 538 * Invoked when the container's size changes. 539 * Un-caches the preferred size. 540 * @param event The resize event. 541 */ 542 public void componentResized (final ComponentEvent event) 543 { 544 setPreferredSize (null); 545 } 546 547 /** 548 * Invoked when the component's position changes. 549 * <i>Not used.</I> 550 * @param event The component event. 551 */ 552 public void componentMoved (final ComponentEvent event) 553 { 554 } 555 556 /** 557 * Invoked when the component has been made visible. 558 * <i>Not used.</I> 559 * @param event The component event. 560 */ 561 public void componentShown (final ComponentEvent event) 562 { 563 } 564 565 /** 566 * Invoked when the component has been made invisible. 567 * <i>Not used.</I> 568 * @param event The component event. 569 */ 570 public void componentHidden (final ComponentEvent event) 571 { 572 } 573 574 // 575 // HierarchyListener interface 576 // 577 578 /** 579 * Handles this components ancestor being added to a container. 580 * Registers this component as a listener for size changes on the 581 * ancestor so that we may un-cache the prefereed size and force 582 * a recalculation. 583 * @param event The heirarchy event. 584 */ 585 public void hierarchyChanged (final HierarchyEvent event) 586 { 587 if (0 != (event.getChangeFlags () & HierarchyEvent.PARENT_CHANGED)) 588 { 589 Component dad = event.getChanged (); 590 Component parent = getParent (); 591 if ((null != parent) && (parent.getParent () == dad)) 592 dad.addComponentListener (this); 593 } 594 } 595 } 596 597 /* 598 * Revision Control Modification History 599 * 600 * $Log: PicturePanel.java,v $ 601 * Revision 1.2 2005/04/12 11:27:41 derrickoswald 602 * Documentation revamp part two. 603 * 604 * Revision 1.1 2003/09/21 18:20:56 derrickoswald 605 * Thumbelina 606 * Created a lexer GUI application to extract images behind thumbnails. 607 * Added a task in the ant build script - thumbelina - to create the jar file. 608 * You need JDK 1.4.x to build it. It can be run on JDK 1.3.x in crippled mode. 609 * Usage: java -Xmx256M thumbelina.jar [URL] 610 * 611 * 612 */