JDK-4193267 : JList getLastVisibleIndex
  • Type: Bug
  • Component: client-libs
  • Sub-Component: javax.swing
  • Affected Version: 1.2.0,1.3.0
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic,windows_98
  • CPU: generic,x86
  • Submitted: 1998-11-27
  • Updated: 2001-01-03
  • Resolved: 2001-01-03
Related Reports
Duplicate :  
Relates :  
Description

Name: diC59631			Date: 11/27/98


There appears to be a bug with the JList getListVisibleIndex()
method which claims to return the index of the cell at the lower 
right corner or -1 if nothing in visible or the list is empty. 

    

    This behaviour however fails if this viewable area of the JList
is greater than the total size of the all the components in the 
list. Under such circumstance the JList reports -1 despite their 
still being a viewable cell on the screen.


      

      I include the following code. Its rather long Im afraid. The crucial
method is updateCurrentlyViewedImages() which is the last method. If
the JViewPort is larger than the JList contents the currentlyDisplayed
array list will be cleared although it should have all of the cells in it.


package phtfitdev;

import javax.swing.*;
import javax.swing.event.*;
import java.util.*;
import phtfitdev.datastructs.*;
import java.lang.reflect.InvocationTargetException;


/**
 * SyndromeThumbViewer.java <P>
 *
 * This class is essentially an embedded JList. It
 * is used to display the thumb nails of the syndromes which are
 * passed to it.
 * <P>
 * Compliant: 1.2
 * <P>
 * Created: Tue Sep 22 16:44:12 1998
 *
 * @author Phillip Lord
 * @version 1.0
 */

