Selecting a JList item by double-clicking or pressing Enter

For detecting double-clicking, add a MouseListener and catch the MouseEvents where clickCount is equal to 2.
For detecting the enter key, add a KeyListener and check whether the KeyCode is KeyEvent.VK_ENTER.

Here’s an example:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
  
public class Main extends JFrame {
 
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      Vector v = new Vector();
      for (int i=0; i<50; i++) {
         v.addElement("Item #" + i);
      }
      getContentPane().add(new JLabel("Double-clicked on: "));
      final JTextField dblTextField = new JTextField(15);
      getContentPane().add(dblTextField);
      getContentPane().add(new JLabel("Enter key on: "));
      final JTextField entTextField = new JTextField(15);
      getContentPane().add(entTextField);
      final JList list = new JList(v);
 
      // catch double-click events
      list.addMouseListener(new MouseAdapter() {
         public void mouseClicked(MouseEvent me) {
            if (me.getClickCount() == 2) {
               dblTextField.setText(""+list.getSelectedValue()); 
            }
         }
      });
 
      // catch enter-key events
      list.addKeyListener(new KeyAdapter() { 
         public void keyReleased(KeyEvent ke) { 
            if (ke.getKeyCode() == KeyEvent.VK_ENTER) { 
               entTextField.setText(""+list.getSelectedValue());
            }
         }
      });
 
      getContentPane().add(new JScrollPane(list));    
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      setSize(200, 300);
   }
   
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Displaying the tick value of a JSlider as the slider is being moved

Add a ChangeListener to your JSlider and implement the method stateChanged. Here’s an example:

Main.java:

import javax.swing.event.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new FlowLayout());
    
      // notice this components is final because
      // it is being used in an inner class
      final JTextField value = new JTextField(5);
 
      final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 30, 15);
      // draw the major tick marks (one for every tick label)
      slider.setMajorTickSpacing(10);
      // draw the minor tick marks (between the tick labels)
      slider.setMinorTickSpacing(1);
      // draw the tick marks
      slider.setPaintTicks(true);
      // draw the tick mark labels
      slider.setPaintLabels(true);
 
      slider.addChangeListener(new ChangeListener() {
         public void stateChanged(ChangeEvent ce) {
            value.setText(""+slider.getValue());
         }
      });
 
      getContentPane().add(value);
      getContentPane().add(slider);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Limiting the length of text entered in a JTextField

Create a custom component that extends from JTextField and allow the user to specify a maximum length. The implementation is simple: capture the insertString method and execute super.insertString when the condition (current length < = maximumlength) is met.

Main.java:

import javax.swing.text.*;
import java.awt.event.*;
import javax.swing.*;
import java.text.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      getContentPane().add(new LimitedTextField("Try typing more", 15, 15));
      getContentPane().add(new LimitedTextField(10, 3));
  
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      setSize(300, 300);
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}
 
class LimitedTextField extends JTextField
{
   private int maxCols = 0;
 
   public LimitedTextField(int maxCols) {
      super();
      this.maxCols = maxCols;
   }
 
   public LimitedTextField(int cols, int maxCols) {
      super(cols);
      this.maxCols = maxCols;
   }
  
   public LimitedTextField(String text, int maxCols) {
      super();
      this.maxCols = maxCols;
      setText(text);
   } 
 
   public LimitedTextField(String text, int cols, int maxCols) {
      super(cols);
      this.maxCols = maxCols;
      setText(text);
   }
 
   protected Document createDefaultModel() {
      return new LimitedDocument();
   }
  
   protected class LimitedDocument extends PlainDocument {
      public void insertString(int offset, String str, AttributeSet as) 
            throws BadLocationException  {
         if (str == null || getLength() + str.length() <= maxCols) {
            super.insertString(offset, str, as);
         }
         else {
            int remainder = maxCols – getLength();
            if (remainder > 0) {
               super.insertString(offset, str.substring(0, remainder), as);
            }
         }
      }
   }
}

Displaying the EURO sign in a JLabel

JLabel is able to render HTML (3.2). You can use the code € inside html tags as shown in following example:

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.text.*;
import java.awt.*;
   
public class Main extends JFrame {
   public Main() {
      getContentPane().setLayout(new FlowLayout());
 
      JLabel label = new JLabel("<html>&#8364;</html>");
      getContentPane().add(label);     
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent event) {
            System.exit(0);   
         }      
      });
 
      pack();
   }
 
   public static void main(String[] args) {
      (new Main()).show();
   }
}

Changing the font of all tooltips

You can make a global change to the appearance of several components by using the UIManager class.
To change the font of your ToolTips, set the key ToolTip.font to your desired font.

The easiest way to change the font of only one of your tooltips is to use HTML tags.

Here’s an example of both:

Main.java:

import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.awt.*;
 
