Imported GNU Classpath 0.90
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicListUI.java
blob19dfe21f889ce661d6156c1092bdbd403a9bd8a9
1 /* BasicListUI.java --
2 Copyright (C) 2002, 2004, 2005 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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.
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.
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.
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.
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. */
39 package javax.swing.plaf.basic;
41 import java.awt.Component;
42 import java.awt.Dimension;
43 import java.awt.Graphics;
44 import java.awt.Insets;
45 import java.awt.Point;
46 import java.awt.Rectangle;
47 import java.awt.event.ActionEvent;
48 import java.awt.event.ActionListener;
49 import java.awt.event.FocusEvent;
50 import java.awt.event.FocusListener;
51 import java.awt.event.MouseEvent;
52 import java.beans.PropertyChangeEvent;
53 import java.beans.PropertyChangeListener;
55 import javax.swing.AbstractAction;
56 import javax.swing.ActionMap;
57 import javax.swing.CellRendererPane;
58 import javax.swing.DefaultListSelectionModel;
59 import javax.swing.InputMap;
60 import javax.swing.JComponent;
61 import javax.swing.JList;
62 import javax.swing.KeyStroke;
63 import javax.swing.ListCellRenderer;
64 import javax.swing.ListModel;
65 import javax.swing.ListSelectionModel;
66 import javax.swing.LookAndFeel;
67 import javax.swing.SwingUtilities;
68 import javax.swing.UIDefaults;
69 import javax.swing.UIManager;
70 import javax.swing.event.ListDataEvent;
71 import javax.swing.event.ListDataListener;
72 import javax.swing.event.ListSelectionEvent;
73 import javax.swing.event.ListSelectionListener;
74 import javax.swing.event.MouseInputListener;
75 import javax.swing.plaf.ActionMapUIResource;
76 import javax.swing.plaf.ComponentUI;
77 import javax.swing.plaf.InputMapUIResource;
78 import javax.swing.plaf.ListUI;
80 /**
81 * The Basic Look and Feel UI delegate for the
82 * JList.
84 public class BasicListUI extends ListUI
87 /**
88 * A helper class which listens for {@link FocusEvent}s
89 * from the JList.
91 public class FocusHandler implements FocusListener
93 /**
94 * Called when the JList acquires focus.
96 * @param e The FocusEvent representing focus acquisition
98 public void focusGained(FocusEvent e)
100 repaintCellFocus();
104 * Called when the JList loses focus.
106 * @param e The FocusEvent representing focus loss
108 public void focusLost(FocusEvent e)
110 repaintCellFocus();
114 * Helper method to repaint the focused cell's
115 * lost or acquired focus state.
117 protected void repaintCellFocus()
119 // TODO: Implement this properly.
124 * A helper class which listens for {@link ListDataEvent}s generated by
125 * the {@link JList}'s {@link ListModel}.
127 * @see javax.swing.JList#getModel()
129 public class ListDataHandler implements ListDataListener
132 * Called when a general change has happened in the model which cannot
133 * be represented in terms of a simple addition or deletion.
135 * @param e The event representing the change
137 public void contentsChanged(ListDataEvent e)
139 updateLayoutStateNeeded |= modelChanged;
140 list.revalidate();
144 * Called when an interval of objects has been added to the model.
146 * @param e The event representing the addition
148 public void intervalAdded(ListDataEvent e)
150 updateLayoutStateNeeded |= modelChanged;
151 list.revalidate();
155 * Called when an inteval of objects has been removed from the model.
157 * @param e The event representing the removal
159 public void intervalRemoved(ListDataEvent e)
161 updateLayoutStateNeeded |= modelChanged;
162 list.revalidate();
167 * A helper class which listens for {@link ListSelectionEvent}s
168 * from the {@link JList}'s {@link ListSelectionModel}.
170 public class ListSelectionHandler implements ListSelectionListener
173 * Called when the list selection changes.
175 * @param e The event representing the change
177 public void valueChanged(ListSelectionEvent e)
179 int index1 = e.getFirstIndex();
180 int index2 = e.getLastIndex();
181 Rectangle damaged = getCellBounds(list, index1, index2);
182 list.repaint(damaged);
187 * This class is used to mimmic the behaviour of the JDK when registering
188 * keyboard actions. It is the same as the private class used in JComponent
189 * for the same reason. This class receives an action event and dispatches
190 * it to the true receiver after altering the actionCommand property of the
191 * event.
193 private static class ActionListenerProxy
194 extends AbstractAction
196 ActionListener target;
197 String bindingCommandName;
199 public ActionListenerProxy(ActionListener li,
200 String cmd)
202 target = li;
203 bindingCommandName = cmd;
206 public void actionPerformed(ActionEvent e)
208 ActionEvent derivedEvent = new ActionEvent(e.getSource(),
209 e.getID(),
210 bindingCommandName,
211 e.getModifiers());
212 target.actionPerformed(derivedEvent);
216 class ListAction extends AbstractAction
218 public void actionPerformed (ActionEvent e)
220 int lead = list.getLeadSelectionIndex();
221 int max = list.getModel().getSize() - 1;
222 DefaultListSelectionModel selModel = (DefaultListSelectionModel)list.getSelectionModel();
223 String command = e.getActionCommand();
224 // Do nothing if list is empty
225 if (max == -1)
226 return;
228 if (command.equals("selectNextRow"))
230 selectNextIndex();
232 else if (command.equals("selectPreviousRow"))
234 selectPreviousIndex();
236 else if (command.equals("clearSelection"))
238 list.clearSelection();
240 else if (command.equals("selectAll"))
242 list.setSelectionInterval(0, max);
243 // this next line is to restore the lead selection index to the old
244 // position, because select-all should not change the lead index
245 list.addSelectionInterval(lead, lead);
247 else if (command.equals("selectLastRow"))
249 list.setSelectedIndex(list.getModel().getSize() - 1);
251 else if (command.equals("selectLastRowChangeLead"))
253 selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1);
255 else if (command.equals("scrollDownExtendSelection"))
257 int target;
258 if (lead == list.getLastVisibleIndex())
260 target = Math.min
261 (max, lead + (list.getLastVisibleIndex() -
262 list.getFirstVisibleIndex() + 1));
264 else
265 target = list.getLastVisibleIndex();
266 selModel.setLeadSelectionIndex(target);
268 else if (command.equals("scrollDownChangeLead"))
270 int target;
271 if (lead == list.getLastVisibleIndex())
273 target = Math.min
274 (max, lead + (list.getLastVisibleIndex() -
275 list.getFirstVisibleIndex() + 1));
277 else
278 target = list.getLastVisibleIndex();
279 selModel.moveLeadSelectionIndex(target);
281 else if (command.equals("scrollUpExtendSelection"))
283 int target;
284 if (lead == list.getFirstVisibleIndex())
286 target = Math.max
287 (0, lead - (list.getLastVisibleIndex() -
288 list.getFirstVisibleIndex() + 1));
290 else
291 target = list.getFirstVisibleIndex();
292 selModel.setLeadSelectionIndex(target);
294 else if (command.equals("scrollUpChangeLead"))
296 int target;
297 if (lead == list.getFirstVisibleIndex())
299 target = Math.max
300 (0, lead - (list.getLastVisibleIndex() -
301 list.getFirstVisibleIndex() + 1));
303 else
304 target = list.getFirstVisibleIndex();
305 selModel.moveLeadSelectionIndex(target);
307 else if (command.equals("selectNextRowExtendSelection"))
309 selModel.setLeadSelectionIndex(Math.min(lead + 1,max));
311 else if (command.equals("selectFirstRow"))
313 list.setSelectedIndex(0);
315 else if (command.equals("selectFirstRowChangeLead"))
317 selModel.moveLeadSelectionIndex(0);
319 else if (command.equals("selectFirstRowExtendSelection"))
321 selModel.setLeadSelectionIndex(0);
323 else if (command.equals("selectPreviousRowExtendSelection"))
325 selModel.setLeadSelectionIndex(Math.max(0,lead - 1));
327 else if (command.equals("scrollUp"))
329 int target;
330 if (lead == list.getFirstVisibleIndex())
332 target = Math.max
333 (0, lead - (list.getLastVisibleIndex() -
334 list.getFirstVisibleIndex() + 1));
336 else
337 target = list.getFirstVisibleIndex();
338 list.setSelectedIndex(target);
340 else if (command.equals("selectLastRowExtendSelection"))
342 selModel.setLeadSelectionIndex(list.getModel().getSize() - 1);
344 else if (command.equals("scrollDown"))
346 int target;
347 if (lead == list.getLastVisibleIndex())
349 target = Math.min
350 (max, lead + (list.getLastVisibleIndex() -
351 list.getFirstVisibleIndex() + 1));
353 else
354 target = list.getLastVisibleIndex();
355 list.setSelectedIndex(target);
357 else if (command.equals("selectNextRowChangeLead"))
359 if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
360 selectNextIndex();
361 else
363 selModel.moveLeadSelectionIndex(Math.min(max, lead + 1));
366 else if (command.equals("selectPreviousRowChangeLead"))
368 if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
369 selectPreviousIndex();
370 else
372 selModel.moveLeadSelectionIndex(Math.max(0, lead - 1));
375 else if (command.equals("addToSelection"))
377 list.addSelectionInterval(lead, lead);
379 else if (command.equals("extendTo"))
381 selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(),
382 lead);
384 else if (command.equals("toggleAndAnchor"))
386 if (!list.isSelectedIndex(lead))
387 list.addSelectionInterval(lead, lead);
388 else
389 list.removeSelectionInterval(lead, lead);
390 selModel.setAnchorSelectionIndex(lead);
392 else
394 // DEBUG: uncomment the following line to print out
395 // key bindings that aren't implemented yet
397 // System.out.println ("not implemented: "+e.getActionCommand());
400 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
405 * A helper class which listens for {@link MouseEvent}s
406 * from the {@link JList}.
408 public class MouseInputHandler implements MouseInputListener
411 * Called when a mouse button press/release cycle completes
412 * on the {@link JList}
414 * @param event The event representing the mouse click
416 public void mouseClicked(MouseEvent event)
418 Point click = event.getPoint();
419 int index = locationToIndex(list, click);
420 if (index == -1)
421 return;
422 if (event.isShiftDown())
424 if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
425 list.setSelectedIndex(index);
426 else if (list.getSelectionMode() ==
427 ListSelectionModel.SINGLE_INTERVAL_SELECTION)
428 // COMPAT: the IBM VM is compatible with the following line of code.
429 // However, compliance with Sun's VM would correspond to replacing
430 // getAnchorSelectionIndex() with getLeadSelectionIndex().This is
431 // both unnatural and contradictory to the way they handle other
432 // similar UI interactions.
433 list.setSelectionInterval(list.getAnchorSelectionIndex(), index);
434 else
435 // COMPAT: both Sun and IBM are compatible instead with:
436 // list.setSelectionInterval
437 // (list.getLeadSelectionIndex(),index);
438 // Note that for IBM this is contradictory to what they did in
439 // the above situation for SINGLE_INTERVAL_SELECTION.
440 // The most natural thing to do is the following:
441 if (list.isSelectedIndex(list.getAnchorSelectionIndex()))
442 list.getSelectionModel().setLeadSelectionIndex(index);
443 else
444 list.addSelectionInterval(list.getAnchorSelectionIndex(), index);
446 else if (event.isControlDown())
448 if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
449 list.setSelectedIndex(index);
450 else if (list.isSelectedIndex(index))
451 list.removeSelectionInterval(index,index);
452 else
453 list.addSelectionInterval(index,index);
455 else
456 list.setSelectedIndex(index);
458 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
462 * Called when a mouse button is pressed down on the
463 * {@link JList}.
465 * @param event The event representing the mouse press
467 public void mousePressed(MouseEvent event)
469 // TODO: What should be done here, if anything?
473 * Called when a mouse button is released on
474 * the {@link JList}
476 * @param event The event representing the mouse press
478 public void mouseReleased(MouseEvent event)
480 // TODO: What should be done here, if anything?
484 * Called when the mouse pointer enters the area bounded
485 * by the {@link JList}
487 * @param event The event representing the mouse entry
489 public void mouseEntered(MouseEvent event)
491 // TODO: What should be done here, if anything?
495 * Called when the mouse pointer leaves the area bounded
496 * by the {@link JList}
498 * @param event The event representing the mouse exit
500 public void mouseExited(MouseEvent event)
502 // TODO: What should be done here, if anything?
506 * Called when the mouse pointer moves over the area bounded
507 * by the {@link JList} while a button is held down.
509 * @param event The event representing the mouse drag
511 public void mouseDragged(MouseEvent event)
513 Point click = event.getPoint();
514 int index = locationToIndex(list, click);
515 if (index == -1)
516 return;
517 if (!event.isShiftDown() && !event.isControlDown())
518 list.setSelectedIndex(index);
520 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
524 * Called when the mouse pointer moves over the area bounded
525 * by the {@link JList}.
527 * @param event The event representing the mouse move
529 public void mouseMoved(MouseEvent event)
531 // TODO: What should be done here, if anything?
536 * Helper class which listens to {@link PropertyChangeEvent}s
537 * from the {@link JList}.
539 public class PropertyChangeHandler implements PropertyChangeListener
542 * Called when the {@link JList} changes one of its bound properties.
544 * @param e The event representing the property change
546 public void propertyChange(PropertyChangeEvent e)
548 if (e.getPropertyName().equals("model"))
550 if (e.getOldValue() != null && e.getOldValue() instanceof ListModel)
552 ListModel oldModel = (ListModel) e.getOldValue();
553 oldModel.removeListDataListener(listDataListener);
555 if (e.getNewValue() != null && e.getNewValue() instanceof ListModel)
557 ListModel newModel = (ListModel) e.getNewValue();
558 newModel.addListDataListener(BasicListUI.this.listDataListener);
561 updateLayoutStateNeeded |= modelChanged;
563 else if (e.getPropertyName().equals("selectionModel"))
564 updateLayoutStateNeeded |= selectionModelChanged;
565 else if (e.getPropertyName().equals("font"))
566 updateLayoutStateNeeded |= fontChanged;
567 else if (e.getPropertyName().equals("fixedCellWidth"))
568 updateLayoutStateNeeded |= fixedCellWidthChanged;
569 else if (e.getPropertyName().equals("fixedCellHeight"))
570 updateLayoutStateNeeded |= fixedCellHeightChanged;
571 else if (e.getPropertyName().equals("prototypeCellValue"))
572 updateLayoutStateNeeded |= prototypeCellValueChanged;
573 else if (e.getPropertyName().equals("cellRenderer"))
574 updateLayoutStateNeeded |= cellRendererChanged;
579 * A constant to indicate that the model has changed.
581 protected static final int modelChanged = 1;
584 * A constant to indicate that the selection model has changed.
586 protected static final int selectionModelChanged = 2;
589 * A constant to indicate that the font has changed.
591 protected static final int fontChanged = 4;
594 * A constant to indicate that the fixedCellWidth has changed.
596 protected static final int fixedCellWidthChanged = 8;
599 * A constant to indicate that the fixedCellHeight has changed.
601 protected static final int fixedCellHeightChanged = 16;
604 * A constant to indicate that the prototypeCellValue has changed.
606 protected static final int prototypeCellValueChanged = 32;
609 * A constant to indicate that the cellRenderer has changed.
611 protected static final int cellRendererChanged = 64;
614 * Creates a new BasicListUI for the component.
616 * @param c The component to create a UI for
618 * @return A new UI
620 public static ComponentUI createUI(final JComponent c)
622 return new BasicListUI();
625 /** The current focus listener. */
626 protected FocusListener focusListener;
628 /** The data listener listening to the model. */
629 protected ListDataListener listDataListener;
631 /** The selection listener listening to the selection model. */
632 protected ListSelectionListener listSelectionListener;
634 /** The mouse listener listening to the list. */
635 protected MouseInputListener mouseInputListener;
637 /** The property change listener listening to the list. */
638 protected PropertyChangeListener propertyChangeListener;
640 /** Saved reference to the list this UI was created for. */
641 protected JList list;
644 * The height of a single cell in the list. This field is used when the
645 * fixedCellHeight property of the list is set. Otherwise this field is
646 * set to <code>-1</code> and {@link #cellHeights} is used instead.
648 protected int cellHeight;
650 /** The width of a single cell in the list. */
651 protected int cellWidth;
653 /**
654 * An array of varying heights of cells in the list, in cases where each
655 * cell might have a different height. This field is used when the
656 * <code>fixedCellHeight</code> property of the list is not set. Otherwise
657 * this field is <code>null</code> and {@link #cellHeight} is used.
659 protected int[] cellHeights;
662 * A bitmask that indicates which properties of the JList have changed.
663 * When nonzero, indicates that the UI class is out of
664 * date with respect to the underlying list, and must recalculate the
665 * list layout before painting or performing size calculations.
667 * @see #modelChanged
668 * @see #selectionModelChanged
669 * @see #fontChanged
670 * @see #fixedCellWidthChanged
671 * @see #fixedCellHeightChanged
672 * @see #prototypeCellValueChanged
673 * @see #cellRendererChanged
675 protected int updateLayoutStateNeeded;
678 * The {@link CellRendererPane} that is used for painting.
680 protected CellRendererPane rendererPane;
682 /** The action bound to KeyStrokes. */
683 ListAction action;
686 * Calculate the height of a particular row. If there is a fixed {@link
687 * #cellHeight}, return it; otherwise return the specific row height
688 * requested from the {@link #cellHeights} array. If the requested row
689 * is invalid, return <code>-1</code>.
691 * @param row The row to get the height of
693 * @return The height, in pixels, of the specified row
695 protected int getRowHeight(int row)
697 int height;
698 if (cellHeights == null)
699 height = cellHeight;
700 else
702 if (row < 0 || row >= cellHeights.length)
703 height = -1;
704 else
705 height = cellHeights[row];
707 return height;
711 * Calculate the bounds of a particular cell, considering the upper left
712 * corner of the list as the origin position <code>(0,0)</code>.
714 * @param l Ignored; calculates over <code>this.list</code>
715 * @param index1 The first row to include in the bounds
716 * @param index2 The last row to incude in the bounds
718 * @return A rectangle encompassing the range of rows between
719 * <code>index1</code> and <code>index2</code> inclusive
721 public Rectangle getCellBounds(JList l, int index1, int index2)
723 maybeUpdateLayoutState();
725 if (l != list || cellWidth == -1)
726 return null;
728 int minIndex = Math.min(index1, index2);
729 int maxIndex = Math.max(index1, index2);
730 Point loc = indexToLocation(list, minIndex);
732 // When the layoutOrientation is VERTICAL, then the width == the list
733 // width. Otherwise the cellWidth field is used.
734 int width = cellWidth;
735 if (l.getLayoutOrientation() == JList.VERTICAL)
736 width = l.getWidth();
738 Rectangle bounds = new Rectangle(loc.x, loc.y, width,
739 getCellHeight(minIndex));
740 for (int i = minIndex + 1; i <= maxIndex; i++)
742 Point hiLoc = indexToLocation(list, i);
743 bounds = SwingUtilities.computeUnion(hiLoc.x, hiLoc.y, width,
744 getCellHeight(i), bounds);
747 return bounds;
751 * Calculates the maximum cell height.
753 * @param index the index of the cell
755 * @return the maximum cell height
757 private int getCellHeight(int index)
759 int height = cellHeight;
760 if (height <= 0)
762 if (list.getLayoutOrientation() == JList.VERTICAL)
763 height = getRowHeight(index);
764 else
766 for (int j = 0; j < cellHeights.length; j++)
767 height = Math.max(height, cellHeights[j]);
770 return height;
774 * Calculate the Y coordinate of the upper edge of a particular row,
775 * considering the Y coordinate <code>0</code> to occur at the top of the
776 * list.
778 * @param row The row to calculate the Y coordinate of
780 * @return The Y coordinate of the specified row, or <code>-1</code> if
781 * the specified row number is invalid
783 protected int convertRowToY(int row)
785 int y = 0;
786 for (int i = 0; i < row; ++i)
788 int h = getRowHeight(i);
789 if (h == -1)
790 return -1;
791 y += h;
793 return y;
797 * Calculate the row number containing a particular Y coordinate,
798 * considering the Y coodrinate <code>0</code> to occur at the top of the
799 * list.
801 * @param y0 The Y coordinate to calculate the row number for
803 * @return The row number containing the specified Y value, or <code>-1</code>
804 * if the list model is empty
806 * @specnote This method is specified to return -1 for an invalid Y
807 * coordinate. However, some simple tests show that the behaviour
808 * is to return the index of the last list element for an Y
809 * coordinate that lies outside of the list bounds (even for
810 * negative indices). <code>-1</code>
811 * is only returned if the list model is empty.
813 protected int convertYToRow(int y0)
815 if (list.getModel().getSize() == 0)
816 return -1;
818 // When y0 < 0, then the JDK returns the maximum row index of the list. So
819 // do we.
820 if (y0 < 0)
821 return list.getModel().getSize() - 1;
823 // Update the layout if necessary.
824 maybeUpdateLayoutState();
826 int index = list.getModel().getSize() - 1;
828 // If a fixed cell height is set, then we can work more efficient.
829 if (cellHeight > 0)
830 index = Math.min(y0 / cellHeight, index);
831 // If we have no fixed cell height, we must add up each cell height up
832 // to y0.
833 else
835 int h = 0;
836 for (int row = 0; row < cellHeights.length; ++row)
838 h += cellHeights[row];
839 if (y0 < h)
841 index = row;
842 break;
846 return index;
850 * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link
851 * #cellWidth} properties by examining the variouis properties of the
852 * {@link JList}.
854 protected void updateLayoutState()
856 int nrows = list.getModel().getSize();
857 cellHeight = -1;
858 cellWidth = -1;
859 if (cellHeights == null || cellHeights.length != nrows)
860 cellHeights = new int[nrows];
861 ListCellRenderer rend = list.getCellRenderer();
862 // Update the cellHeight(s) fields.
863 int fixedCellHeight = list.getFixedCellHeight();
864 if (fixedCellHeight > 0)
866 cellHeight = fixedCellHeight;
867 cellHeights = null;
869 else
871 cellHeight = -1;
872 for (int i = 0; i < nrows; ++i)
874 Component flyweight =
875 rend.getListCellRendererComponent(list,
876 list.getModel().getElementAt(i),
877 i, list.isSelectedIndex(i),
878 list.getSelectionModel().getAnchorSelectionIndex() == i);
879 Dimension dim = flyweight.getPreferredSize();
880 cellHeights[i] = dim.height;
884 // Update the cellWidth field.
885 int fixedCellWidth = list.getFixedCellWidth();
886 if (fixedCellWidth > 0)
887 cellWidth = fixedCellWidth;
888 else
890 for (int i = 0; i < nrows; ++i)
892 Component flyweight =
893 rend.getListCellRendererComponent(list,
894 list.getModel().getElementAt(i),
895 i, list.isSelectedIndex(i),
896 list.getSelectionModel().getAnchorSelectionIndex() == i);
897 Dimension dim = flyweight.getPreferredSize();
898 cellWidth = Math.max(cellWidth, dim.width);
904 * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded}
905 * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero.
907 protected void maybeUpdateLayoutState()
909 if (updateLayoutStateNeeded != 0)
911 updateLayoutState();
912 updateLayoutStateNeeded = 0;
917 * Creates a new BasicListUI object.
919 public BasicListUI()
921 updateLayoutStateNeeded = 1;
922 rendererPane = new CellRendererPane();
926 * Installs various default settings (mostly colors) from the {@link
927 * UIDefaults} into the {@link JList}
929 * @see #uninstallDefaults
931 protected void installDefaults()
933 LookAndFeel.installColorsAndFont(list, "List.background",
934 "List.foreground", "List.font");
935 list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
936 list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
937 list.setOpaque(true);
941 * Resets to <code>null</code> those defaults which were installed in
942 * {@link #installDefaults}
944 protected void uninstallDefaults()
946 list.setForeground(null);
947 list.setBackground(null);
948 list.setSelectionForeground(null);
949 list.setSelectionBackground(null);
953 * Attaches all the listeners we have in the UI class to the {@link
954 * JList}, its model and its selection model.
956 * @see #uninstallListeners
958 protected void installListeners()
960 if (focusListener == null)
961 focusListener = createFocusListener();
962 list.addFocusListener(focusListener);
963 if (listDataListener == null)
964 listDataListener = createListDataListener();
965 list.getModel().addListDataListener(listDataListener);
966 if (listSelectionListener == null)
967 listSelectionListener = createListSelectionListener();
968 list.addListSelectionListener(listSelectionListener);
969 if (mouseInputListener == null)
970 mouseInputListener = createMouseInputListener();
971 list.addMouseListener(mouseInputListener);
972 list.addMouseMotionListener(mouseInputListener);
973 if (propertyChangeListener == null)
974 propertyChangeListener = createPropertyChangeListener();
975 list.addPropertyChangeListener(propertyChangeListener);
979 * Detaches all the listeners we attached in {@link #installListeners}.
981 protected void uninstallListeners()
983 list.removeFocusListener(focusListener);
984 list.getModel().removeListDataListener(listDataListener);
985 list.removeListSelectionListener(listSelectionListener);
986 list.removeMouseListener(mouseInputListener);
987 list.removeMouseMotionListener(mouseInputListener);
988 list.removePropertyChangeListener(propertyChangeListener);
992 * Installs keyboard actions for this UI in the {@link JList}.
994 protected void installKeyboardActions()
996 InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap");
997 InputMapUIResource parentInputMap = new InputMapUIResource();
998 // FIXME: The JDK uses a LazyActionMap for parentActionMap
999 ActionMap parentActionMap = new ActionMapUIResource();
1000 action = new ListAction();
1001 Object keys[] = focusInputMap.allKeys();
1002 // Register key bindings in the UI InputMap-ActionMap pair
1003 for (int i = 0; i < keys.length; i++)
1005 KeyStroke stroke = (KeyStroke)keys[i];
1006 String actionString = (String) focusInputMap.get(stroke);
1007 parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
1008 stroke.getModifiers()),
1009 actionString);
1011 parentActionMap.put (actionString,
1012 new ActionListenerProxy(action, actionString));
1014 // Register the new InputMap-ActionMap as the parents of the list's
1015 // InputMap and ActionMap
1016 parentInputMap.setParent(list.getInputMap().getParent());
1017 parentActionMap.setParent(list.getActionMap().getParent());
1018 list.getInputMap().setParent(parentInputMap);
1019 list.getActionMap().setParent(parentActionMap);
1023 * Uninstalls keyboard actions for this UI in the {@link JList}.
1025 protected void uninstallKeyboardActions()
1027 // TODO: Implement this properly.
1031 * Installs the various aspects of the UI in the {@link JList}. In
1032 * particular, calls {@link #installDefaults}, {@link #installListeners}
1033 * and {@link #installKeyboardActions}. Also saves a reference to the
1034 * provided component, cast to a {@link JList}.
1036 * @param c The {@link JList} to install the UI into
1038 public void installUI(final JComponent c)
1040 super.installUI(c);
1041 list = (JList) c;
1042 installDefaults();
1043 installListeners();
1044 installKeyboardActions();
1045 maybeUpdateLayoutState();
1049 * Uninstalls all the aspects of the UI which were installed in {@link
1050 * #installUI}. When finished uninstalling, drops the saved reference to
1051 * the {@link JList}.
1053 * @param c Ignored; the UI is uninstalled from the {@link JList}
1054 * reference saved during the call to {@link #installUI}
1056 public void uninstallUI(final JComponent c)
1058 uninstallKeyboardActions();
1059 uninstallListeners();
1060 uninstallDefaults();
1061 list = null;
1065 * Gets the size this list would prefer to assume. This is calculated by
1066 * calling {@link #getCellBounds} over the entire list.
1068 * @param c Ignored; uses the saved {@link JList} reference
1070 * @return DOCUMENT ME!
1072 public Dimension getPreferredSize(JComponent c)
1074 maybeUpdateLayoutState();
1075 int size = list.getModel().getSize();
1076 int visibleRows = list.getVisibleRowCount();
1077 int layoutOrientation = list.getLayoutOrientation();
1079 int h;
1080 int w;
1081 int maxCellHeight = cellHeight;
1082 if (maxCellHeight <= 0)
1084 for (int i = 0; i < cellHeights.length; i++)
1085 maxCellHeight = Math.max(maxCellHeight, cellHeights[i]);
1087 if (layoutOrientation == JList.HORIZONTAL_WRAP)
1089 if (visibleRows > 0)
1091 // We cast to double here to force double divisions.
1092 double modelSize = size;
1093 int neededColumns = (int) Math.ceil(modelSize / visibleRows);
1094 int adjustedRows = (int) Math.ceil(modelSize / neededColumns);
1095 h = maxCellHeight * adjustedRows;
1096 w = cellWidth * neededColumns;
1098 else
1100 int neededColumns = Math.min(1, list.getWidth() / cellWidth);
1101 h = size / neededColumns * maxCellHeight;
1102 w = neededColumns * cellWidth;
1105 else if (layoutOrientation == JList.VERTICAL_WRAP)
1107 if (visibleRows > 0)
1108 h = visibleRows * maxCellHeight;
1109 else
1110 h = Math.max(list.getHeight(), maxCellHeight);
1111 int neededColumns = h / maxCellHeight;
1112 w = cellWidth * neededColumns;
1114 else
1116 if (list.getFixedCellWidth() > 0)
1117 w = list.getFixedCellWidth();
1118 else
1119 w = cellWidth;
1120 if (list.getFixedCellHeight() > 0)
1121 // FIXME: We need to add some cellVerticalMargins here, according
1122 // to the specs.
1123 h = list.getFixedCellHeight() * size;
1124 else
1125 h = maxCellHeight * size;
1127 Insets insets = list.getInsets();
1128 Dimension retVal = new Dimension(w + insets.left + insets.right,
1129 h + insets.top + insets.bottom);
1130 return retVal;
1134 * Paints a single cell in the list.
1136 * @param g The graphics context to paint in
1137 * @param row The row number to paint
1138 * @param bounds The bounds of the cell to paint, assuming a coordinate
1139 * system beginning at <code>(0,0)</code> in the upper left corner of the
1140 * list
1141 * @param rend A cell renderer to paint with
1142 * @param data The data to provide to the cell renderer
1143 * @param sel A selection model to provide to the cell renderer
1144 * @param lead The lead selection index of the list
1146 protected void paintCell(Graphics g, int row, Rectangle bounds,
1147 ListCellRenderer rend, ListModel data,
1148 ListSelectionModel sel, int lead)
1150 boolean isSel = list.isSelectedIndex(row);
1151 boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus();
1152 Component comp = rend.getListCellRendererComponent(list,
1153 data.getElementAt(row),
1154 0, isSel, hasFocus);
1155 rendererPane.paintComponent(g, comp, list, bounds);
1159 * Paints the list by repeatedly calling {@link #paintCell} for each visible
1160 * cell in the list.
1162 * @param g The graphics context to paint with
1163 * @param c Ignored; uses the saved {@link JList} reference
1165 public void paint(Graphics g, JComponent c)
1167 int nrows = list.getModel().getSize();
1168 if (nrows == 0)
1169 return;
1171 maybeUpdateLayoutState();
1172 ListCellRenderer render = list.getCellRenderer();
1173 ListModel model = list.getModel();
1174 ListSelectionModel sel = list.getSelectionModel();
1175 int lead = sel.getLeadSelectionIndex();
1176 Rectangle clip = g.getClipBounds();
1178 int startIndex = locationToIndex(list, new Point(clip.x, clip.y));
1179 int endIndex = locationToIndex(list, new Point(clip.x + clip.width,
1180 clip.y + clip.height));
1182 for (int row = startIndex; row <= endIndex; ++row)
1184 Rectangle bounds = getCellBounds(list, row, row);
1185 if (bounds.intersects(clip))
1186 paintCell(g, row, bounds, render, model, sel, lead);
1191 * Computes the index of a list cell given a point within the list. If the
1192 * location lies outside the bounds of the list, the greatest index in the
1193 * list model is returned.
1195 * @param l the list which on which the computation is based on
1196 * @param location the coordinates
1198 * @return the index of the list item that is located at the given
1199 * coordinates or <code>-1</code> if the list model is empty
1201 public int locationToIndex(JList l, Point location)
1203 int layoutOrientation = list.getLayoutOrientation();
1204 int index = -1;
1205 switch (layoutOrientation)
1207 case JList.VERTICAL:
1208 index = convertYToRow(location.y);
1209 break;
1210 case JList.HORIZONTAL_WRAP:
1211 // determine visible rows and cells per row
1212 int maxCellHeight = getCellHeight(0);
1213 int visibleRows = list.getHeight() / maxCellHeight;
1214 int cellsPerRow = -1;
1215 int numberOfItems = list.getModel().getSize();
1216 cellsPerRow = numberOfItems / visibleRows + 1;
1218 // determine index for the given location
1219 int cellsPerColumn = numberOfItems / cellsPerRow + 1;
1220 int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1);
1221 int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn);
1222 index = gridX + gridY * cellsPerRow;
1223 break;
1224 case JList.VERTICAL_WRAP:
1225 // determine visible rows and cells per column
1226 int maxCellHeight2 = getCellHeight(0);
1227 int visibleRows2 = list.getHeight() / maxCellHeight2;
1228 int numberOfItems2 = list.getModel().getSize();
1229 int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1;
1231 int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1);
1232 int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2);
1233 index = gridY2 + gridX2 * visibleRows2;
1234 break;
1236 return index;
1239 public Point indexToLocation(JList l, int index)
1241 int layoutOrientation = list.getLayoutOrientation();
1242 Point loc = null;
1243 switch (layoutOrientation)
1245 case JList.VERTICAL:
1246 loc = new Point(0, convertRowToY(index));
1247 break;
1248 case JList.HORIZONTAL_WRAP:
1249 // determine visible rows and cells per row
1250 int maxCellHeight = getCellHeight(0);
1251 int visibleRows = list.getHeight() / maxCellHeight;
1252 int numberOfCellsPerRow = -1;
1253 int numberOfItems = list.getModel().getSize();
1254 numberOfCellsPerRow = numberOfItems / visibleRows + 1;
1256 // compute coordinates inside the grid
1257 int gridX = index % numberOfCellsPerRow;
1258 int gridY = index / numberOfCellsPerRow;
1259 int locX = gridX * cellWidth;
1260 int locY;
1261 locY = gridY * maxCellHeight;
1262 loc = new Point(locX, locY);
1263 break;
1264 case JList.VERTICAL_WRAP:
1265 // determine visible rows and cells per column
1266 int maxCellHeight2 = getCellHeight(0);
1267 int visibleRows2 = list.getHeight() / maxCellHeight2;
1268 // compute coordinates inside the grid
1269 if (visibleRows2 > 0)
1271 int gridY2 = index % visibleRows2;
1272 int gridX2 = index / visibleRows2;
1273 int locX2 = gridX2 * cellWidth;
1274 int locY2 = gridY2 * maxCellHeight2;
1275 loc = new Point(locX2, locY2);
1277 else
1278 loc = new Point(0, convertRowToY(index));
1279 break;
1281 return loc;
1285 * Creates and returns the focus listener for this UI.
1287 * @return the focus listener for this UI
1289 protected FocusListener createFocusListener()
1291 return new FocusHandler();
1295 * Creates and returns the list data listener for this UI.
1297 * @return the list data listener for this UI
1299 protected ListDataListener createListDataListener()
1301 return new ListDataHandler();
1305 * Creates and returns the list selection listener for this UI.
1307 * @return the list selection listener for this UI
1309 protected ListSelectionListener createListSelectionListener()
1311 return new ListSelectionHandler();
1315 * Creates and returns the mouse input listener for this UI.
1317 * @return the mouse input listener for this UI
1319 protected MouseInputListener createMouseInputListener()
1321 return new MouseInputHandler();
1325 * Creates and returns the property change listener for this UI.
1327 * @return the property change listener for this UI
1329 protected PropertyChangeListener createPropertyChangeListener()
1331 return new PropertyChangeHandler();
1335 * Selects the next list item and force it to be visible.
1337 protected void selectNextIndex()
1339 int index = list.getSelectionModel().getLeadSelectionIndex();
1340 if (index < list.getModel().getSize() - 1)
1342 index++;
1343 list.setSelectedIndex(index);
1345 list.ensureIndexIsVisible(index);
1349 * Selects the previous list item and force it to be visible.
1351 protected void selectPreviousIndex()
1353 int index = list.getSelectionModel().getLeadSelectionIndex();
1354 if (index > 0)
1356 index--;
1357 list.setSelectedIndex(index);
1359 list.ensureIndexIsVisible(index);