public class SyndromeThumbViewer extends JScrollPane 
  implements ImageObjectObserver, ChangeListener, ListDataListener
{
  private JList list;
  private ChunkyListModel model;
  private Vector storedSyndromes;
  private static String noImageWarning = new String ( " :- No Images" );
  private InternalEventQueue qu;
  

  
  public SyndromeThumbViewer()
  {
    //Instantiate model and Jlist, with view
    model = new ChunkyListModel( );
    model.addListDataListener( this );
    list = new JList( model );
    getViewport().setView( list );
    getViewport().addChangeListener( this );
    //This Jlist uses a specialised cell rendered, which places both
    //the image and descript onto a JLabel. The thumbs are of a fixed
    //height, so the cells might as well be. 
    list.setCellRenderer( new SyndromeThumbViewerCellRenderer( this ) );
    list.setFixedCellHeight( 100 );
    //This is set at an arbritarily wide value. It speeds up the processing
    //of the Jlist esp. when there are a lot of syndromes. It has the disadvantage
    //that the bottom scroll bar will reflect the arbritarily large width created
    //but this is not a particularly big disadvantage
    list.setFixedCellWidth( 2000 );
    //Instantiate the loader thread which loads Syndromes onto this 
    //list
    qu = new InternalEventQueue();
  }

  public void addSyndrome( SyndromeDataObject syndrome )
  {
    qu.enqueue( new InternalEvent( this, "expandSyndrome", syndrome, "addSyndrome1" ) );
  }
  
  public void addSyndrome( SyndromeDataObject[] syndrome )
  {
    for ( int i = 0; i < syndrome.length; i++ ){
      addSyndrome( syndrome[ i ] );
    }
  }
  
  /**
   * Several methods use this method. It expands a SyndromeDataObject and returns
   * an ImageDataObject array, or a String. Its a pretty slow method (at least if
   * the ImageDataObject array has not been retrieved before) and is meant to be 
   * used as the slow method in the internal event queue
   * @param synd the syndrome data object typed as an Object
   * @return an ImageDataObject or a String
   */
  Object expandSyndrome( Object synd )
  {
    SyndromeDataObject syndrome = (SyndromeDataObject)synd;
    //This bit requires lots of database access. V.Slow
    ImageDataObject[] imgs = syndrome.getImages();
    //If there are no images return the description
    if ( imgs == null ) return syndrome.getDescript() + noImageWarning;
    //If there are images return the array
    return imgs;
  }
  
  
  void addSyndrome1( Object images )
  {
    if ( images instanceof String ){
      model.addElement( (String)images );
      return;
    }
    model.addAll( (ImageDataObject[])images );
  }
  
 
  public void removeSyndrome( SyndromeDataObject[] syndrome )
  {
    for ( int i = 0; i < syndrome.length; i++ ){
      removeSyndrome( syndrome[ i ] );
    }
  }
  
  public void removeSyndrome( SyndromeDataObject syndrome )
  {
    qu.enqueue( new InternalEvent( this, "expandSyndrome", syndrome, "removeSyndrome1" ) );
  }

  void removeSyndrome1( Object images )
  {
    if ( images instanceof String ){
      model.removeElement( (String)images );
      return;
    }
    model.removeAll( (ImageDataObject[])images );
  }
  
  public void clearSyndromes()
  {
    qu.makeEmpty();
    model.clear();
  }

  
  public ImageDataObject[] getSelectedImages()
  {
    Object[] values = list.getSelectedValues();
    ImageDataObject[] img = new ImageDataObject[ values.length ];
    for ( int i = 0; i < img.length; i++ ){
      img[ i ] = (ImageDataObject)values[ i ];
    }
    
    return img;
  }
  
  
  /**
   * This method is predominately for debugging. It returns an object array
   * of the current contents of the JList, which will be either ImageDataObjects
   * or the description of the the syndrome
   * @return the contents
   */
  public Object[] getContents()
  {
    
    return model.toArray();
  }

  int count;
  
  public void imageObjectUpdate( ImageDataObject image )
  {
    //Get an array (arraylist is not sync'd )
    Object[] displaying = currentlyDisplayed.toArray();
    //Scan thru and repaint if necessary
    for ( int i = 0; i < displaying.length; i++ ){
      if ( displaying[ i ] == image ) {
	System.out.println (count++ + "Image has updated and repaint will occur" );
	repaint();
	return;
      }
    }
    System.out.println ( count++ + "Image has updated but repaint is not happening" );
    System.out.println( "Image updated is " + image.getSyndrome().getDescript() );
    //    System.out.println( ((ImageDataObject)displaying[ 0 ]).getSyndrome().getDescript() );
    System.out.println ("displaying is " + displaying.length + " long" );
    if ( displaying.length > 0 ) System.out.println ( ((ImageDataObject)displaying[ 0 ]).getSyndrome().getDescript() );
  }

  //The next set of methods are used to maintain a list of the currently
  //viewed images, so that repaint can be called intelligently when 
  //an Imageupdate occurs. It has the side effect that the currently viewed
  //images are referenced in an ArrayList which means that they wont 
  //get GC'd in the mean time.
  
  //This method is called in response to changes in the JViewPort which 
  //is being viewed, and is required byte the ChangeListener interface
  public void stateChanged( ChangeEvent e )
  {
    updateCurrentlyViewedImages();
  }
  
  //One of these methods occurs in response to changes in the list data model
  //and are required byte the ListDataListener interface
  public void contentsChanged( ListDataEvent e )
  {
    updateCurrentlyViewedImages();
  }
  
  public void intervalAdded( ListDataEvent e )
  {
    updateCurrentlyViewedImages();
  }
  
  public void intervalRemoved( ListDataEvent e )
  {
    updateCurrentlyViewedImages();
  }
  
  
  //Store the previous values here. Start off at -1 which means empty JList or 
  //nothing viewable...
  private int firstVisibleIndex = -1;
  private int lastVisibleIndex = -1;
  private ArrayList currentlyDisplayed = new ArrayList();
  //Update the images currently on the screen
  void updateCurrentlyViewedImages()
  {
    
    //If selection has changed
    if ( list.getFirstVisibleIndex() != firstVisibleIndex || list.getLastVisibleIndex() != lastVisibleIndex ){
      //Store references to all of the ImageIcons, preventing them from GC'ing 
      //as they are normally stored as Soft references
      
      //Clear the old images
      currentlyDisplayed.clear();
      int firstIndex = list.getFirstVisibleIndex();
      int lastIndex = list.getLastVisibleIndex();
      
      /*THIS BIT IS MY WORKAROUND FOR THE BUG
      
      if ( lastIndex == -1 &&  firstIndex != -1  ){
	lastIndex = model.getSize() - 1;
      }
      HERE ENDS THE WORKAROUND FOR THE BUG*/            

      //There are still some images left      
      if ( firstIndex !=  -1 ){
	System.out.println ("The currently displayed images are at indexes " 
			    + firstIndex + " and " + lastIndex );
	for ( int i = firstIndex ; i < lastIndex + 1; i++ ){
	  currentlyDisplayed.add( model.elementAt( i ) );
	  if ( model.elementAt( i ) instanceof ImageDataObject ){
	    System.out.println ( ((ImageDataObject)model.elementAt( i )).getSyndrome().getDescript() );
	  }
	  
	}
      }
    }
  }
  
  
} // SyndromeThumbViewer



     This bug appears to be to be identical to 4109697 however as that bug 
