Save This Page
Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]
    1   /* BasicListUI.java --
    2      Copyright (C) 2002, 2004, 2005, 2006 Free Software Foundation, Inc.
    3   
    4   This file is part of GNU Classpath.
    5   
    6   GNU Classpath is free software; you can redistribute it and/or modify
    7   it under the terms of the GNU General Public License as published by
    8   the Free Software Foundation; either version 2, or (at your option)
    9   any later version.
   10   
   11   GNU Classpath is distributed in the hope that it will be useful, but
   12   WITHOUT ANY WARRANTY; without even the implied warranty of
   13   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   14   General Public License for more details.
   15   
   16   You should have received a copy of the GNU General Public License
   17   along with GNU Classpath; see the file COPYING.  If not, write to the
   18   Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
   19   02110-1301 USA.
   20   
   21   Linking this library statically or dynamically with other modules is
   22   making a combined work based on this library.  Thus, the terms and
   23   conditions of the GNU General Public License cover the whole
   24   combination.
   25   
   26   As a special exception, the copyright holders of this library give you
   27   permission to link this library with independent modules to produce an
   28   executable, regardless of the license terms of these independent
   29   modules, and to copy and distribute the resulting executable under
   30   terms of your choice, provided that you also meet, for each linked
   31   independent module, the terms and conditions of the license of that
   32   module.  An independent module is a module which is not derived from
   33   or based on this library.  If you modify this library, you may extend
   34   this exception to your version of the library, but you are not
   35   obligated to do so.  If you do not wish to do so, delete this
   36   exception statement from your version. */
   37   
   38   
   39   package javax.swing.plaf.basic;
   40   
   41   import gnu.classpath.NotImplementedException;
   42   
   43   import java.awt.Component;
   44   import java.awt.Dimension;
   45   import java.awt.Graphics;
   46   import java.awt.Insets;
   47   import java.awt.Point;
   48   import java.awt.Rectangle;
   49   import java.awt.event.ActionEvent;
   50   import java.awt.event.ActionListener;
   51   import java.awt.event.FocusEvent;
   52   import java.awt.event.FocusListener;
   53   import java.awt.event.MouseEvent;
   54   import java.beans.PropertyChangeEvent;
   55   import java.beans.PropertyChangeListener;
   56   
   57   import javax.swing.AbstractAction;
   58   import javax.swing.ActionMap;
   59   import javax.swing.CellRendererPane;
   60   import javax.swing.DefaultListSelectionModel;
   61   import javax.swing.InputMap;
   62   import javax.swing.JComponent;
   63   import javax.swing.JList;
   64   import javax.swing.KeyStroke;
   65   import javax.swing.ListCellRenderer;
   66   import javax.swing.ListModel;
   67   import javax.swing.ListSelectionModel;
   68   import javax.swing.LookAndFeel;
   69   import javax.swing.SwingUtilities;
   70   import javax.swing.UIDefaults;
   71   import javax.swing.UIManager;
   72   import javax.swing.event.ListDataEvent;
   73   import javax.swing.event.ListDataListener;
   74   import javax.swing.event.ListSelectionEvent;
   75   import javax.swing.event.ListSelectionListener;
   76   import javax.swing.event.MouseInputListener;
   77   import javax.swing.plaf.ActionMapUIResource;
   78   import javax.swing.plaf.ComponentUI;
   79   import javax.swing.plaf.InputMapUIResource;
   80   import javax.swing.plaf.ListUI;
   81   
   82   /**
   83    * The Basic Look and Feel UI delegate for the 
   84    * JList.
   85    */
   86   public class BasicListUI extends ListUI
   87   {
   88   
   89     /**
   90      * A helper class which listens for {@link FocusEvent}s
   91      * from the JList.
   92      */
   93     public class FocusHandler implements FocusListener
   94     {
   95       /**
   96        * Called when the JList acquires focus.
   97        *
   98        * @param e The FocusEvent representing focus acquisition
   99        */
  100       public void focusGained(FocusEvent e)
  101       {
  102         repaintCellFocus();
  103       }
  104   
  105       /**
  106        * Called when the JList loses focus.
  107        *
  108        * @param e The FocusEvent representing focus loss
  109        */
  110       public void focusLost(FocusEvent e)
  111       {
  112         repaintCellFocus();
  113       }
  114   
  115       /**
  116        * Helper method to repaint the focused cell's 
  117        * lost or acquired focus state.
  118        */
  119       protected void repaintCellFocus()
  120       {
  121         // TODO: Implement this properly.
  122       }
  123     }
  124   
  125     /**
  126      * A helper class which listens for {@link ListDataEvent}s generated by
  127      * the {@link JList}'s {@link ListModel}.
  128      *
  129      * @see javax.swing.JList#getModel()
  130      */
  131     public class ListDataHandler implements ListDataListener
  132     {
  133       /**
  134        * Called when a general change has happened in the model which cannot
  135        * be represented in terms of a simple addition or deletion.
  136        *
  137        * @param e The event representing the change
  138        */
  139       public void contentsChanged(ListDataEvent e)
  140       {
  141         updateLayoutStateNeeded |= modelChanged;
  142         list.revalidate();
  143       }
  144   
  145       /**
  146        * Called when an interval of objects has been added to the model.
  147        *
  148        * @param e The event representing the addition
  149        */
  150       public void intervalAdded(ListDataEvent e)
  151       {
  152         updateLayoutStateNeeded |= modelChanged;
  153         list.revalidate();
  154       }
  155   
  156       /**
  157        * Called when an inteval of objects has been removed from the model.
  158        *
  159        * @param e The event representing the removal
  160        */
  161       public void intervalRemoved(ListDataEvent e)
  162       {
  163         updateLayoutStateNeeded |= modelChanged;
  164         list.revalidate();
  165       }
  166     }
  167   
  168     /**
  169      * A helper class which listens for {@link ListSelectionEvent}s
  170      * from the {@link JList}'s {@link ListSelectionModel}.
  171      */
  172     public class ListSelectionHandler implements ListSelectionListener
  173     {
  174       /**
  175        * Called when the list selection changes.  
  176        *
  177        * @param e The event representing the change
  178        */
  179       public void valueChanged(ListSelectionEvent e)
  180       {
  181         int index1 = e.getFirstIndex();
  182         int index2 = e.getLastIndex();
  183         Rectangle damaged = getCellBounds(list, index1, index2);
  184         if (damaged != null)
  185           list.repaint(damaged);
  186       }
  187     }
  188   
  189     /**
  190      * This class is used to mimmic the behaviour of the JDK when registering
  191      * keyboard actions.  It is the same as the private class used in JComponent
  192      * for the same reason.  This class receives an action event and dispatches
  193      * it to the true receiver after altering the actionCommand property of the
  194      * event.
  195      */
  196     private static class ActionListenerProxy
  197       extends AbstractAction
  198     {
  199       ActionListener target;
  200       String bindingCommandName;
  201   
  202       public ActionListenerProxy(ActionListener li, 
  203                                  String cmd)
  204       {
  205         target = li;
  206         bindingCommandName = cmd;
  207       }
  208   
  209       public void actionPerformed(ActionEvent e)
  210       {
  211         ActionEvent derivedEvent = new ActionEvent(e.getSource(),
  212                                                    e.getID(),
  213                                                    bindingCommandName,
  214                                                    e.getModifiers());
  215         target.actionPerformed(derivedEvent);
  216       }
  217     }
  218     
  219     class ListAction extends AbstractAction
  220     {
  221       public void actionPerformed (ActionEvent e)
  222       {
  223         int lead = list.getLeadSelectionIndex();
  224         int max = list.getModel().getSize() - 1;
  225         DefaultListSelectionModel selModel = (DefaultListSelectionModel)list.getSelectionModel();
  226         String command = e.getActionCommand();
  227         // Do nothing if list is empty
  228         if (max == -1)
  229           return;
  230         
  231         if (command.equals("selectNextRow"))
  232           {
  233             selectNextIndex();
  234           }
  235         else if (command.equals("selectPreviousRow"))
  236           {
  237             selectPreviousIndex();
  238           }
  239         else if (command.equals("clearSelection"))
  240           {
  241             list.clearSelection();
  242           }
  243         else if (command.equals("selectAll"))
  244           {
  245             list.setSelectionInterval(0, max);
  246             // this next line is to restore the lead selection index to the old
  247             // position, because select-all should not change the lead index
  248             list.addSelectionInterval(lead, lead);
  249           }
  250         else if (command.equals("selectLastRow"))
  251           {
  252             list.setSelectedIndex(list.getModel().getSize() - 1); 
  253           }
  254         else if (command.equals("selectLastRowChangeLead"))
  255           {
  256             selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1);
  257           }
  258         else if (command.equals("scrollDownExtendSelection"))
  259           {
  260             int target;
  261             if (lead == list.getLastVisibleIndex())
  262               {
  263                 target = Math.min
  264                   (max, lead + (list.getLastVisibleIndex() -
  265                       list.getFirstVisibleIndex() + 1));
  266               }
  267             else
  268               target = list.getLastVisibleIndex();
  269             selModel.setLeadSelectionIndex(target);
  270           }
  271         else if (command.equals("scrollDownChangeLead"))
  272           {
  273             int target;
  274             if (lead == list.getLastVisibleIndex())
  275               {
  276                 target = Math.min
  277                   (max, lead + (list.getLastVisibleIndex() -
  278                       list.getFirstVisibleIndex() + 1));
  279               }
  280             else
  281               target = list.getLastVisibleIndex();
  282             selModel.moveLeadSelectionIndex(target);
  283           }
  284         else if (command.equals("scrollUpExtendSelection"))
  285           {
  286             int target;
  287             if (lead == list.getFirstVisibleIndex())
  288               {
  289                 target = Math.max 
  290                   (0, lead - (list.getLastVisibleIndex() - 
  291                       list.getFirstVisibleIndex() + 1));
  292               }
  293             else
  294               target = list.getFirstVisibleIndex();
  295             selModel.setLeadSelectionIndex(target);
  296           }
  297         else if (command.equals("scrollUpChangeLead"))
  298           {
  299             int target;
  300             if (lead == list.getFirstVisibleIndex())
  301               {
  302                 target = Math.max 
  303                   (0, lead - (list.getLastVisibleIndex() - 
  304                       list.getFirstVisibleIndex() + 1));
  305               }
  306             else
  307               target = list.getFirstVisibleIndex();
  308             selModel.moveLeadSelectionIndex(target);
  309           }
  310         else if (command.equals("selectNextRowExtendSelection"))
  311           {
  312             selModel.setLeadSelectionIndex(Math.min(lead + 1,max));
  313           }
  314         else if (command.equals("selectFirstRow"))
  315           {
  316             list.setSelectedIndex(0);
  317           }
  318         else if (command.equals("selectFirstRowChangeLead"))
  319             {
  320               selModel.moveLeadSelectionIndex(0);
  321             }
  322         else if (command.equals("selectFirstRowExtendSelection"))
  323           {
  324             selModel.setLeadSelectionIndex(0);
  325           }
  326         else if (command.equals("selectPreviousRowExtendSelection"))
  327           {
  328             selModel.setLeadSelectionIndex(Math.max(0,lead - 1));
  329           }
  330         else if (command.equals("scrollUp"))
  331           {
  332             int target;
  333             if (lead == list.getFirstVisibleIndex())
  334               {
  335                 target = Math.max 
  336                   (0, lead - (list.getLastVisibleIndex() - 
  337                       list.getFirstVisibleIndex() + 1));
  338               }
  339             else
  340               target = list.getFirstVisibleIndex();
  341             list.setSelectedIndex(target);          
  342           }
  343         else if (command.equals("selectLastRowExtendSelection"))
  344           {
  345             selModel.setLeadSelectionIndex(list.getModel().getSize() - 1);
  346           }
  347         else if (command.equals("scrollDown"))
  348           {
  349             int target;
  350             if (lead == list.getLastVisibleIndex())
  351               {
  352                 target = Math.min
  353                   (max, lead + (list.getLastVisibleIndex() -
  354                       list.getFirstVisibleIndex() + 1));
  355               }
  356             else
  357               target = list.getLastVisibleIndex();
  358             list.setSelectedIndex(target);
  359           }
  360         else if (command.equals("selectNextRowChangeLead"))
  361             {
  362               if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
  363                 selectNextIndex();
  364               else
  365                 {
  366                   selModel.moveLeadSelectionIndex(Math.min(max, lead + 1));
  367                 }
  368             }
  369         else if (command.equals("selectPreviousRowChangeLead"))
  370           {
  371             if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
  372               selectPreviousIndex();
  373             else
  374               {
  375                 selModel.moveLeadSelectionIndex(Math.max(0, lead - 1));
  376               }
  377           }      
  378         else if (command.equals("addToSelection"))
  379           {
  380             list.addSelectionInterval(lead, lead);
  381           }
  382         else if (command.equals("extendTo"))
  383           {
  384             selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(),
  385                                           lead);
  386           }
  387         else if (command.equals("toggleAndAnchor"))
  388           {
  389             if (!list.isSelectedIndex(lead))
  390               list.addSelectionInterval(lead, lead);
  391             else
  392               list.removeSelectionInterval(lead, lead);
  393             selModel.setAnchorSelectionIndex(lead);
  394           }
  395         else 
  396           {
  397             // DEBUG: uncomment the following line to print out 
  398             // key bindings that aren't implemented yet
  399             
  400             // System.out.println ("not implemented: "+e.getActionCommand());
  401           }
  402         
  403         list.ensureIndexIsVisible(list.getLeadSelectionIndex());
  404       }
  405     }
  406        
  407     /**
  408      * A helper class which listens for {@link MouseEvent}s 
  409      * from the {@link JList}.
  410      */
  411     public class MouseInputHandler implements MouseInputListener
  412     {
  413       /**
  414        * Called when a mouse button press/release cycle completes
  415        * on the {@link JList}
  416        *
  417        * @param event The event representing the mouse click
  418        */
  419       public void mouseClicked(MouseEvent event)
  420       {
  421         Point click = event.getPoint();
  422         int index = locationToIndex(list, click);
  423         if (index == -1)
  424           return;
  425         if (event.isShiftDown())
  426           {
  427             if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
  428               list.setSelectedIndex(index);
  429             else if (list.getSelectionMode() == 
  430                      ListSelectionModel.SINGLE_INTERVAL_SELECTION)
  431               // COMPAT: the IBM VM is compatible with the following line of code.
  432               // However, compliance with Sun's VM would correspond to replacing 
  433               // getAnchorSelectionIndex() with getLeadSelectionIndex().This is 
  434               // both unnatural and contradictory to the way they handle other 
  435               // similar UI interactions.
  436               list.setSelectionInterval(list.getAnchorSelectionIndex(), index);
  437             else
  438               // COMPAT: both Sun and IBM are compatible instead with:
  439               // list.setSelectionInterval
  440               //     (list.getLeadSelectionIndex(),index);
  441               // Note that for IBM this is contradictory to what they did in 
  442               // the above situation for SINGLE_INTERVAL_SELECTION.  
  443               // The most natural thing to do is the following:
  444               if (list.isSelectedIndex(list.getAnchorSelectionIndex()))
  445                 list.getSelectionModel().setLeadSelectionIndex(index);
  446               else
  447                 list.addSelectionInterval(list.getAnchorSelectionIndex(), index);
  448           }
  449         else if (event.isControlDown())
  450           {
  451             if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
  452               list.setSelectedIndex(index);
  453             else if (list.isSelectedIndex(index))
  454               list.removeSelectionInterval(index,index);
  455             else
  456               list.addSelectionInterval(index,index);
  457           }
  458         else
  459           list.setSelectedIndex(index);
  460         
  461         list.ensureIndexIsVisible(list.getLeadSelectionIndex());
  462       }
  463   
  464       /**
  465        * Called when a mouse button is pressed down on the
  466        * {@link JList}.
  467        *
  468        * @param event The event representing the mouse press
  469        */
  470       public void mousePressed(MouseEvent event)
  471       {
  472         // TODO: What should be done here, if anything?
  473       }
  474   
  475       /**
  476        * Called when a mouse button is released on
  477        * the {@link JList}
  478        *
  479        * @param event The event representing the mouse press
  480        */
  481       public void mouseReleased(MouseEvent event)
  482       {
  483         // TODO: What should be done here, if anything?
  484       }
  485   
  486       /**
  487        * Called when the mouse pointer enters the area bounded
  488        * by the {@link JList}
  489        *
  490        * @param event The event representing the mouse entry
  491        */
  492       public void mouseEntered(MouseEvent event)
  493       {
  494         // TODO: What should be done here, if anything?
  495       }
  496   
  497       /**
  498        * Called when the mouse pointer leaves the area bounded
  499        * by the {@link JList}
  500        *
  501        * @param event The event representing the mouse exit
  502        */
  503       public void mouseExited(MouseEvent event)
  504       {
  505         // TODO: What should be done here, if anything?
  506       }
  507   
  508       /**
  509        * Called when the mouse pointer moves over the area bounded
  510        * by the {@link JList} while a button is held down.
  511        *
  512        * @param event The event representing the mouse drag
  513        */
  514       public void mouseDragged(MouseEvent event)
  515       {
  516         Point click = event.getPoint();
  517         int index = locationToIndex(list, click);
  518         if (index == -1)
  519           return;
  520         if (!event.isShiftDown() && !event.isControlDown())
  521           list.setSelectedIndex(index);
  522         
  523         list.ensureIndexIsVisible(list.getLeadSelectionIndex());
  524       }
  525   
  526       /**
  527        * Called when the mouse pointer moves over the area bounded
  528        * by the {@link JList}.
  529        *
  530        * @param event The event representing the mouse move
  531        */
  532       public void mouseMoved(MouseEvent event)
  533       {
  534         // TODO: What should be done here, if anything?
  535       }
  536     }
  537   
  538     /**
  539      * Helper class which listens to {@link PropertyChangeEvent}s
  540      * from the {@link JList}.
  541      */
  542     public class PropertyChangeHandler implements PropertyChangeListener
  543     {
  544       /**
  545        * Called when the {@link JList} changes one of its bound properties.
  546        *
  547        * @param e The event representing the property change
  548        */
  549       public void propertyChange(PropertyChangeEvent e)
  550       {
  551         if (e.getPropertyName().equals("model"))
  552           {
  553             if (e.getOldValue() != null && e.getOldValue() instanceof ListModel)
  554               {
  555                 ListModel oldModel = (ListModel) e.getOldValue();
  556                 oldModel.removeListDataListener(listDataListener);
  557               }
  558             if (e.getNewValue() != null && e.getNewValue() instanceof ListModel)
  559               {
  560                 ListModel newModel = (ListModel) e.getNewValue();
  561                 newModel.addListDataListener(BasicListUI.this.listDataListener);
  562               }
  563   
  564             updateLayoutStateNeeded |= modelChanged;
  565           }
  566         else if (e.getPropertyName().equals("selectionModel"))
  567           updateLayoutStateNeeded |= selectionModelChanged;
  568         else if (e.getPropertyName().equals("font"))
  569           updateLayoutStateNeeded |= fontChanged;
  570         else if (e.getPropertyName().equals("fixedCellWidth"))
  571           updateLayoutStateNeeded |= fixedCellWidthChanged;
  572         else if (e.getPropertyName().equals("fixedCellHeight"))
  573           updateLayoutStateNeeded |= fixedCellHeightChanged;
  574         else if (e.getPropertyName().equals("prototypeCellValue"))
  575           updateLayoutStateNeeded |= prototypeCellValueChanged;
  576         else if (e.getPropertyName().equals("cellRenderer"))
  577           updateLayoutStateNeeded |= cellRendererChanged;
  578       }
  579     }
  580   
  581     /**
  582      * A constant to indicate that the model has changed.
  583      */
  584     protected static final int modelChanged = 1;
  585   
  586     /**
  587      * A constant to indicate that the selection model has changed.
  588      */
  589     protected static final int selectionModelChanged = 2;
  590   
  591     /**
  592      * A constant to indicate that the font has changed.
  593      */
  594     protected static final int fontChanged = 4;
  595   
  596     /**
  597      * A constant to indicate that the fixedCellWidth has changed.
  598      */
  599     protected static final int fixedCellWidthChanged = 8;
  600   
  601     /**
  602      * A constant to indicate that the fixedCellHeight has changed.
  603      */
  604     protected static final int fixedCellHeightChanged = 16;
  605   
  606     /**
  607      * A constant to indicate that the prototypeCellValue has changed.
  608      */
  609     protected static final int prototypeCellValueChanged = 32;
  610   
  611     /**
  612      * A constant to indicate that the cellRenderer has changed.
  613      */
  614     protected static final int cellRendererChanged = 64;
  615   
  616     /**
  617      * Creates a new BasicListUI for the component.
  618      *
  619      * @param c The component to create a UI for
  620      *
  621      * @return A new UI
  622      */
  623     public static ComponentUI createUI(final JComponent c)
  624     {
  625       return new BasicListUI();
  626     }
  627   
  628     /** The current focus listener. */
  629     protected FocusListener focusListener;
  630   
  631     /** The data listener listening to the model. */
  632     protected ListDataListener listDataListener;
  633   
  634     /** The selection listener listening to the selection model. */
  635     protected ListSelectionListener listSelectionListener;
  636   
  637     /** The mouse listener listening to the list. */
  638     protected MouseInputListener mouseInputListener;
  639   
  640     /** The property change listener listening to the list. */
  641     protected PropertyChangeListener propertyChangeListener;
  642   
  643     /** Saved reference to the list this UI was created for. */
  644     protected JList list;
  645   
  646     /**
  647      * The height of a single cell in the list. This field is used when the
  648      * fixedCellHeight property of the list is set. Otherwise this field is
  649      * set to <code>-1</code> and {@link #cellHeights} is used instead.
  650      */
  651     protected int cellHeight;
  652   
  653     /** The width of a single cell in the list. */
  654     protected int cellWidth;
  655   
  656     /** 
  657      * An array of varying heights of cells in the list, in cases where each
  658      * cell might have a different height. This field is used when the
  659      * <code>fixedCellHeight</code> property of the list is not set. Otherwise
  660      * this field is <code>null</code> and {@link #cellHeight} is used.
  661      */
  662     protected int[] cellHeights;
  663   
  664     /**
  665      * A bitmask that indicates which properties of the JList have changed.
  666      * When nonzero, indicates that the UI class is out of
  667      * date with respect to the underlying list, and must recalculate the
  668      * list layout before painting or performing size calculations.
  669      *
  670      * @see #modelChanged
  671      * @see #selectionModelChanged
  672      * @see #fontChanged
  673      * @see #fixedCellWidthChanged
  674      * @see #fixedCellHeightChanged
  675      * @see #prototypeCellValueChanged
  676      * @see #cellRendererChanged
  677      */
  678     protected int updateLayoutStateNeeded;
  679   
  680     /**
  681      * The {@link CellRendererPane} that is used for painting.
  682      */
  683     protected CellRendererPane rendererPane;
  684     
  685     /** The action bound to KeyStrokes. */
  686     ListAction action;
  687   
  688     /**
  689      * Calculate the height of a particular row. If there is a fixed {@link
  690      * #cellHeight}, return it; otherwise return the specific row height
  691      * requested from the {@link #cellHeights} array. If the requested row
  692      * is invalid, return <code>-1</code>.
  693      *
  694      * @param row The row to get the height of
  695      *
  696      * @return The height, in pixels, of the specified row
  697      */
  698     protected int getRowHeight(int row)
  699     {
  700       int height;
  701       if (cellHeights == null)
  702         height = cellHeight;
  703       else
  704         {
  705           if (row < 0 || row >= cellHeights.length)
  706             height = -1;
  707           else
  708             height = cellHeights[row];
  709         }
  710       return height;
  711     }
  712   
  713     /**
  714      * Calculate the bounds of a particular cell, considering the upper left
  715      * corner of the list as the origin position <code>(0,0)</code>.
  716      *
  717      * @param l Ignored; calculates over <code>this.list</code>
  718      * @param index1 The first row to include in the bounds
  719      * @param index2 The last row to incude in the bounds
  720      *
  721      * @return A rectangle encompassing the range of rows between 
  722      * <code>index1</code> and <code>index2</code> inclusive, or null
  723      * such a rectangle couldn't be calculated for the given indexes.
  724      */
  725     public Rectangle getCellBounds(JList l, int index1, int index2)
  726     {
  727       maybeUpdateLayoutState();
  728   
  729       if (l != list || cellWidth == -1)
  730         return null;
  731   
  732       int minIndex = Math.min(index1, index2);
  733       int maxIndex = Math.max(index1, index2);
  734       Point loc = indexToLocation(list, minIndex);
  735   
  736       // When the layoutOrientation is VERTICAL, then the width == the list
  737       // width. Otherwise the cellWidth field is used.
  738       int width = cellWidth;
  739       if (l.getLayoutOrientation() == JList.VERTICAL)
  740         width = l.getWidth();
  741   
  742       Rectangle bounds = new Rectangle(loc.x, loc.y, width,
  743                                        getCellHeight(minIndex));
  744       for (int i = minIndex + 1; i <= maxIndex; i++)
  745         {
  746           Point hiLoc = indexToLocation(list, i);
  747           bounds = SwingUtilities.computeUnion(hiLoc.x, hiLoc.y, width,
  748                                                getCellHeight(i), bounds);
  749         }
  750   
  751       return bounds;
  752     }
  753   
  754     /**
  755      * Calculates the maximum cell height.
  756      *
  757      * @param index the index of the cell
  758      *
  759      * @return the maximum cell height
  760      */
  761     private int getCellHeight(int index)
  762     {
  763       int height = cellHeight;
  764       if (height <= 0)
  765         {
  766           if (list.getLayoutOrientation() == JList.VERTICAL)
  767             height = getRowHeight(index);
  768           else
  769             {
  770               for (int j = 0; j < cellHeights.length; j++)
  771                 height = Math.max(height, cellHeights[j]);
  772             }
  773         }
  774       return height;
  775     }
  776   
  777     /**
  778      * Calculate the Y coordinate of the upper edge of a particular row,
  779      * considering the Y coordinate <code>0</code> to occur at the top of the
  780      * list.
  781      *
  782      * @param row The row to calculate the Y coordinate of
  783      *
  784      * @return The Y coordinate of the specified row, or <code>-1</code> if
  785      * the specified row number is invalid
  786      */
  787     protected int convertRowToY(int row)
  788     {
  789       int y = 0;
  790       for (int i = 0; i < row; ++i)
  791         {
  792           int h = getRowHeight(i);
  793           if (h == -1)
  794             return -1;
  795           y += h;
  796         }
  797       return y;
  798     }
  799   
  800     /**
  801      * Calculate the row number containing a particular Y coordinate,
  802      * considering the Y coodrinate <code>0</code> to occur at the top of the
  803      * list.
  804      *
  805      * @param y0 The Y coordinate to calculate the row number for
  806      *
  807      * @return The row number containing the specified Y value, or <code>-1</code>
  808      *         if the list model is empty
  809      *
  810      * @specnote This method is specified to return -1 for an invalid Y
  811      *           coordinate. However, some simple tests show that the behaviour
  812      *           is to return the index of the last list element for an Y
  813      *           coordinate that lies outside of the list bounds (even for
  814      *           negative indices). <code>-1</code>
  815      *           is only returned if the list model is empty.
  816      */
  817     protected int convertYToRow(int y0)
  818     {
  819       if (list.getModel().getSize() == 0)
  820         return -1;
  821   
  822       // When y0 < 0, then the JDK returns the maximum row index of the list. So
  823       // do we.
  824       if (y0 < 0)
  825         return list.getModel().getSize() - 1;
  826   
  827       // Update the layout if necessary.
  828       maybeUpdateLayoutState();
  829   
  830       int index = list.getModel().getSize() - 1;
  831   
  832       // If a fixed cell height is set, then we can work more efficient.
  833       if (cellHeight > 0)
  834         index = Math.min(y0 / cellHeight, index);
  835       // If we have no fixed cell height, we must add up each cell height up
  836       // to y0.
  837       else
  838         {
  839           int h = 0;
  840           for (int row = 0; row < cellHeights.length; ++row)
  841             {
  842               h += cellHeights[row];
  843               if (y0 < h)
  844                 {
  845                   index = row;
  846                   break;
  847                 }
  848             }
  849         }
  850       return index;
  851     }
  852   
  853     /**
  854      * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link
  855      * #cellWidth} properties by examining the variouis properties of the
  856      * {@link JList}.
  857      */
  858     protected void updateLayoutState()
  859     {
  860       int nrows = list.getModel().getSize();
  861       cellHeight = -1;
  862       cellWidth = -1;
  863       if (cellHeights == null || cellHeights.length != nrows)
  864         cellHeights = new int[nrows];
  865       ListCellRenderer rend = list.getCellRenderer();
  866       // Update the cellHeight(s) fields.
  867       int fixedCellHeight = list.getFixedCellHeight();
  868       if (fixedCellHeight > 0)
  869         {
  870           cellHeight = fixedCellHeight;
  871           cellHeights = null;
  872         }
  873       else
  874         {
  875           cellHeight = -1;
  876           for (int i = 0; i < nrows; ++i)
  877             {
  878               Component flyweight =
  879                 rend.getListCellRendererComponent(list,
  880                         list.getModel().getElementAt(i),
  881                         i, list.isSelectedIndex(i),
  882                         list.getSelectionModel().getAnchorSelectionIndex() == i);
  883               Dimension dim = flyweight.getPreferredSize();
  884               cellHeights[i] = dim.height;
  885             }
  886         }
  887   
  888       // Update the cellWidth field.
  889       int fixedCellWidth = list.getFixedCellWidth();
  890       if (fixedCellWidth > 0)
  891         cellWidth = fixedCellWidth;
  892       else
  893         {
  894           for (int i = 0; i < nrows; ++i)
  895             {
  896               Component flyweight =
  897                 rend.getListCellRendererComponent(list,
  898                                                   list.getModel().getElementAt(i),
  899                                                   i, list.isSelectedIndex(i),
  900                                                   list.getSelectionModel().getAnchorSelectionIndex() == i);
  901               Dimension dim = flyweight.getPreferredSize();
  902               cellWidth = Math.max(cellWidth, dim.width);
  903             }
  904         }
  905     }
  906   
  907     /**
  908      * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded}
  909      * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero.
  910      */
  911     protected void maybeUpdateLayoutState()
  912     {
  913       if (updateLayoutStateNeeded != 0)
  914         {
  915           updateLayoutState();
  916           updateLayoutStateNeeded = 0;
  917         }
  918     }
  919   
  920     /**
  921      * Creates a new BasicListUI object.
  922      */
  923     public BasicListUI()
  924     {
  925       updateLayoutStateNeeded = 1;
  926       rendererPane = new CellRendererPane();
  927     }
  928   
  929     /**
  930      * Installs various default settings (mostly colors) from the {@link
  931      * UIDefaults} into the {@link JList}
  932      *
  933      * @see #uninstallDefaults
  934      */
  935     protected void installDefaults()
  936     {
  937       LookAndFeel.installColorsAndFont(list, "List.background",
  938                                        "List.foreground", "List.font");
  939       list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
  940       list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
  941       list.setOpaque(true);
  942     }
  943   
  944     /**
  945      * Resets to <code>null</code> those defaults which were installed in 
  946      * {@link #installDefaults}
  947      */
  948     protected void uninstallDefaults()
  949     {
  950       list.setForeground(null);
  951       list.setBackground(null);
  952       list.setSelectionForeground(null);
  953       list.setSelectionBackground(null);
  954     }
  955   
  956     /**
  957      * Attaches all the listeners we have in the UI class to the {@link
  958      * JList}, its model and its selection model.
  959      *
  960      * @see #uninstallListeners
  961      */
  962     protected void installListeners()
  963     {
  964       if (focusListener == null)
  965         focusListener = createFocusListener();
  966       list.addFocusListener(focusListener);
  967       if (listDataListener == null)
  968         listDataListener = createListDataListener();
  969       list.getModel().addListDataListener(listDataListener);
  970       if (listSelectionListener == null)
  971         listSelectionListener = createListSelectionListener();
  972       list.addListSelectionListener(listSelectionListener);
  973       if (mouseInputListener == null)
  974         mouseInputListener = createMouseInputListener();
  975       list.addMouseListener(mouseInputListener);
  976       list.addMouseMotionListener(mouseInputListener);
  977       if (propertyChangeListener == null)
  978         propertyChangeListener = createPropertyChangeListener();
  979       list.addPropertyChangeListener(propertyChangeListener);
  980     }
  981   
  982     /**
  983      * Detaches all the listeners we attached in {@link #installListeners}.
  984      */
  985     protected void uninstallListeners()
  986     {
  987       list.removeFocusListener(focusListener);
  988       list.getModel().removeListDataListener(listDataListener);
  989       list.removeListSelectionListener(listSelectionListener);
  990       list.removeMouseListener(mouseInputListener);
  991       list.removeMouseMotionListener(mouseInputListener);
  992       list.removePropertyChangeListener(propertyChangeListener);
  993     }
  994     
  995     /**
  996      * Installs keyboard actions for this UI in the {@link JList}.
  997      */
  998     protected void installKeyboardActions()
  999     {
 1000       InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap");
 1001       InputMapUIResource parentInputMap = new InputMapUIResource();
 1002       // FIXME: The JDK uses a LazyActionMap for parentActionMap
 1003       ActionMap parentActionMap = new ActionMapUIResource();
 1004       action = new ListAction();
 1005       Object keys[] = focusInputMap.allKeys();
 1006       // Register key bindings in the UI InputMap-ActionMap pair
 1007       for (int i = 0; i < keys.length; i++)
 1008         {
 1009           KeyStroke stroke = (KeyStroke)keys[i];
 1010           String actionString = (String) focusInputMap.get(stroke);
 1011           parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
 1012                                                     stroke.getModifiers()),
 1013                              actionString);
 1014   
 1015           parentActionMap.put (actionString, 
 1016                                new ActionListenerProxy(action, actionString));
 1017         }
 1018       // Register the new InputMap-ActionMap as the parents of the list's
 1019       // InputMap and ActionMap
 1020       parentInputMap.setParent(list.getInputMap().getParent());
 1021       parentActionMap.setParent(list.getActionMap().getParent());
 1022       list.getInputMap().setParent(parentInputMap);
 1023       list.getActionMap().setParent(parentActionMap);
 1024     }
 1025   
 1026     /**
 1027      * Uninstalls keyboard actions for this UI in the {@link JList}.
 1028      */
 1029     protected void uninstallKeyboardActions()
 1030       throws NotImplementedException
 1031     {
 1032       // TODO: Implement this properly.
 1033     }
 1034   
 1035     /**
 1036      * Installs the various aspects of the UI in the {@link JList}. In
 1037      * particular, calls {@link #installDefaults}, {@link #installListeners}
 1038      * and {@link #installKeyboardActions}. Also saves a reference to the
 1039      * provided component, cast to a {@link JList}.
 1040      *
 1041      * @param c The {@link JList} to install the UI into
 1042      */
 1043     public void installUI(final JComponent c)
 1044     {
 1045       super.installUI(c);
 1046       list = (JList) c;
 1047       installDefaults();
 1048       installListeners();
 1049       installKeyboardActions();
 1050       maybeUpdateLayoutState();
 1051     }
 1052   
 1053     /**
 1054      * Uninstalls all the aspects of the UI which were installed in {@link
 1055      * #installUI}. When finished uninstalling, drops the saved reference to
 1056      * the {@link JList}.
 1057      *
 1058      * @param c Ignored; the UI is uninstalled from the {@link JList}
 1059      * reference saved during the call to {@link #installUI}
 1060      */
 1061     public void uninstallUI(final JComponent c)
 1062     {
 1063       uninstallKeyboardActions();
 1064       uninstallListeners();
 1065       uninstallDefaults();
 1066       list = null;
 1067     }
 1068   
 1069     /**
 1070      * Gets the size this list would prefer to assume. This is calculated by
 1071      * calling {@link #getCellBounds} over the entire list.
 1072      *
 1073      * @param c Ignored; uses the saved {@link JList} reference 
 1074      *
 1075      * @return DOCUMENT ME!
 1076      */
 1077     public Dimension getPreferredSize(JComponent c)
 1078     {
 1079       maybeUpdateLayoutState();
 1080       int size = list.getModel().getSize();
 1081       int visibleRows = list.getVisibleRowCount();
 1082       int layoutOrientation = list.getLayoutOrientation();
 1083   
 1084       int h;
 1085       int w;
 1086       int maxCellHeight = cellHeight;
 1087       if (maxCellHeight <= 0)
 1088         {
 1089           for (int i = 0; i < cellHeights.length; i++)
 1090             maxCellHeight = Math.max(maxCellHeight, cellHeights[i]);
 1091         }
 1092       if (layoutOrientation == JList.HORIZONTAL_WRAP)
 1093         {
 1094           if (visibleRows > 0)
 1095             {
 1096               // We cast to double here to force double divisions.
 1097               double modelSize = size;
 1098               int neededColumns = (int) Math.ceil(modelSize / visibleRows); 
 1099               int adjustedRows = (int) Math.ceil(modelSize / neededColumns);
 1100               h = maxCellHeight * adjustedRows;
 1101               w = cellWidth * neededColumns;
 1102             }
 1103           else
 1104             {
 1105               int neededColumns = Math.min(1, list.getWidth() / cellWidth);
 1106               h = size / neededColumns * maxCellHeight;
 1107               w = neededColumns * cellWidth;
 1108             }
 1109         }
 1110       else if (layoutOrientation == JList.VERTICAL_WRAP)
 1111         {
 1112           if (visibleRows > 0)
 1113             h = visibleRows * maxCellHeight;
 1114           else
 1115             h = Math.max(list.getHeight(), maxCellHeight);
 1116           int neededColumns = h / maxCellHeight;
 1117           w = cellWidth * neededColumns;
 1118         }
 1119       else
 1120         {
 1121           if (list.getFixedCellWidth() > 0)
 1122             w = list.getFixedCellWidth();
 1123           else
 1124             w = cellWidth;
 1125           if (list.getFixedCellHeight() > 0)
 1126             // FIXME: We need to add some cellVerticalMargins here, according
 1127             // to the specs.
 1128             h = list.getFixedCellHeight() * size;
 1129           else
 1130             h = maxCellHeight * size;
 1131         }
 1132       Insets insets = list.getInsets();
 1133       Dimension retVal = new Dimension(w + insets.left + insets.right,
 1134                                        h + insets.top + insets.bottom);
 1135       return retVal;
 1136     }
 1137   
 1138     /**
 1139      * Paints a single cell in the list.
 1140      *
 1141      * @param g The graphics context to paint in
 1142      * @param row The row number to paint
 1143      * @param bounds The bounds of the cell to paint, assuming a coordinate
 1144      * system beginning at <code>(0,0)</code> in the upper left corner of the
 1145      * list
 1146      * @param rend A cell renderer to paint with
 1147      * @param data The data to provide to the cell renderer
 1148      * @param sel A selection model to provide to the cell renderer
 1149      * @param lead The lead selection index of the list
 1150      */
 1151     protected void paintCell(Graphics g, int row, Rectangle bounds,
 1152                    ListCellRenderer rend, ListModel data,
 1153                    ListSelectionModel sel, int lead)
 1154     {
 1155       boolean isSel = list.isSelectedIndex(row);
 1156       boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus();
 1157       Component comp = rend.getListCellRendererComponent(list,
 1158                                                          data.getElementAt(row),
 1159                                                          0, isSel, hasFocus);
 1160       rendererPane.paintComponent(g, comp, list, bounds);
 1161     }
 1162   
 1163     /**
 1164      * Paints the list by repeatedly calling {@link #paintCell} for each visible
 1165      * cell in the list.
 1166      *
 1167      * @param g The graphics context to paint with
 1168      * @param c Ignored; uses the saved {@link JList} reference 
 1169      */
 1170     public void paint(Graphics g, JComponent c)
 1171     {
 1172       int nrows = list.getModel().getSize();
 1173       if (nrows == 0)
 1174         return;
 1175   
 1176       maybeUpdateLayoutState();
 1177       ListCellRenderer render = list.getCellRenderer();
 1178       ListModel model = list.getModel();
 1179       ListSelectionModel sel = list.getSelectionModel();
 1180       int lead = sel.getLeadSelectionIndex();
 1181       Rectangle clip = g.getClipBounds();
 1182   
 1183       int startIndex = locationToIndex(list, new Point(clip.x, clip.y));
 1184       int endIndex = locationToIndex(list, new Point(clip.x + clip.width,
 1185                                                clip.y + clip.height));
 1186       
 1187       for (int row = startIndex; row <= endIndex; ++row)
 1188         {
 1189           Rectangle bounds = getCellBounds(list, row, row);
 1190           if (bounds != null && bounds.intersects(clip))
 1191             paintCell(g, row, bounds, render, model, sel, lead);
 1192         }
 1193     }
 1194   
 1195     /**
 1196      * Computes the index of a list cell given a point within the list. If the
 1197      * location lies outside the bounds of the list, the greatest index in the
 1198      * list model is returned.
 1199      *
 1200      * @param l the list which on which the computation is based on
 1201      * @param location the coordinates
 1202      *
 1203      * @return the index of the list item that is located at the given
 1204      *         coordinates or <code>-1</code> if the list model is empty
 1205      */
 1206     public int locationToIndex(JList l, Point location)
 1207     {
 1208       int layoutOrientation = list.getLayoutOrientation();
 1209       int index = -1;
 1210       switch (layoutOrientation)
 1211         {
 1212         case JList.VERTICAL:
 1213           index = convertYToRow(location.y);
 1214           break;
 1215         case JList.HORIZONTAL_WRAP:
 1216           // determine visible rows and cells per row
 1217           int maxCellHeight = getCellHeight(0);
 1218           int visibleRows = list.getHeight() / maxCellHeight;
 1219           int cellsPerRow = -1;
 1220           int numberOfItems = list.getModel().getSize();
 1221           cellsPerRow = numberOfItems / visibleRows + 1;
 1222   
 1223           // determine index for the given location
 1224           int cellsPerColumn = numberOfItems / cellsPerRow + 1;
 1225           int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1);
 1226           int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn);
 1227           index = gridX + gridY * cellsPerRow;
 1228           break;
 1229         case JList.VERTICAL_WRAP:
 1230           // determine visible rows and cells per column
 1231           int maxCellHeight2 = getCellHeight(0);
 1232           int visibleRows2 = list.getHeight() / maxCellHeight2;
 1233           int numberOfItems2 = list.getModel().getSize();
 1234           int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1;
 1235   
 1236           int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1);
 1237           int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2);
 1238           index = gridY2 + gridX2 * visibleRows2;
 1239           break;
 1240         }
 1241       return index;
 1242     }
 1243   
 1244     public Point indexToLocation(JList l, int index)
 1245     {
 1246       int layoutOrientation = list.getLayoutOrientation();
 1247       Point loc = null;
 1248       switch (layoutOrientation)
 1249         {
 1250         case JList.VERTICAL:
 1251           loc = new Point(0, convertRowToY(index));
 1252           break;
 1253         case JList.HORIZONTAL_WRAP:
 1254           // determine visible rows and cells per row
 1255           int maxCellHeight = getCellHeight(0);
 1256           int visibleRows = list.getHeight() / maxCellHeight;
 1257           int numberOfCellsPerRow = -1;
 1258           int numberOfItems = list.getModel().getSize();
 1259           numberOfCellsPerRow = numberOfItems / visibleRows + 1;
 1260   
 1261           // compute coordinates inside the grid
 1262           int gridX = index % numberOfCellsPerRow;
 1263           int gridY = index / numberOfCellsPerRow;
 1264           int locX = gridX * cellWidth;
 1265           int locY;
 1266           locY = gridY * maxCellHeight;
 1267           loc = new Point(locX, locY);
 1268           break;
 1269         case JList.VERTICAL_WRAP:
 1270           // determine visible rows and cells per column
 1271           int maxCellHeight2 = getCellHeight(0);
 1272           int visibleRows2 = list.getHeight() / maxCellHeight2;
 1273           // compute coordinates inside the grid
 1274           if (visibleRows2 > 0)
 1275             {
 1276               int gridY2 = index % visibleRows2;
 1277               int gridX2 = index / visibleRows2;
 1278               int locX2 = gridX2 * cellWidth;
 1279               int locY2 = gridY2 * maxCellHeight2;
 1280               loc = new Point(locX2, locY2);
 1281             }
 1282           else
 1283             loc = new Point(0, convertRowToY(index));
 1284           break;
 1285         }
 1286       return loc;
 1287     }
 1288   
 1289     /**
 1290      * Creates and returns the focus listener for this UI.
 1291      *
 1292      * @return the focus listener for this UI
 1293      */
 1294     protected FocusListener createFocusListener()
 1295     {
 1296       return new FocusHandler();
 1297     }
 1298   
 1299     /**
 1300      * Creates and returns the list data listener for this UI.
 1301      *
 1302      * @return the list data listener for this UI
 1303      */
 1304     protected ListDataListener createListDataListener()
 1305     {
 1306       return new ListDataHandler();
 1307     }
 1308   
 1309     /**
 1310      * Creates and returns the list selection listener for this UI.
 1311      *
 1312      * @return the list selection listener for this UI
 1313      */
 1314     protected ListSelectionListener createListSelectionListener()
 1315     {
 1316       return new ListSelectionHandler();
 1317     }
 1318   
 1319     /**
 1320      * Creates and returns the mouse input listener for this UI.
 1321      *
 1322      * @return the mouse input listener for this UI
 1323      */
 1324     protected MouseInputListener createMouseInputListener()
 1325     {
 1326       return new MouseInputHandler();
 1327     }
 1328   
 1329     /**
 1330      * Creates and returns the property change listener for this UI.
 1331      *
 1332      * @return the property change listener for this UI
 1333      */
 1334     protected PropertyChangeListener createPropertyChangeListener()
 1335     {
 1336       return new PropertyChangeHandler();
 1337     }
 1338   
 1339     /**
 1340      * Selects the next list item and force it to be visible.
 1341      */
 1342     protected void selectNextIndex()
 1343     {
 1344       int index = list.getSelectionModel().getLeadSelectionIndex();
 1345       if (index < list.getModel().getSize() - 1)
 1346         {
 1347           index++;
 1348           list.setSelectedIndex(index);
 1349         }
 1350       list.ensureIndexIsVisible(index);
 1351     }
 1352   
 1353     /**
 1354      * Selects the previous list item and force it to be visible.
 1355      */
 1356     protected void selectPreviousIndex()
 1357     {
 1358       int index = list.getSelectionModel().getLeadSelectionIndex();
 1359       if (index > 0)
 1360         {
 1361           index--;
 1362           list.setSelectedIndex(index);
 1363         }
 1364       list.ensureIndexIsVisible(index);
 1365     }
 1366   }

Save This Page
Home » openjdk-7 » javax » swing » plaf » basic » [javadoc | source]