Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicListUI.java
blob00d157a62c445087c80b7a6d6437cc8dff500289
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.UIDefaults;
68 import javax.swing.UIManager;
69 import javax.swing.event.ListDataEvent;
70 import javax.swing.event.ListDataListener;
71 import javax.swing.event.ListSelectionEvent;
72 import javax.swing.event.ListSelectionListener;
73 import javax.swing.event.MouseInputListener;
74 import javax.swing.plaf.ActionMapUIResource;
75 import javax.swing.plaf.ComponentUI;
76 import javax.swing.plaf.InputMapUIResource;
77 import javax.swing.plaf.ListUI;
79 /**
80 * The Basic Look and Feel UI delegate for the
81 * JList.
83 public class BasicListUI extends ListUI
86 /**
87 * A helper class which listens for {@link FocusEvent}s
88 * from the JList.
90 public class FocusHandler implements FocusListener
92 /**
93 * Called when the JList acquires focus.
95 * @param e The FocusEvent representing focus acquisition
97 public void focusGained(FocusEvent e)
99 repaintCellFocus();
103 * Called when the JList loses focus.
105 * @param e The FocusEvent representing focus loss
107 public void focusLost(FocusEvent e)
109 repaintCellFocus();
113 * Helper method to repaint the focused cell's
114 * lost or acquired focus state.
116 protected void repaintCellFocus()
118 // TODO: Implement this properly.
123 * A helper class which listens for {@link ListDataEvent}s generated by
124 * the {@link JList}'s {@link ListModel}.
126 * @see javax.swing.JList#getModel()
128 public class ListDataHandler implements ListDataListener
131 * Called when a general change has happened in the model which cannot
132 * be represented in terms of a simple addition or deletion.
134 * @param e The event representing the change
136 public void contentsChanged(ListDataEvent e)
138 list.revalidate();
142 * Called when an interval of objects has been added to the model.
144 * @param e The event representing the addition
146 public void intervalAdded(ListDataEvent e)
148 list.revalidate();
152 * Called when an inteval of objects has been removed from the model.
154 * @param e The event representing the removal
156 public void intervalRemoved(ListDataEvent e)
158 list.revalidate();
163 * A helper class which listens for {@link ListSelectionEvent}s
164 * from the {@link JList}'s {@link ListSelectionModel}.
166 public class ListSelectionHandler implements ListSelectionListener
169 * Called when the list selection changes.
171 * @param e The event representing the change
173 public void valueChanged(ListSelectionEvent e)
175 int index1 = e.getFirstIndex();
176 int index2 = e.getLastIndex();
177 Rectangle damaged = getCellBounds(list, index1, index2);
178 list.repaint(damaged);
183 * This class is used to mimmic the behaviour of the JDK when registering
184 * keyboard actions. It is the same as the private class used in JComponent
185 * for the same reason. This class receives an action event and dispatches
186 * it to the true receiver after altering the actionCommand property of the
187 * event.
189 private static class ActionListenerProxy
190 extends AbstractAction
192 ActionListener target;
193 String bindingCommandName;
195 public ActionListenerProxy(ActionListener li,
196 String cmd)
198 target = li;
199 bindingCommandName = cmd;
202 public void actionPerformed(ActionEvent e)
204 ActionEvent derivedEvent = new ActionEvent(e.getSource(),
205 e.getID(),
206 bindingCommandName,
207 e.getModifiers());
208 target.actionPerformed(derivedEvent);
212 class ListAction extends AbstractAction
214 public void actionPerformed (ActionEvent e)
216 int lead = list.getLeadSelectionIndex();
217 int max = list.getModel().getSize() - 1;
218 DefaultListSelectionModel selModel = (DefaultListSelectionModel)list.getSelectionModel();
219 String command = e.getActionCommand();
220 // Do nothing if list is empty
221 if (max == -1)
222 return;
224 if (command.equals("selectNextRow"))
226 selectNextIndex();
228 else if (command.equals("selectPreviousRow"))
230 selectPreviousIndex();
232 else if (command.equals("clearSelection"))
234 list.clearSelection();
236 else if (command.equals("selectAll"))
238 list.setSelectionInterval(0, max);
239 // this next line is to restore the lead selection index to the old
240 // position, because select-all should not change the lead index
241 list.addSelectionInterval(lead, lead);
243 else if (command.equals("selectLastRow"))
245 list.setSelectedIndex(list.getModel().getSize() - 1);
247 else if (command.equals("selectLastRowChangeLead"))
249 selModel.moveLeadSelectionIndex(list.getModel().getSize() - 1);
251 else if (command.equals("scrollDownExtendSelection"))
253 int target;
254 if (lead == list.getLastVisibleIndex())
256 target = Math.min
257 (max, lead + (list.getLastVisibleIndex() -
258 list.getFirstVisibleIndex() + 1));
260 else
261 target = list.getLastVisibleIndex();
262 selModel.setLeadSelectionIndex(target);
264 else if (command.equals("scrollDownChangeLead"))
266 int target;
267 if (lead == list.getLastVisibleIndex())
269 target = Math.min
270 (max, lead + (list.getLastVisibleIndex() -
271 list.getFirstVisibleIndex() + 1));
273 else
274 target = list.getLastVisibleIndex();
275 selModel.moveLeadSelectionIndex(target);
277 else if (command.equals("scrollUpExtendSelection"))
279 int target;
280 if (lead == list.getFirstVisibleIndex())
282 target = Math.max
283 (0, lead - (list.getLastVisibleIndex() -
284 list.getFirstVisibleIndex() + 1));
286 else
287 target = list.getFirstVisibleIndex();
288 selModel.setLeadSelectionIndex(target);
290 else if (command.equals("scrollUpChangeLead"))
292 int target;
293 if (lead == list.getFirstVisibleIndex())
295 target = Math.max
296 (0, lead - (list.getLastVisibleIndex() -
297 list.getFirstVisibleIndex() + 1));
299 else
300 target = list.getFirstVisibleIndex();
301 selModel.moveLeadSelectionIndex(target);
303 else if (command.equals("selectNextRowExtendSelection"))
305 selModel.setLeadSelectionIndex(Math.min(lead + 1,max));
307 else if (command.equals("selectFirstRow"))
309 list.setSelectedIndex(0);
311 else if (command.equals("selectFirstRowChangeLead"))
313 selModel.moveLeadSelectionIndex(0);
315 else if (command.equals("selectFirstRowExtendSelection"))
317 selModel.setLeadSelectionIndex(0);
319 else if (command.equals("selectPreviousRowExtendSelection"))
321 selModel.setLeadSelectionIndex(Math.max(0,lead - 1));
323 else if (command.equals("scrollUp"))
325 int target;
326 if (lead == list.getFirstVisibleIndex())
328 target = Math.max
329 (0, lead - (list.getLastVisibleIndex() -
330 list.getFirstVisibleIndex() + 1));
332 else
333 target = list.getFirstVisibleIndex();
334 list.setSelectedIndex(target);
336 else if (command.equals("selectLastRowExtendSelection"))
338 selModel.setLeadSelectionIndex(list.getModel().getSize() - 1);
340 else if (command.equals("scrollDown"))
342 int target;
343 if (lead == list.getLastVisibleIndex())
345 target = Math.min
346 (max, lead + (list.getLastVisibleIndex() -
347 list.getFirstVisibleIndex() + 1));
349 else
350 target = list.getLastVisibleIndex();
351 list.setSelectedIndex(target);
353 else if (command.equals("selectNextRowChangeLead"))
355 if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
356 selectNextIndex();
357 else
359 selModel.moveLeadSelectionIndex(Math.min(max, lead + 1));
362 else if (command.equals("selectPreviousRowChangeLead"))
364 if (selModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
365 selectPreviousIndex();
366 else
368 selModel.moveLeadSelectionIndex(Math.max(0, lead - 1));
371 else if (command.equals("addToSelection"))
373 list.addSelectionInterval(lead, lead);
375 else if (command.equals("extendTo"))
377 selModel.setSelectionInterval(selModel.getAnchorSelectionIndex(),
378 lead);
380 else if (command.equals("toggleAndAnchor"))
382 if (!list.isSelectedIndex(lead))
383 list.addSelectionInterval(lead, lead);
384 else
385 list.removeSelectionInterval(lead, lead);
386 selModel.setAnchorSelectionIndex(lead);
388 else
390 // DEBUG: uncomment the following line to print out
391 // key bindings that aren't implemented yet
393 // System.out.println ("not implemented: "+e.getActionCommand());
396 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
401 * A helper class which listens for {@link MouseEvent}s
402 * from the {@link JList}.
404 public class MouseInputHandler implements MouseInputListener
407 * Called when a mouse button press/release cycle completes
408 * on the {@link JList}
410 * @param event The event representing the mouse click
412 public void mouseClicked(MouseEvent event)
414 Point click = event.getPoint();
415 int index = locationToIndex(list, click);
416 if (index == -1)
417 return;
418 if (event.isShiftDown())
420 if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
421 list.setSelectedIndex(index);
422 else if (list.getSelectionMode() ==
423 ListSelectionModel.SINGLE_INTERVAL_SELECTION)
424 // COMPAT: the IBM VM is compatible with the following line of code.
425 // However, compliance with Sun's VM would correspond to replacing
426 // getAnchorSelectionIndex() with getLeadSelectionIndex().This is
427 // both unnatural and contradictory to the way they handle other
428 // similar UI interactions.
429 list.setSelectionInterval(list.getAnchorSelectionIndex(), index);
430 else
431 // COMPAT: both Sun and IBM are compatible instead with:
432 // list.setSelectionInterval
433 // (list.getLeadSelectionIndex(),index);
434 // Note that for IBM this is contradictory to what they did in
435 // the above situation for SINGLE_INTERVAL_SELECTION.
436 // The most natural thing to do is the following:
437 if (list.isSelectedIndex(list.getAnchorSelectionIndex()))
438 list.getSelectionModel().setLeadSelectionIndex(index);
439 else
440 list.addSelectionInterval(list.getAnchorSelectionIndex(), index);
442 else if (event.isControlDown())
444 if (list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)
445 list.setSelectedIndex(index);
446 else if (list.isSelectedIndex(index))
447 list.removeSelectionInterval(index,index);
448 else
449 list.addSelectionInterval(index,index);
451 else
452 list.setSelectedIndex(index);
454 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
458 * Called when a mouse button is pressed down on the
459 * {@link JList}.
461 * @param event The event representing the mouse press
463 public void mousePressed(MouseEvent event)
465 // TODO: What should be done here, if anything?
469 * Called when a mouse button is released on
470 * the {@link JList}
472 * @param event The event representing the mouse press
474 public void mouseReleased(MouseEvent event)
476 // TODO: What should be done here, if anything?
480 * Called when the mouse pointer enters the area bounded
481 * by the {@link JList}
483 * @param event The event representing the mouse entry
485 public void mouseEntered(MouseEvent event)
487 // TODO: What should be done here, if anything?
491 * Called when the mouse pointer leaves the area bounded
492 * by the {@link JList}
494 * @param event The event representing the mouse exit
496 public void mouseExited(MouseEvent event)
498 // TODO: What should be done here, if anything?
502 * Called when the mouse pointer moves over the area bounded
503 * by the {@link JList} while a button is held down.
505 * @param event The event representing the mouse drag
507 public void mouseDragged(MouseEvent event)
509 Point click = event.getPoint();
510 int index = locationToIndex(list, click);
511 if (index == -1)
512 return;
513 if (!event.isShiftDown() && !event.isControlDown())
514 list.setSelectedIndex(index);
516 list.ensureIndexIsVisible(list.getLeadSelectionIndex());
520 * Called when the mouse pointer moves over the area bounded
521 * by the {@link JList}.
523 * @param event The event representing the mouse move
525 public void mouseMoved(MouseEvent event)
527 // TODO: What should be done here, if anything?
532 * Helper class which listens to {@link PropertyChangeEvent}s
533 * from the {@link JList}.
535 public class PropertyChangeHandler implements PropertyChangeListener
538 * Called when the {@link JList} changes one of its bound properties.
540 * @param e The event representing the property change
542 public void propertyChange(PropertyChangeEvent e)
544 if (e.getSource() == BasicListUI.this.list)
546 if (e.getOldValue() != null && e.getOldValue() instanceof ListModel)
547 ((ListModel) e.getOldValue()).removeListDataListener(BasicListUI.this.listDataListener);
549 if (e.getNewValue() != null && e.getNewValue() instanceof ListModel)
550 ((ListModel) e.getNewValue()).addListDataListener(BasicListUI.this.listDataListener);
552 // Update the updateLayoutStateNeeded flag.
553 if (e.getPropertyName().equals("model"))
554 updateLayoutStateNeeded |= modelChanged;
555 else if (e.getPropertyName().equals("selectionModel"))
556 updateLayoutStateNeeded |= selectionModelChanged;
557 else if (e.getPropertyName().equals("font"))
558 updateLayoutStateNeeded |= fontChanged;
559 else if (e.getPropertyName().equals("fixedCellWidth"))
560 updateLayoutStateNeeded |= fixedCellWidthChanged;
561 else if (e.getPropertyName().equals("fixedCellHeight"))
562 updateLayoutStateNeeded |= fixedCellHeightChanged;
563 else if (e.getPropertyName().equals("prototypeCellValue"))
564 updateLayoutStateNeeded |= prototypeCellValueChanged;
565 else if (e.getPropertyName().equals("cellRenderer"))
566 updateLayoutStateNeeded |= cellRendererChanged;
571 * A constant to indicate that the model has changed.
573 protected static final int modelChanged = 1;
576 * A constant to indicate that the selection model has changed.
578 protected static final int selectionModelChanged = 2;
581 * A constant to indicate that the font has changed.
583 protected static final int fontChanged = 4;
586 * A constant to indicate that the fixedCellWidth has changed.
588 protected static final int fixedCellWidthChanged = 8;
591 * A constant to indicate that the fixedCellHeight has changed.
593 protected static final int fixedCellHeightChanged = 16;
596 * A constant to indicate that the prototypeCellValue has changed.
598 protected static final int prototypeCellValueChanged = 32;
601 * A constant to indicate that the cellRenderer has changed.
603 protected static final int cellRendererChanged = 64;
606 * Creates a new BasicListUI for the component.
608 * @param c The component to create a UI for
610 * @return A new UI
612 public static ComponentUI createUI(final JComponent c)
614 return new BasicListUI();
617 /** The current focus listener. */
618 protected FocusListener focusListener;
620 /** The data listener listening to the model. */
621 protected ListDataListener listDataListener;
623 /** The selection listener listening to the selection model. */
624 protected ListSelectionListener listSelectionListener;
626 /** The mouse listener listening to the list. */
627 protected MouseInputListener mouseInputListener;
629 /** The property change listener listening to the list. */
630 protected PropertyChangeListener propertyChangeListener;
632 /** Saved reference to the list this UI was created for. */
633 protected JList list;
636 * The height of a single cell in the list. This field is used when the
637 * fixedCellHeight property of the list is set. Otherwise this field is
638 * set to <code>-1</code> and {@link #cellHeights} is used instead.
640 protected int cellHeight;
642 /** The width of a single cell in the list. */
643 protected int cellWidth;
645 /**
646 * An array of varying heights of cells in the list, in cases where each
647 * cell might have a different height. This field is used when the
648 * <code>fixedCellHeight</code> property of the list is not set. Otherwise
649 * this field is <code>null</code> and {@link #cellHeight} is used.
651 protected int[] cellHeights;
654 * A bitmask that indicates which properties of the JList have changed.
655 * When nonzero, indicates that the UI class is out of
656 * date with respect to the underlying list, and must recalculate the
657 * list layout before painting or performing size calculations.
659 * @see #modelChanged
660 * @see #selectionModelChanged
661 * @see #fontChanged
662 * @see #fixedCellWidthChanged
663 * @see #fixedCellHeightChanged
664 * @see #prototypeCellValueChanged
665 * @see #cellRendererChanged
667 protected int updateLayoutStateNeeded;
670 * The {@link CellRendererPane} that is used for painting.
672 protected CellRendererPane rendererPane;
674 /** The action bound to KeyStrokes. */
675 ListAction action;
678 * Calculate the height of a particular row. If there is a fixed {@link
679 * #cellHeight}, return it; otherwise return the specific row height
680 * requested from the {@link #cellHeights} array. If the requested row
681 * is invalid, return <code>-1</code>.
683 * @param row The row to get the height of
685 * @return The height, in pixels, of the specified row
687 protected int getRowHeight(int row)
689 int height;
690 if (cellHeights == null)
691 height = cellHeight;
692 else
694 if (row < 0 || row >= cellHeights.length)
695 height = -1;
696 else
697 height = cellHeights[row];
699 return height;
703 * Calculate the bounds of a particular cell, considering the upper left
704 * corner of the list as the origin position <code>(0,0)</code>.
706 * @param l Ignored; calculates over <code>this.list</code>
707 * @param index1 The first row to include in the bounds
708 * @param index2 The last row to incude in the bounds
710 * @return A rectangle encompassing the range of rows between
711 * <code>index1</code> and <code>index2</code> inclusive
713 public Rectangle getCellBounds(JList l, int index1, int index2)
715 maybeUpdateLayoutState();
717 if (l != list || cellWidth == -1)
718 return null;
720 int minIndex = Math.min(index1, index2);
721 int maxIndex = Math.max(index1, index2);
722 Point loc = indexToLocation(list, minIndex);
723 Rectangle bounds = new Rectangle(loc.x, loc.y, cellWidth,
724 getCellHeight(minIndex));
725 for (int i = minIndex + 1; i <= maxIndex; i++)
727 Point hiLoc = indexToLocation(list, i);
728 Rectangle hibounds = new Rectangle(hiLoc.x, hiLoc.y, cellWidth,
729 getCellHeight(i));
730 bounds = bounds.union(hibounds);
733 return bounds;
737 * Calculates the maximum cell height.
739 * @param index the index of the cell
741 * @return the maximum cell height
743 private int getCellHeight(int index)
745 int height = cellHeight;
746 if (height <= 0)
748 if (list.getLayoutOrientation() == JList.VERTICAL)
749 height = getRowHeight(index);
750 else
752 for (int j = 0; j < cellHeights.length; j++)
753 height = Math.max(height, cellHeights[j]);
756 return height;
760 * Calculate the Y coordinate of the upper edge of a particular row,
761 * considering the Y coordinate <code>0</code> to occur at the top of the
762 * list.
764 * @param row The row to calculate the Y coordinate of
766 * @return The Y coordinate of the specified row, or <code>-1</code> if
767 * the specified row number is invalid
769 protected int convertRowToY(int row)
771 int y = 0;
772 for (int i = 0; i < row; ++i)
774 int h = getRowHeight(i);
775 if (h == -1)
776 return -1;
777 y += h;
779 return y;
783 * Calculate the row number containing a particular Y coordinate,
784 * considering the Y coodrinate <code>0</code> to occur at the top of the
785 * list.
787 * @param y0 The Y coordinate to calculate the row number for
789 * @return The row number containing the specified Y value, or <code>-1</code>
790 * if the list model is empty
792 * @specnote This method is specified to return -1 for an invalid Y
793 * coordinate. However, some simple tests show that the behaviour
794 * is to return the index of the last list element for an Y
795 * coordinate that lies outside of the list bounds (even for
796 * negative indices). <code>-1</code>
797 * is only returned if the list model is empty.
799 protected int convertYToRow(int y0)
801 if (list.getModel().getSize() == 0)
802 return -1;
804 // When y0 < 0, then the JDK returns the maximum row index of the list. So
805 // do we.
806 if (y0 < 0)
807 return list.getModel().getSize() - 1;
809 // Update the layout if necessary.
810 maybeUpdateLayoutState();
812 int index = list.getModel().getSize() - 1;
814 // If a fixed cell height is set, then we can work more efficient.
815 if (cellHeight > 0)
816 index = Math.min(y0 / cellHeight, index);
817 // If we have no fixed cell height, we must add up each cell height up
818 // to y0.
819 else
821 int h = 0;
822 for (int row = 0; row < cellHeights.length; ++row)
824 h += cellHeights[row];
825 if (y0 < h)
827 index = row;
828 break;
832 return index;
836 * Recomputes the {@link #cellHeights}, {@link #cellHeight}, and {@link
837 * #cellWidth} properties by examining the variouis properties of the
838 * {@link JList}.
840 protected void updateLayoutState()
842 int nrows = list.getModel().getSize();
843 cellHeight = -1;
844 cellWidth = -1;
845 if (cellHeights == null || cellHeights.length != nrows)
846 cellHeights = new int[nrows];
847 ListCellRenderer rend = list.getCellRenderer();
848 // Update the cellHeight(s) fields.
849 int fixedCellHeight = list.getFixedCellHeight();
850 if (fixedCellHeight > 0)
852 cellHeight = fixedCellHeight;
853 cellHeights = null;
855 else
857 cellHeight = -1;
858 for (int i = 0; i < nrows; ++i)
860 Component flyweight =
861 rend.getListCellRendererComponent(list,
862 list.getModel().getElementAt(i),
863 i, list.isSelectedIndex(i),
864 list.getSelectionModel().getAnchorSelectionIndex() == i);
865 Dimension dim = flyweight.getPreferredSize();
866 cellHeights[i] = dim.height;
870 // Update the cellWidth field.
871 int fixedCellWidth = list.getFixedCellWidth();
872 if (fixedCellWidth > 0)
873 cellWidth = fixedCellWidth;
874 else
876 for (int i = 0; i < nrows; ++i)
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 cellWidth = Math.max(cellWidth, dim.width);
886 if (list.getLayoutOrientation() == JList.VERTICAL)
887 cellWidth = Math.max(cellWidth, list.getSize().width);
892 * Calls {@link #updateLayoutState} if {@link #updateLayoutStateNeeded}
893 * is nonzero, then resets {@link #updateLayoutStateNeeded} to zero.
895 protected void maybeUpdateLayoutState()
897 if (updateLayoutStateNeeded != 0 || !list.isValid())
899 updateLayoutState();
900 updateLayoutStateNeeded = 0;
905 * Creates a new BasicListUI object.
907 public BasicListUI()
909 updateLayoutStateNeeded = 1;
910 rendererPane = new CellRendererPane();
914 * Installs various default settings (mostly colors) from the {@link
915 * UIDefaults} into the {@link JList}
917 * @see #uninstallDefaults
919 protected void installDefaults()
921 LookAndFeel.installColorsAndFont(list, "List.background",
922 "List.foreground", "List.font");
923 list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
924 list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
925 list.setOpaque(true);
929 * Resets to <code>null</code> those defaults which were installed in
930 * {@link #installDefaults}
932 protected void uninstallDefaults()
934 list.setForeground(null);
935 list.setBackground(null);
936 list.setSelectionForeground(null);
937 list.setSelectionBackground(null);
941 * Attaches all the listeners we have in the UI class to the {@link
942 * JList}, its model and its selection model.
944 * @see #uninstallListeners
946 protected void installListeners()
948 if (focusListener == null)
949 focusListener = createFocusListener();
950 list.addFocusListener(focusListener);
951 if (listDataListener == null)
952 listDataListener = createListDataListener();
953 list.getModel().addListDataListener(listDataListener);
954 if (listSelectionListener == null)
955 listSelectionListener = createListSelectionListener();
956 list.addListSelectionListener(listSelectionListener);
957 if (mouseInputListener == null)
958 mouseInputListener = createMouseInputListener();
959 list.addMouseListener(mouseInputListener);
960 list.addMouseMotionListener(mouseInputListener);
961 if (propertyChangeListener == null)
962 propertyChangeListener = createPropertyChangeListener();
963 list.addPropertyChangeListener(propertyChangeListener);
967 * Detaches all the listeners we attached in {@link #installListeners}.
969 protected void uninstallListeners()
971 list.removeFocusListener(focusListener);
972 list.getModel().removeListDataListener(listDataListener);
973 list.removeListSelectionListener(listSelectionListener);
974 list.removeMouseListener(mouseInputListener);
975 list.removeMouseMotionListener(mouseInputListener);
976 list.removePropertyChangeListener(propertyChangeListener);
980 * Installs keyboard actions for this UI in the {@link JList}.
982 protected void installKeyboardActions()
984 InputMap focusInputMap = (InputMap) UIManager.get("List.focusInputMap");
985 InputMapUIResource parentInputMap = new InputMapUIResource();
986 // FIXME: The JDK uses a LazyActionMap for parentActionMap
987 ActionMap parentActionMap = new ActionMapUIResource();
988 action = new ListAction();
989 Object keys[] = focusInputMap.allKeys();
990 // Register key bindings in the UI InputMap-ActionMap pair
991 for (int i = 0; i < keys.length; i++)
993 KeyStroke stroke = (KeyStroke)keys[i];
994 String actionString = (String) focusInputMap.get(stroke);
995 parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
996 stroke.getModifiers()),
997 actionString);
999 parentActionMap.put (actionString,
1000 new ActionListenerProxy(action, actionString));
1002 // Register the new InputMap-ActionMap as the parents of the list's
1003 // InputMap and ActionMap
1004 parentInputMap.setParent(list.getInputMap().getParent());
1005 parentActionMap.setParent(list.getActionMap().getParent());
1006 list.getInputMap().setParent(parentInputMap);
1007 list.getActionMap().setParent(parentActionMap);
1011 * Uninstalls keyboard actions for this UI in the {@link JList}.
1013 protected void uninstallKeyboardActions()
1015 // TODO: Implement this properly.
1019 * Installs the various aspects of the UI in the {@link JList}. In
1020 * particular, calls {@link #installDefaults}, {@link #installListeners}
1021 * and {@link #installKeyboardActions}. Also saves a reference to the
1022 * provided component, cast to a {@link JList}.
1024 * @param c The {@link JList} to install the UI into
1026 public void installUI(final JComponent c)
1028 super.installUI(c);
1029 list = (JList) c;
1030 installDefaults();
1031 installListeners();
1032 installKeyboardActions();
1033 maybeUpdateLayoutState();
1037 * Uninstalls all the aspects of the UI which were installed in {@link
1038 * #installUI}. When finished uninstalling, drops the saved reference to
1039 * the {@link JList}.
1041 * @param c Ignored; the UI is uninstalled from the {@link JList}
1042 * reference saved during the call to {@link #installUI}
1044 public void uninstallUI(final JComponent c)
1046 uninstallKeyboardActions();
1047 uninstallListeners();
1048 uninstallDefaults();
1049 list = null;
1053 * Gets the size this list would prefer to assume. This is calculated by
1054 * calling {@link #getCellBounds} over the entire list.
1056 * @param c Ignored; uses the saved {@link JList} reference
1058 * @return DOCUMENT ME!
1060 public Dimension getPreferredSize(JComponent c)
1062 maybeUpdateLayoutState();
1063 int size = list.getModel().getSize();
1064 int visibleRows = list.getVisibleRowCount();
1065 int layoutOrientation = list.getLayoutOrientation();
1067 int h;
1068 int w;
1069 int maxCellHeight = cellHeight;
1070 if (maxCellHeight <= 0)
1072 for (int i = 0; i < cellHeights.length; i++)
1073 maxCellHeight = Math.max(maxCellHeight, cellHeights[i]);
1075 if (layoutOrientation == JList.HORIZONTAL_WRAP)
1077 if (visibleRows > 0)
1079 // We cast to double here to force double divisions.
1080 double modelSize = size;
1081 int neededColumns = (int) Math.ceil(modelSize / visibleRows);
1082 int adjustedRows = (int) Math.ceil(modelSize / neededColumns);
1083 h = maxCellHeight * adjustedRows;
1084 w = cellWidth * neededColumns;
1086 else
1088 int neededColumns = Math.min(1, list.getWidth() / cellWidth);
1089 h = size / neededColumns * maxCellHeight;
1090 w = neededColumns * cellWidth;
1093 else if (layoutOrientation == JList.VERTICAL_WRAP)
1095 if (visibleRows > 0)
1096 h = visibleRows * maxCellHeight;
1097 else
1098 h = Math.max(list.getHeight(), maxCellHeight);
1099 int neededColumns = h / maxCellHeight;
1100 w = cellWidth * neededColumns;
1102 else
1104 if (list.getFixedCellWidth() > 0)
1105 w = list.getFixedCellWidth();
1106 else
1107 w = cellWidth;
1108 if (list.getFixedCellHeight() > 0)
1109 // FIXME: We need to add some cellVerticalMargins here, according
1110 // to the specs.
1111 h = list.getFixedCellHeight() * size;
1112 else
1113 h = maxCellHeight * size;
1115 Insets insets = list.getInsets();
1116 Dimension retVal = new Dimension(w + insets.left + insets.right,
1117 h + insets.top + insets.bottom);
1118 return retVal;
1122 * Paints a single cell in the list.
1124 * @param g The graphics context to paint in
1125 * @param row The row number to paint
1126 * @param bounds The bounds of the cell to paint, assuming a coordinate
1127 * system beginning at <code>(0,0)</code> in the upper left corner of the
1128 * list
1129 * @param rend A cell renderer to paint with
1130 * @param data The data to provide to the cell renderer
1131 * @param sel A selection model to provide to the cell renderer
1132 * @param lead The lead selection index of the list
1134 protected void paintCell(Graphics g, int row, Rectangle bounds,
1135 ListCellRenderer rend, ListModel data,
1136 ListSelectionModel sel, int lead)
1138 boolean isSel = list.isSelectedIndex(row);
1139 boolean hasFocus = (list.getLeadSelectionIndex() == row) && BasicListUI.this.list.hasFocus();
1140 Component comp = rend.getListCellRendererComponent(list,
1141 data.getElementAt(row),
1142 0, isSel, hasFocus);
1143 rendererPane.paintComponent(g, comp, list, bounds);
1147 * Paints the list by repeatedly calling {@link #paintCell} for each visible
1148 * cell in the list.
1150 * @param g The graphics context to paint with
1151 * @param c Ignored; uses the saved {@link JList} reference
1153 public void paint(Graphics g, JComponent c)
1155 int nrows = list.getModel().getSize();
1156 if (nrows == 0)
1157 return;
1159 maybeUpdateLayoutState();
1160 ListCellRenderer render = list.getCellRenderer();
1161 ListModel model = list.getModel();
1162 ListSelectionModel sel = list.getSelectionModel();
1163 int lead = sel.getLeadSelectionIndex();
1164 Rectangle clip = g.getClipBounds();
1166 int startIndex = locationToIndex(list, new Point(clip.x, clip.y));
1167 int endIndex = locationToIndex(list, new Point(clip.x + clip.width,
1168 clip.y + clip.height));
1170 for (int row = startIndex; row <= endIndex; ++row)
1172 Rectangle bounds = getCellBounds(list, row, row);
1173 if (bounds.intersects(clip))
1174 paintCell(g, row, bounds, render, model, sel, lead);
1179 * Computes the index of a list cell given a point within the list. If the
1180 * location lies outside the bounds of the list, the greatest index in the
1181 * list model is returned.
1183 * @param l the list which on which the computation is based on
1184 * @param location the coordinates
1186 * @return the index of the list item that is located at the given
1187 * coordinates or <code>-1</code> if the list model is empty
1189 public int locationToIndex(JList l, Point location)
1191 int layoutOrientation = list.getLayoutOrientation();
1192 int index = -1;
1193 switch (layoutOrientation)
1195 case JList.VERTICAL:
1196 index = convertYToRow(location.y);
1197 break;
1198 case JList.HORIZONTAL_WRAP:
1199 // determine visible rows and cells per row
1200 int maxCellHeight = getCellHeight(0);
1201 int visibleRows = list.getHeight() / maxCellHeight;
1202 int cellsPerRow = -1;
1203 int numberOfItems = list.getModel().getSize();
1204 cellsPerRow = numberOfItems / visibleRows + 1;
1206 // determine index for the given location
1207 int cellsPerColumn = numberOfItems / cellsPerRow + 1;
1208 int gridX = Math.min(location.x / cellWidth, cellsPerRow - 1);
1209 int gridY = Math.min(location.y / maxCellHeight, cellsPerColumn);
1210 index = gridX + gridY * cellsPerRow;
1211 break;
1212 case JList.VERTICAL_WRAP:
1213 // determine visible rows and cells per column
1214 int maxCellHeight2 = getCellHeight(0);
1215 int visibleRows2 = list.getHeight() / maxCellHeight2;
1216 int numberOfItems2 = list.getModel().getSize();
1217 int cellsPerRow2 = numberOfItems2 / visibleRows2 + 1;
1219 int gridX2 = Math.min(location.x / cellWidth, cellsPerRow2 - 1);
1220 int gridY2 = Math.min(location.y / maxCellHeight2, visibleRows2);
1221 index = gridY2 + gridX2 * visibleRows2;
1222 break;
1224 return index;
1227 public Point indexToLocation(JList l, int index)
1229 int layoutOrientation = list.getLayoutOrientation();
1230 Point loc = null;
1231 switch (layoutOrientation)
1233 case JList.VERTICAL:
1234 loc = new Point(0, convertRowToY(index));
1235 break;
1236 case JList.HORIZONTAL_WRAP:
1237 // determine visible rows and cells per row
1238 int maxCellHeight = getCellHeight(0);
1239 int visibleRows = list.getHeight() / maxCellHeight;
1240 int numberOfCellsPerRow = -1;
1241 int numberOfItems = list.getModel().getSize();
1242 numberOfCellsPerRow = numberOfItems / visibleRows + 1;
1244 // compute coordinates inside the grid
1245 int gridX = index % numberOfCellsPerRow;
1246 int gridY = index / numberOfCellsPerRow;
1247 int locX = gridX * cellWidth;
1248 int locY;
1249 locY = gridY * maxCellHeight;
1250 loc = new Point(locX, locY);
1251 break;
1252 case JList.VERTICAL_WRAP:
1253 // determine visible rows and cells per column
1254 int maxCellHeight2 = getCellHeight(0);
1255 int visibleRows2 = list.getHeight() / maxCellHeight2;
1256 // compute coordinates inside the grid
1257 if (visibleRows2 > 0)
1259 int gridY2 = index % visibleRows2;
1260 int gridX2 = index / visibleRows2;
1261 int locX2 = gridX2 * cellWidth;
1262 int locY2 = gridY2 * maxCellHeight2;
1263 loc = new Point(locX2, locY2);
1265 else
1266 loc = new Point(0, convertRowToY(index));
1267 break;
1269 return loc;
1273 * Creates and returns the focus listener for this UI.
1275 * @return the focus listener for this UI
1277 protected FocusListener createFocusListener()
1279 return new FocusHandler();
1283 * Creates and returns the list data listener for this UI.
1285 * @return the list data listener for this UI
1287 protected ListDataListener createListDataListener()
1289 return new ListDataHandler();
1293 * Creates and returns the list selection listener for this UI.
1295 * @return the list selection listener for this UI
1297 protected ListSelectionListener createListSelectionListener()
1299 return new ListSelectionHandler();
1303 * Creates and returns the mouse input listener for this UI.
1305 * @return the mouse input listener for this UI
1307 protected MouseInputListener createMouseInputListener()
1309 return new MouseInputHandler();
1313 * Creates and returns the property change listener for this UI.
1315 * @return the property change listener for this UI
1317 protected PropertyChangeListener createPropertyChangeListener()
1319 return new PropertyChangeHandler();
1323 * Selects the next list item and force it to be visible.
1325 protected void selectNextIndex()
1327 int index = list.getSelectionModel().getLeadSelectionIndex();
1328 if (index < list.getModel().getSize() - 1)
1330 index++;
1331 list.setSelectedIndex(index);
1333 list.ensureIndexIsVisible(index);
1337 * Selects the previous list item and force it to be visible.
1339 protected void selectPreviousIndex()
1341 int index = list.getSelectionModel().getLeadSelectionIndex();
1342 if (index > 0)
1344 index--;
1345 list.setSelectedIndex(index);
1347 list.ensureIndexIsVisible(index);