public class Main extends JFrame {
   public Main() {
      super("JToolTip Change Fonts Demonstration");
 
      getContentPane().setLayout(new GridLayout(2, 1, 30, 30)); 
 
      UIManager.put("ToolTip.font",new Font("SansSerif", Font.ITALIC, 36)); 
 
      JLabel labelSmall = new JLabel("Label with a large ToolTip");
      labelSmall.setToolTipText("Tooltip with a large font");
 
      JLabel labelLarge = new JLabel("Label with an HTML ToolTip");
      labelLarge.setToolTipText("<html><font color=red> HTML ToolTip </font></html>");
         
      getContentPane().add(labelSmall);
      getContentPane().add(labelLarge);
 
      addWindowListener(new WindowAdapter() {
         public void windowClosing(WindowEvent e) {
            System.exit(0);
         }
      });
 
      setSize(450, 300);
   }
 
   public static void main(String[] args) {
      Main main = new Main();
      main.setVisible(true);
   }
}

Preventing the user from renaming files/directories in a JFileChooser

This is a hard task. I sat 3 hours to solve the problem for Jbuzzer. I do not know, if I solved the problem in a good way but it works.

Problem

The problem is, that deep inside of the UI of the JFileChooser – at the level of concrete implementation of the Look & Feel – the internal javax.swing.JList is instantiated without even keeping a member handle to it. It is done within a method that instantiates it, configures it (e.g.: with mouse listeners), adds it in a JPanel and returns that one.

The java jdk source code (1.4)

public class MetalFileChooserUI extends BasicFileChooserUI{
   private JPanel listViewPanel;
 
   public void installComponents(JFileChooser fc) {
      ...
      listViewPanel = createList(fc);
      ...
   }
 
   // A big don't: Hardcoded listeners 
   // in the UI implementation.
   protected JPanel createList(JFileChooser fc) {
      list = new JList() { <anonymous initialisation >};
      ...
      <b>list.addMouseListener(createSingleClickListener(fc,list));</b>
      ...
   }

   private MouseListener reateSingleClickListener(JFileChooser fc, JList list) 
   {
      return new SingleClickListener(list);
   }
 