is reported as fixed it can't be.
(Review ID: 43427)
======================================================================

Comments
WORK AROUND Name: diC59631 Date: 11/27/98 I would suggest the following work-around int lastIndex = list.getLastVisibleIndex(); if ( lastIndex == -1 && list.getFirstVisibleIndex() != -1 ){ lastIndex == model.getSize() - 1; } or as a fix within the Swing code itself. public int getLastVisibleIndex() { Rectangle r = getVisibleRect(); Point visibleLR = new Point((r.x + r.width) - 1, (r.y + r.height) - 1); int index = locationToIndex( visibleLR ) ; if ( index == -1 && getFirstVisibleIndex() == -1 ) return -1; if ( index == -1 ) return listModel.getSize() -1; return index; } -1 will only be returned if getFirstVisibleIndex == -1 which means empty list or nothing on screen. ======================================================================
11-06-2004

EVALUATION Hans, you'll have to make the call on this. If you like the current behavior, than I'ld say the javadoc should be updated a bit to better indicate this. scott.violet@eng 1999-09-20 The correct solution to this bug will be more difficult than the current suggested fix because, in 1.4, support for newspaper style column layout has been added. If the new layoutOrientation property is JList.HORIZONTAL, then the lower right hand corner of the visible rectangle may not overlap a cell, even when the last cell is NOT visible: Here's an example 1 5 9 2 6 10 +-----------+ 3 | 7 | | | 4 | 8 | +-----------+ This list has 10 cells and is layed out JList.HORIZONTAL. The box encloses the visible rectangle and the lower right corner doesn't include any cell. In this case the last visible cell index is 8. I believe the correct algorithm is this: Find the first valid index (not -1) from: - The index of the cell that contains the lower right hand corner of the visible rectangle. This is the current algorithm. - The index of the last cell if the intersection of the last cells bounds rectangle (getCellBounds) and the visible rectangle isn't empty. - The index of the cell that overlaps the lower left corner of the visible rectangle. And the last clause only needs to be applied when when layoutOrientation is JList.HORIZONTAL. hans.muller@Eng 2000-12-21 I am closing this out as a duplicate of 4308384. In adding support for horizontaly layout this was fixed. scott.violet@eng 2001-01-03
21-12-2000

SUGGESTED FIX ------- JList.java ------- *** /tmp/d2L14X_ Tue May 9 15:45:35 2000 --- JList.java Tue May 9 15:37:47 2000 *************** *** 735,741 **** public int getLastVisibleIndex() { Rectangle r = getVisibleRect(); Point visibleLR = new Point((r.x + r.width) - 1, (r.y + r.height) - 1); ! return locationToIndex(visibleLR); } --- 735,751 ---- public int getLastVisibleIndex() { Rectangle r = getVisibleRect(); Point visibleLR = new Point((r.x + r.width) - 1, (r.y + r.height) - 1); ! int lastVI = locationToIndex(visibleLR); ! if (lastVI==-1) { ! int maxIndex = getModel().getSize()-1; ! if (maxIndex>=0) { ! Rectangle maxIndexRect = getCellBounds(maxIndex, maxIndex); ! if (maxIndexRect.y + maxIndexRect.height > r.y) { ! lastVI = maxIndex; ! } ! } ! } ! return lastVI; } andrey.pikalev@Eng 2000-05-09
09-05-2000