   protected class SingleClickListener extends MouseAdapter {
      JList list;
      public void mouseClicked(MouseEvent e) {
         if (SwingUtilities.isLeftMouseButton(e)) {
            if (e.getClickCount() == 1) {
               JFileChooser fc = getFileChooser();
               int index = list.locationToIndex(e.getPoint());
               if ((!fc.isMultiSelectionEnabled() 
                   || fc.getSelectedFiles().length <= 1)
                   && index >= 0 
                   && list.isSelectedIndex(index)
                   && getEditIndex() == index 
                   && editFile == null) {
                      editFileName(index);
                   } 
               else {
                   ...
               }
            }

Solution

  1. Create your JFileChooser.

  • Run a search for it’s Component child of type JList.
  • Get the MouseListener of this JList whose class name contains “SingleClick” (I did not search other plaf packages: they might do the same bad thing but add the MouseListener for editing the JList in another way. This bugfix will only work for javax.swing.plaf.metal).
  • Remove that MouseListener from the retrieved JList.

    Code

    import javax.swing.JList;
    import java.awt.Component;
    import java.awt.Container;
    import javax.swing.JFileChooser;
    import java.awt.event.MouseListener;
     
    public class somename {
       ...
       /**
       * <p>
       * Hack to get the {@link List} of the {@link JFileChooser} 
       * instance used for loading sounds: 
       * We have to remove the "Single Click" listener 
       * that allows renaming files.
       * </p>
       */
       private JList searchJList(Container fileChooser)
       {
          JList ret = null;
          // First check, wether i am a JList:
          if (fileChooser instanceof JList){
              ret = (JList)fileChooser;
          }
          // Ok, me not: let's ask the children.
          else {
             Component[] children = fileChooser.getComponents();
             for(int i=children.length-1;i>=0;i--) {
                if (children[i] instanceof Container) {
                   ret = searchJList((Container)children[i]);
                   if(ret != null) {
                      break;
                   }
                }
             }
          }
    
          return ret;
       }
     
       // Just demo code! 
       public static void main(String[]args){
          JFileChooser load = new JFileChooser("/home/user/...");
          JList list = searchJList(this.openDialog);
          if (list!=null) {
             String listenerClassName;
             MouseListener[] listeners = list.getMouseListeners();
             for (int i=0;i< listeners.length;i++) {
                listenerCName = listeners[i].getClass().getName();
                if(listenerCName.indexOf("SingleClick")!= -1) {      
                   list.removeMouseListener(mouseListeners[i]);
                   break;
                }
             }
          }
          // Show the file chooser.
       }
    }
    

    Further examinations of other L&F packages could require changes. This has only be tested with javax.swing.plaf.metal.

    See the working example in Jbuzzer, a small fun-application for mapping sounds to keystrokes: http://sourceforge.net/projects/jbuzzer/.

  • Allowing only individual rows to be selected in a JTable

    By default, multiple rows can be selected in a Table. You can change this behavior through a ListSelectionModel. Invoke the method setSelectionMode on your JTable object and set it to one of these three static final variables defined in ListSelectionModel:

    MULTIPLE_INTERVAL_SELECTION: to allow multiple contiguous selection ranges
    SINGLE_INTERVAL_SELECTION: to allow one contiguous selection range
    SINGLE_SELECTION: to allow only one list index selection

    Add a ListSelectionListener and define the behaviour in valueChanged.

    Here’s a working example:

    import javax.swing.event.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.awt.*;
     
    public class Main extends JFrame 
    {
       public Main() {
          super("Table example, Wines from Bordeaux");
     
          Object[][] tabledata = {
                { "Chateau Meyney, St. Estephe", 	   new Integer(1994), "$18.75"},
                { "Chateau Montrose, St. Estephe", 	   new Integer(1975), "$54.25" },
                { "Chateau Gloria, St. Julien", 	   new Integer(1993), "$22.99" },
                { "Chateau Beychevelle, St. Julien",   new Integer(1970), "$61.63" },
                { "Chateau La Tour de Mons, Margeaux", new Integer(1975), "$57.03" },
                { "Chateau Brane-Cantenac, Margeaux",  new Integer(1978), "$49.92" },
          };
     
          String columnheaders[] = { "Wine", "Vintage", "Price" };
     
          JTable table = new JTable(tabledata, columnheaders);
          table.setPreferredScrollableViewportSize(new Dimension(500, 70));
          JScrollPane scrollPane = new JScrollPane(table);
     
          table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
          ListSelectionModel listSelectionModel = table.getSelectionModel();
          listSelectionModel.addListSelectionListener(new ListSelectionListener() {
             public void valueChanged(ListSelectionEvent e) {
     
                // ignore clicks when user is still making his selection,
                // eg. dragging the mouse
                if (e.getValueIsAdjusting()) return;
                      
                ListSelectionModel lsm = (ListSelectionModel) e.getSource();
                if (lsm.isSelectionEmpty()) {
                   System.out.println("No selection!");
                } else {
                   int selectedRow = lsm.getMinSelectionIndex();
                   System.out.println("Row selected: " + selectedRow);
                }
     
             } 
          });
      
          getContentPane().add(scrollPane);
     
          addWindowListener(new WindowAdapter() {
             public void windowClosing(WindowEvent we) {
                System.exit(0);
             }
          });
     
          pack();
       }
     
       public static void main(String []args) {
          Main main = new Main();
          main.show();
       }
    }
    

    Creating a JTable with multiple row headers

    Courtesy of Nobuo Tamemasa (http://www2.gol.com/users/tame/swing/examples/JTableExamples1.htm7)



    MultipleRowHeaderExample.java:

    import java.awt.*;
    import java.awt.event.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import javax.swing.event.*;
       
    /*   ----------------------------------------------
     *  |         SNo.        |
     *   ----------------------------------------------
     *  |          |     1    |
     *  |   Name   |-----------------------------------
     *  |          |     2    |
     *   ----------------------------------------------
     *  |          |     1    |
     *  |          |-----------------------------------
     *  | Language |     2    |
     *  |          |-----------------------------------
     *  |          |     3    |
     *   ----------------------------------------------
     */
     
    /**
     * @version 1.0 03/06/99
     */
    public class MultipleRowHeaderExample extends JFrame {
      Object[][] data;
      Object[] column;
      JTable table;
      MultiSpanCellTable fixedTable;
     
      public MultipleRowHeaderExample() {
        super( "Multiple Row Header Example" );
        setSize( 400, 150 );
        
        data =  new Object[][]{
            {"SNo."    ,"" },
            {"Name"    ,"1"},
            {""        ,"2"},
            {"Language","1"},
            {""        ,"2"},
            {""        ,"3"}};
        column = new Object[]{"",""};
        
        AttributiveCellTableModel fixedModel = new AttributiveCellTableModel(data, column) {
          public boolean CellEditable(int row, int col) { 
            return false; 
          }
        };
            
        CellSpan cellAtt =(CellSpan)fixedModel.getCellAttribute();
        cellAtt.combine(new int[] {0}    ,new int[] {0,1});
        cellAtt.combine(new int[] {1,2}  ,new int[] {0});
        cellAtt.combine(new int[] {3,4,5},new int[] {0});
        
        DefaultTableModel    model = new DefaultTableModel(data.length, 3);
            
        fixedTable = new MultiSpanCellTable( fixedModel );
        table = new JTable( model );
        fixedTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        fixedTable.setDefaultRenderer(Object.class, new RowHeaderRenderer(fixedTable));
        fixedTable.setGridColor(table.getTableHeader().getBackground());
        
        JScrollPane scroll = new JScrollPane( table );
        JViewport viewport = new JViewport();
        viewport.setView(fixedTable);
        viewport.setPreferredSize(fixedTable.getPreferredSize());
        scroll.setRowHeaderView(viewport);
        
        getContentPane().add(scroll, BorderLayout.CENTER);
      }
     
      public static void main(String[] args) {
        MultipleRowHeaderExample frame = new MultipleRowHeaderExample();
        frame.addWindowListener( new WindowAdapter() {
          public void windowClosing( WindowEvent e ) {
            System.exit(0);
          }
        });
        frame.setVisible(true);
      }
      
      class RowHeaderRenderer extends JLabel implements TableCellRenderer {  
        RowHeaderRenderer(JTable table) {
          JTableHeader header = table.getTableHeader();
          setOpaque(true);
          setBorder(UIManager.getBorder("TableHeader.cellBorder"));
          setHorizontalAlignment(CENTER);
          setForeground(header.getForeground());
          setBackground(header.getBackground());
          setFont(header.getFont());
        }
       
        public Component getTableCellRendererComponent(JTable table, Object value,
                              boolean isSelected, boolean hasFocus, int row, int column) {
          setText((value == null) ? "" : value.toString());
          return this;
        }
      }
      
    }
    

    AttributiveCellTableModel.java:

    import java.util.*;
    import java.awt.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import javax.swing.event.*;
     
    /**
     * @version 1.0 11/22/98
     */
     
    public class AttributiveCellTableModel extends DefaultTableModel {
     
      protected CellAttribute cellAtt;
         
      public AttributiveCellTableModel() {
        this((Vector)null, 0);
      }
      public AttributiveCellTableModel(int numRows, int numColumns) {
        Vector names = new Vector(numColumns);
        names.setSize(numColumns);
        setColumnIdentifiers(names);
        dataVector = new Vector();
        setNumRows(numRows);
        cellAtt = new DefaultCellAttribute(numRows,numColumns);
      }
      public AttributiveCellTableModel(Vector columnNames, int numRows) {
        setColumnIdentifiers(columnNames);
        dataVector = new Vector();
        setNumRows(numRows);
        cellAtt = new DefaultCellAttribute(numRows,columnNames.size());
      }
      public AttributiveCellTableModel(Object[] columnNames, int numRows) {
        this(convertToVector(columnNames), numRows);
      }  
      public AttributiveCellTableModel(Vector data, Vector columnNames) {
        setDataVector(data, columnNames);
      }
      public AttributiveCellTableModel(Object[][] data, Object[] columnNames) {
        setDataVector(data, columnNames);
      }
         
      public void setDataVector(Vector newData, Vector columnNames) {
        if (newData == null)
          throw new IllegalArgumentException("setDataVector() - Null parameter");
        dataVector = new Vector(0);
        setColumnIdentifiers(columnNames);
        dataVector = newData;
         
        //
        cellAtt = new DefaultCellAttribute(dataVector.size(),
                                           columnIdentifiers.size());
         
        newRowsAdded(new TableModelEvent(this, 0, getRowCount()-1,
                     TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
      }
     
      public void addColumn(Object columnName, Vector columnData) {
        if (columnName == null)
          throw new IllegalArgumentException("addColumn() - null parameter");
        columnIdentifiers.addElement(columnName);
        int index = 0;
        Enumeration enumeration = dataVector.elements();
        while (enumeration.hasMoreElements()) {
          Object value;
          if ((columnData != null) && (index < columnData.size()))
              value = columnData.elementAt(index);
          else
            value = null;
          ((Vector)enumeration.nextElement()).addElement(value);
          index++;
        }
     
        //
        cellAtt.addColumn();
    
        fireTableStructureChanged();
      }
     
      public void addRow(Vector rowData) {
        Vector newData = null;
        if (rowData == null) {
          newData = new Vector(getColumnCount());
        }
        else {
          rowData.setSize(getColumnCount());
        }
        dataVector.addElement(newData);
     
        //
        cellAtt.addRow();
     
        newRowsAdded(new TableModelEvent(this, getRowCount()-1, getRowCount()-1,
           TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
      }
     
      public void insertRow(int row, Vector rowData) {
        if (rowData == null) {
          rowData = new Vector(getColumnCount());
        }
        else {
          rowData.setSize(getColumnCount());
        }
     
        dataVector.insertElementAt(rowData, row);
     
        //
        cellAtt.insertRow(row);
     
        newRowsAdded(new TableModelEvent(this, row, row,
           TableModelEvent.ALL_COLUMNS, TableModelEvent.INSERT));
      }
      
      public CellAttribute getCellAttribute() {
        return cellAtt;
      }
     
      public void setCellAttribute(CellAttribute newCellAtt) {
        int numColumns = getColumnCount();
        int numRows    = getRowCount();
        if ((newCellAtt.getSize().width  != numColumns) ||
            (newCellAtt.getSize().height != numRows)) {
          newCellAtt.setSize(new Dimension(numRows, numColumns));
        }
        cellAtt = newCellAtt;
        fireTableDataChanged();
      }
     
      /*
      public void changeCellAttribute(int row, int column, Object command) {
        cellAtt.changeAttribute(row, column, command);
      }
     
      public void changeCellAttribute(int[] rows, int[] columns, Object command) {
        cellAtt.changeAttribute(rows, columns, command);
      }
      */
    }
    

    DefaultCellAttribute.java:

    import java.awt.*;
     
    /**
     * @version 1.0 11/22/98
     */
     
    public class DefaultCellAttribute 
    //    implements CellAttribute ,CellSpan  {
          implements CellAttribute ,CellSpan ,ColoredCell ,CellFont {
     
      //
      // !!!! CAUTION !!!!!
      // these values must be synchronized to Table data
      //
      protected int rowSize;
      protected int columnSize;
      protected int[][][] span;                   // CellSpan
      protected Color[][] foreground;             // ColoredCell
      protected Color[][] background;             //
      protected Font[][]  font;                   // CellFont
       
      public DefaultCellAttribute() {
        this(1,1);
      }
       
      public DefaultCellAttribute(int numRows, int numColumns) {
        setSize(new Dimension(numColumns, numRows));
      }
     
      protected void initValue() {
        for(int i=0; i<span.length;i++) {
          for(int j=0; j<span[i].length; j++) {
            span[i][j][CellSpan.COLUMN] = 1;
            span[i][j][CellSpan.ROW]    = 1;
          }
        }
      }
     
     
      //
      // CellSpan
      //
      public int[] getSpan(int row, int column) {
        if (isOutOfBounds(row, column)) {
          int[] ret_code = {1,1};
          return ret_code;
        }
        return span[row][column];
      }
     
      public void setSpan(int[] span, int row, int column) {
        if (isOutOfBounds(row, column)) return;
        this.span[row][column] = span;
      }
           
      public boolean isVisible(int row, int column) {
        if (isOutOfBounds(row, column)) return false;
        if ((span[row][column][CellSpan.COLUMN] < 1)
          ||(span[row][column][CellSpan.ROW]    < 1)) return false;
        return true;
      }
     
      public void combine(int[] rows, int[] columns) {
        if (isOutOfBounds(rows, columns)) return;
        int    rowSpan  = rows.length;
        int columnSpan  = columns.length;
        int startRow    = rows[0];
        int startColumn = columns[0];
        for (int i=0;i<rowSpan;i++) {
          for (int j=0;j<columnSpan;j++) {
            if ((span[startRow +i][startColumn +j][CellSpan.COLUMN] != 1)
              ||(span[startRow +i][startColumn +j][CellSpan.ROW]    != 1)) {
              //System.out.println("can't combine");
              return ;
            }
          }
        }
        for (int i=0,ii=0;i<rowSpan;i++,ii--) {
          for (int j=0,jj=0;j<columnSpan;j++,jj--) {
            span[startRow +i][startColumn +j][CellSpan.COLUMN] = jj;
            span[startRow +i][startColumn +j][CellSpan.ROW]    = ii;
            //System.out.println("r " +ii +"  c " +jj);
          }
        }
        span[startRow][startColumn][CellSpan.COLUMN] = columnSpan;
        span[startRow][startColumn][CellSpan.ROW]    =    rowSpan;
        
      }
     
      public void split(int row, int column) {
        if (isOutOfBounds(row, column)) return;
        int columnSpan = span[row][column][CellSpan.COLUMN];
        int    rowSpan = span[row][column][CellSpan.ROW];
        for (int i=0;i<rowSpan;i++) {
          for (int j=0;j<columnSpan;j++) {
            span[row +i][column +j][CellSpan.COLUMN] = 1;
            span[row +i][column +j][CellSpan.ROW]    = 1;
          }
        }
      }
     
     
      //
      // ColoredCell
      //
      public Color getForeground(int row, int column) {
        if (isOutOfBounds(row, column)) return null;
        return foreground[row][column];
      }
      public void setForeground(Color color, int row, int column) {
        if (isOutOfBounds(row, column)) return;
        foreground[row][column] = color;
      }
      public void setForeground(Color color, int[] rows, int[] columns) {
        if (isOutOfBounds(rows, columns)) return;
        setValues(foreground, color, rows, columns);
      }
      public Color getBackground(int row, int column) {
        if (isOutOfBounds(row, column)) return null;
        return background[row][column];
      }
      public void setBackground(Color color, int row, int column) {
        if (isOutOfBounds(row, column)) return;
        background[row][column] = color;
      }
      public void setBackground(Color color, int[] rows, int[] columns) {
        if (isOutOfBounds(rows, columns)) return;
        setValues(background, color, rows, columns);
      }
      //
     
     
      //
      // CellFont
      //
      public Font getFont(int row, int column) {
        if (isOutOfBounds(row, column)) return null;
        return font[row][column];
      }
      public void setFont(Font font, int row, int column) {
        if (isOutOfBounds(row, column)) return;
        this.font[row][column] = font;
      }
      public void setFont(Font font, int[] rows, int[] columns) {
        if (isOutOfBounds(rows, columns)) return;
        setValues(this.font, font, rows, columns);
      }
      //
     
     
      //
      // CellAttribute
      //
      public void addColumn() {
        int[][][] oldSpan = span;
        int numRows    = oldSpan.length;
        int numColumns = oldSpan[0].length;
        span = new int[numRows][numColumns + 1][2];
        System.arraycopy(oldSpan,0,span,0,numRows);
        for (int i=0;i<numRows;i++) {
          span[i][numColumns][CellSpan.COLUMN] = 1;
          span[i][numColumns][CellSpan.ROW]    = 1;
        }
      }
     
      public void addRow() {
        int[][][] oldSpan = span;
        int numRows    = oldSpan.length;
        int numColumns = oldSpan[0].length;
        span = new int[numRows + 1][numColumns][2];
        System.arraycopy(oldSpan,0,span,0,numRows);
        for (int i=0;i<numColumns;i++) {
          span[numRows][i][CellSpan.COLUMN] = 1;
          span[numRows][i][CellSpan.ROW]    = 1;
        }
      }
     
      public void insertRow(int row) {
        int[][][] oldSpan = span;
        int numRows    = oldSpan.length;
        int numColumns = oldSpan[0].length;
        span = new int[numRows + 1][numColumns][2];
        if (0 < row) {
          System.arraycopy(oldSpan,0,span,0,row-1);
        }
        System.arraycopy(oldSpan,0,span,row,numRows - row);
        for (int i=0;i<numColumns;i++) {
          span[row][i][CellSpan.COLUMN] = 1;
          span[row][i][CellSpan.ROW]    = 1;
        }
      }
     
      public Dimension getSize() {
        return new Dimension(rowSize, columnSize);
      }
     
      public void setSize(Dimension size) {
        columnSize = size.width;
        rowSize    = size.height;
        span = new int[rowSize][columnSize][2];   // 2: COLUMN,ROW
        foreground = new Color[rowSize][columnSize];
        background = new Color[rowSize][columnSize];
        font = new Font[rowSize][columnSize];
        initValue();
      }
     
      /*
      public void changeAttribute(int row, int column, Object command) {
      }
     
      public void changeAttribute(int[] rows, int[] columns, Object command) {
      }
      */
     
      protected boolean isOutOfBounds(int row, int column) {
        if ((row    < 0)||(rowSize    <= row)
          ||(column < 0)||(columnSize <= column)) {
          return true;
        }
        return false;
      }
     
      protected boolean isOutOfBounds(int[] rows, int[] columns) {
        for (int i=0;i<rows.length;i++) {
          if ((rows[i] < 0)||(rowSize <= rows[i])) return true;
        }
        for (int i=0;i<columns.length;i++) {
          if ((columns[i] < 0)||(columnSize <= columns[i])) return true;
        }
        return false;
      }
     
      protected void setValues(Object[][] target, Object value,
                               int[] rows, int[] columns) {
        for (int i=0;i<rows.length;i++) {
          int row = rows[i];
          for (int j=0;j<columns.length;j++) {
            int column = columns[j];
            target[row][column] = value;
          }
        }
      }
    }
    

    CellAttribute.java:

    import java.awt.*;
     
    /**
     * @version 1.0 11/22/98
     */ 
    public interface CellAttribute {
     
      public void addColumn();
     
      public void addRow();
     
      public void insertRow(int row);
     
      public Dimension getSize();
     
      public void setSize(Dimension size);
    }
    

    ColoredCell.java:

    import java.awt.*;
     
    /**
     * @version 1.0 11/22/98
     */
     
    public interface ColoredCell {
      public Color getForeground(int row, int column);
      public void setForeground(Color color, int row, int column);
      public void setForeground(Color color, int[] rows, int[] columns);
     
      public Color getBackground(int row, int column);
      public void setBackground(Color color, int row, int column);
      public void setBackground(Color color, int[] rows, int[] columns);
    }
    

    CellFont.java:

    import java.awt.*;
     
    /**
     * @version 1.0 11/22/98
     */
     
    public interface CellFont {
      public Font getFont(int row, int column);
      public void setFont(Font font, int row, int column);
      public void setFont(Font font, int[] rows, int[] columns);
    }
    

    CellSpan.java:

    /**
     * @version 1.0 11/22/98
     */
     
    public interface CellSpan {
      public final int ROW    = 0;
      public final int COLUMN = 1;
       
      public int[] getSpan(int row, int column);
      public void setSpan(int[] span, int row, int column);
       
      public boolean isVisible(int row, int column);
       
      public void combine(int[] rows, int[] columns);
      public void split(int row, int column);
    
    }

    MultiSpanCellTable.java:

    import java.util.*;
    import java.awt.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import javax.swing.plaf.basic.*;
    import javax.swing.event.*;
     
    /**
     * @version 1.0 11/26/98
     */
     
    public class MultiSpanCellTable extends JTable {
     
      public MultiSpanCellTable(TableModel model) {
        super(model);
        setUI(new MultiSpanCellTableUI());
        getTableHeader().setReorderingAllowed(false);
        setCellSelectionEnabled(true);
        setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION);
      }
      
      public Rectangle getCellRect(int row, int column, boolean includeSpacing) {
        Rectangle sRect = super.getCellRect(row,column,includeSpacing);
        if ((row <0) || (column<0) ||
            (getRowCount() <= row) || (getColumnCount() <= column)) {
            return sRect;
        }
        CellSpan cellAtt = (CellSpan)((AttributiveCellTableModel)getModel()).getCellAttribute();
        if (! cellAtt.isVisible(row,column)) {
          int temp_row    = row;
          int temp_column = column;
          row    += cellAtt.getSpan(temp_row,temp_column)[CellSpan.ROW];
          column += cellAtt.getSpan(temp_row,temp_column)[CellSpan.COLUMN];      
        }
        int[] n = cellAtt.getSpan(row,column);
     
        int index = 0;
        int columnMargin = getColumnModel().getColumnMargin();
        Rectangle cellFrame = new Rectangle();
        int aCellHeight = rowHeight + rowMargin;
        cellFrame.y = row * aCellHeight;
        cellFrame.height = n[CellSpan.ROW] * aCellHeight;
        
        Enumeration enumeration = getColumnModel().getColumns();
        while (enumeration.hasMoreElements()) {
          TableColumn aColumn = (TableColumn)enumeration.nextElement();
          cellFrame.width = aColumn.getWidth() + columnMargin;
          if (index == column) break;
          cellFrame.x += cellFrame.width;
          index++;
        }
        for (int i=0;i< n[CellSpan.COLUMN]-1;i++) {
          TableColumn aColumn = (TableColumn)enumeration.nextElement();
          cellFrame.width += aColumn.getWidth() + columnMargin;
        }
         
        if (!includeSpacing) {
          Dimension spacing = getIntercellSpacing();
          cellFrame.setBounds(cellFrame.x +      spacing.width/2,
                              cellFrame.y +      spacing.height/2,
                              cellFrame.width -  spacing.width,
                              cellFrame.height - spacing.height);
        }
        return cellFrame;
      }
      
      
      private int[] rowColumnAtPoint(Point point) {
        int[] retValue = {-1,-1};
        int row = point.y / (rowHeight + rowMargin);
        if ((row <0)||(getRowCount() <= row)) return retValue;
        int column = getColumnModel().getColumnIndexAtX(point.x);
     
        CellSpan cellAtt = (CellSpan)((AttributiveCellTableModel)getModel()).getCellAttribute();
     
        if (cellAtt.isVisible(row,column)) {
          retValue[CellSpan.COLUMN] = column;
          retValue[CellSpan.ROW   ] = row;
          return retValue;
        }
        retValue[CellSpan.COLUMN] = column + cellAtt.getSpan(row,column)[CellSpan.COLUMN];
        retValue[CellSpan.ROW   ] = row + cellAtt.getSpan(row,column)[CellSpan.ROW];
        return retValue;
      }
     
      public int rowAtPoint(Point point) {
        return rowColumnAtPoint(point)[CellSpan.ROW];
      }
      public int columnAtPoint(Point point) {
        return rowColumnAtPoint(point)[CellSpan.COLUMN];
      }
     
     
      
      public void columnSelectionChanged(ListSelectionEvent e) {
        repaint();
      }
     
      public void valueChanged(ListSelectionEvent e) {
        int firstIndex = e.getFirstIndex();
        int  lastIndex = e.getLastIndex();
        if (firstIndex == -1 && lastIndex == -1) { // Selection cleared.
          repaint();
        }
        Rectangle dirtyRegion = getCellRect(firstIndex, 0, false);
        int numCoumns = getColumnCount();
        int index = firstIndex;
        for (int i=0;i<numCoumns;i++) {
          dirtyRegion.add(getCellRect(index, i, false));
        }
        index = lastIndex;
        for (int i=0;i<numCoumns;i++) {
          dirtyRegion.add(getCellRect(index, i, false));
        }
        repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
      }
     
    }
    

    MultiSpanCellTableUI.java:

    import java.util.*;
    import java.awt.*;
    import javax.swing.*;
    import javax.swing.table.*;
    import javax.swing.plaf.basic.*;
     
    /**
     * @version 1.0 11/26/98
     */
     
    public class MultiSpanCellTableUI extends BasicTableUI {
     
      public void paint(Graphics g, JComponent c) {
        Rectangle oldClipBounds = g.getClipBounds();
        Rectangle clipBounds    = new Rectangle(oldClipBounds);
        int tableWidth   = table.getColumnModel().getTotalColumnWidth();
        clipBounds.width = Math.min(clipBounds.width, tableWidth);
        g.setClip(clipBounds);
     
        int firstIndex = table.rowAtPoint(new Point(0, clipBounds.y));
        int  lastIndex = table.getRowCount()-1;
     
        Rectangle rowRect = new Rectangle(0,0,
          tableWidth, table.getRowHeight() + table.getRowMargin());
        rowRect.y = firstIndex*rowRect.height;
     
        for (int index = firstIndex; index <= lastIndex; index++) {
          if (rowRect.intersects(clipBounds)) {
            //System.out.println();                  // debug
            //System.out.print("" + index +": ");    // row
            paintRow(g, index);
          }
          rowRect.y += rowRect.height;
        }
        g.setClip(oldClipBounds);
      }
     
      private void paintRow(Graphics g, int row) {
        Rectangle rect = g.getClipBounds();
        boolean drawn  = false;
        
        AttributiveCellTableModel tableModel = (AttributiveCellTableModel)table.getModel();
        CellSpan cellAtt = (CellSpan)tableModel.getCellAttribute();
        int numColumns = table.getColumnCount();
     
        for (int column = 0; column < numColumns; column++) {
          Rectangle cellRect = table.getCellRect(row,column,true);
          int cellRow,cellColumn;
          if (cellAtt.isVisible(row,column)) {
            cellRow    = row;
            cellColumn = column;
              //  System.out.print("   "+column+" ");  // debug
          } else {
            cellRow    = row + cellAtt.getSpan(row,column)[CellSpan.ROW];
            cellColumn = column + cellAtt.getSpan(row,column)[CellSpan.COLUMN];
              //  System.out.print("  ("+column+")");  // debug
          }
          if (cellRect.intersects(rect)) {
            drawn = true;
            paintCell(g, cellRect, cellRow, cellColumn);
          } else {
            if (drawn) break;
          } 
        }
      }
     
      private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
        int spacingHeight = table.getRowMargin();
        int spacingWidth  = table.getColumnModel().getColumnMargin();
     
        Color c = g.getColor();
        g.setColor(table.getGridColor());
        g.drawRect(cellRect.x,cellRect.y,cellRect.width-1,cellRect.height-1);
        g.setColor(c);
     
        cellRect.setBounds(cellRect.x + spacingWidth/2, cellRect.y + spacingHeight/2,
                           cellRect.width - spacingWidth, cellRect.height - spacingHeight);
     
        if (table.isEditing() && table.getEditingRow()==row &&
            table.getEditingColumn()==column) {
          Component component = table.getEditorComponent();
          component.setBounds(cellRect);
          component.validate();
        }
        else {
          TableCellRenderer renderer = table.getCellRenderer(row, column);
          Component component = table.prepareRenderer(renderer, row, column);
     
          if (component.getParent() == null) {
            rendererPane.add(component);
          }
          rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
                                      cellRect.width, cellRect.height, true);
        }
      }    
    }
    

    Programmatically scroll a scrollable JTable to a specific cell or row

    The scrollRectToVisible method is the magic one you need. The code fragment below assumes that selectedColumn and selectedRow indicate the cell that you want to make sure is visible. If your rows/columns are not all the same height/width you will have to change the calculations, but the concept is the same.

       table.scrollRectToVisible(
          new Rectangle(columnWidth*selectedColumn,
                        table.getRowHeight()*(selectedRow),
                        columnWidth,
                        table.getRowHeight()));
    

    Determining when a page in a JEditorPane has finished loading

    Add a PropertyChangeListener and listen for the property page.

       htmlPane.addPropertyChangeListener(new PropertyChangeListener() {
          public void propertyChange(PropertyChangeEvent evt) {
             if (!evt.getPropertyName().equals("page")) return;
             System.out.println("Page loaded!");
          }
       });
    

    Main.java:

    import javax.swing.text.html.*;
    import javax.swing.event.*;
    import javax.swing.text.*;
    import java.awt.event.*;
    import javax.swing.*;
    import java.beans.*;
    import java.net.*;
    import java.awt.*;
    import java.io.*;
     
    public class Main extends JFrame
    {
       // need to be final to allow the inner class to access it!
       final JTextField urlTextField = new JTextField();
       final JEditorPane htmlPane = new JEditorPane();
       JButton urlButton = new JButton("Go!");
     
       public static void main(String []args) {
          Main main = new Main();
          main.show();
       }
     
       public Main() { 
          htmlPane.setEditable(false);
          urlTextField.setText("http://www.yahoo.com");
      
          urlButton.addActionListener(new ActionListener() {
             public void actionPerformed(ActionEvent ae) {
                loadPage(urlTextField.getText());
             }
          });
     
          htmlPane.addPropertyChangeListener(new PropertyChangeListener() {
             public void propertyChange(PropertyChangeEvent evt) {
                if (!evt.getPropertyName().equals("page")) return;
                System.out.println("Page loaded!");
             }
          });
     
          htmlPane.addHyperlinkListener(new HyperlinkListener() {
             public void hyperlinkUpdate (HyperlinkEvent event) {
                if (event.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                   urlTextField.setText(event.getURL().toString());
    
                   if (event instanceof HTMLFrameHyperlinkEvent) {
                      HTMLDocument doc = (HTMLDocument) htmlPane.getDocument();
                      doc.processHTMLFrameHyperlinkEvent ((HTMLFrameHyperlinkEvent) event);
                   }
                   else {
                      loadPage(urlTextField.getText());
                   }
                }
             }
          });
     
          getContentPane().setLayout(new BorderLayout());
          JPanel topPanel = new JPanel(new BorderLayout());
          topPanel.add(BorderLayout.CENTER, urlTextField);
          topPanel.add(BorderLayout.EAST, urlButton);
     
          getContentPane().add(BorderLayout.NORTH, topPanel);
          getContentPane().add(BorderLayout.CENTER, new JScrollPane(htmlPane));
     
          addWindowListener(new WindowAdapter() {
             public void windowClosing(WindowEvent e) {
                System.exit(0);
             }
          });
     
          setSize(400, 400);
       }
     
       public void loadPage(String url) {
          try {
             htmlPane.setPage(new URL(urlTextField.getText()));
          }
          catch(Exception e) {
             System.out.println(e);
          }
       }
    }