blob: a7ec88835b84b09dac0f07529ef1a5ed21224860 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
25
26package javax.swing.plaf.basic;
27
28import sun.swing.DefaultLookup;
29import sun.swing.UIAction;
30
31import javax.swing.*;
32import javax.swing.event.*;
33import javax.swing.plaf.*;
34import javax.swing.text.Position;
35
36import java.awt.*;
37import java.awt.event.*;
38import java.awt.datatransfer.Transferable;
39import java.awt.geom.Point2D;
40
41import java.beans.PropertyChangeListener;
42import java.beans.PropertyChangeEvent;
43
44import sun.swing.SwingUtilities2;
45import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
46
47/**
48 * An extensible implementation of {@code ListUI}.
49 * <p>
50 * {@code BasicListUI} instances cannot be shared between multiple
51 * lists.
52 *
53 * @author Hans Muller
54 * @author Philip Milne
55 * @author Shannon Hickey (drag and drop)
56 */
57public class BasicListUI extends ListUI
58{
59 private static final StringBuilder BASELINE_COMPONENT_KEY =
60 new StringBuilder("List.baselineComponent");
61
62 protected JList list = null;
63 protected CellRendererPane rendererPane;
64
65 // Listeners that this UI attaches to the JList
66 protected FocusListener focusListener;
67 protected MouseInputListener mouseInputListener;
68 protected ListSelectionListener listSelectionListener;
69 protected ListDataListener listDataListener;
70 protected PropertyChangeListener propertyChangeListener;
71 private Handler handler;
72
73 protected int[] cellHeights = null;
74 protected int cellHeight = -1;
75 protected int cellWidth = -1;
76 protected int updateLayoutStateNeeded = modelChanged;
77 /**
78 * Height of the list. When asked to paint, if the current size of
79 * the list differs, this will update the layout state.
80 */
81 private int listHeight;
82
83 /**
84 * Width of the list. When asked to paint, if the current size of
85 * the list differs, this will update the layout state.
86 */
87 private int listWidth;
88
89 /**
90 * The layout orientation of the list.
91 */
92 private int layoutOrientation;
93
94 // Following ivars are used if the list is laying out horizontally
95
96 /**
97 * Number of columns to create.
98 */
99 private int columnCount;
100 /**
101 * Preferred height to make the list, this is only used if the
102 * the list is layed out horizontally.
103 */
104 private int preferredHeight;
105 /**
106 * Number of rows per column. This is only used if the row height is
107 * fixed.
108 */
109 private int rowsPerColumn;
110
111 /**
112 * The time factor to treate the series of typed alphanumeric key
113 * as prefix for first letter navigation.
114 */
115 private long timeFactor = 1000L;
116
117 /**
118 * Local cache of JList's client property "List.isFileList"
119 */
120 private boolean isFileList = false;
121
122 /**
123 * Local cache of JList's component orientation property
124 */
125 private boolean isLeftToRight = true;
126
127 /* The bits below define JList property changes that affect layout.
128 * When one of these properties changes we set a bit in
129 * updateLayoutStateNeeded. The change is dealt with lazily, see
130 * maybeUpdateLayoutState. Changes to the JLists model, e.g. the
131 * models length changed, are handled similarly, see DataListener.
132 */
133
134 protected final static int modelChanged = 1 << 0;
135 protected final static int selectionModelChanged = 1 << 1;
136 protected final static int fontChanged = 1 << 2;
137 protected final static int fixedCellWidthChanged = 1 << 3;
138 protected final static int fixedCellHeightChanged = 1 << 4;
139 protected final static int prototypeCellValueChanged = 1 << 5;
140 protected final static int cellRendererChanged = 1 << 6;
141 private final static int layoutOrientationChanged = 1 << 7;
142 private final static int heightChanged = 1 << 8;
143 private final static int widthChanged = 1 << 9;
144 private final static int componentOrientationChanged = 1 << 10;
145
146 private static final int DROP_LINE_THICKNESS = 2;
147
148 static void loadActionMap(LazyActionMap map) {
149 map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN));
150 map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_EXTEND));
151 map.put(new Actions(Actions.SELECT_PREVIOUS_COLUMN_CHANGE_LEAD));
152 map.put(new Actions(Actions.SELECT_NEXT_COLUMN));
153 map.put(new Actions(Actions.SELECT_NEXT_COLUMN_EXTEND));
154 map.put(new Actions(Actions.SELECT_NEXT_COLUMN_CHANGE_LEAD));
155 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW));
156 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_EXTEND));
157 map.put(new Actions(Actions.SELECT_PREVIOUS_ROW_CHANGE_LEAD));
158 map.put(new Actions(Actions.SELECT_NEXT_ROW));
159 map.put(new Actions(Actions.SELECT_NEXT_ROW_EXTEND));
160 map.put(new Actions(Actions.SELECT_NEXT_ROW_CHANGE_LEAD));
161 map.put(new Actions(Actions.SELECT_FIRST_ROW));
162 map.put(new Actions(Actions.SELECT_FIRST_ROW_EXTEND));
163 map.put(new Actions(Actions.SELECT_FIRST_ROW_CHANGE_LEAD));
164 map.put(new Actions(Actions.SELECT_LAST_ROW));
165 map.put(new Actions(Actions.SELECT_LAST_ROW_EXTEND));
166 map.put(new Actions(Actions.SELECT_LAST_ROW_CHANGE_LEAD));
167 map.put(new Actions(Actions.SCROLL_UP));
168 map.put(new Actions(Actions.SCROLL_UP_EXTEND));
169 map.put(new Actions(Actions.SCROLL_UP_CHANGE_LEAD));
170 map.put(new Actions(Actions.SCROLL_DOWN));
171 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND));
172 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_LEAD));
173 map.put(new Actions(Actions.SELECT_ALL));
174 map.put(new Actions(Actions.CLEAR_SELECTION));
175 map.put(new Actions(Actions.ADD_TO_SELECTION));
176 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
177 map.put(new Actions(Actions.EXTEND_TO));
178 map.put(new Actions(Actions.MOVE_SELECTION_TO));
179
180 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
181 TransferHandler.getCutAction());
182 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
183 TransferHandler.getCopyAction());
184 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
185 TransferHandler.getPasteAction());
186 }
187
188 /**
189 * Paint one List cell: compute the relevant state, get the "rubber stamp"
190 * cell renderer component, and then use the CellRendererPane to paint it.
191 * Subclasses may want to override this method rather than paint().
192 *
193 * @see #paint
194 */
195 protected void paintCell(
196 Graphics g,
197 int row,
198 Rectangle rowBounds,
199 ListCellRenderer cellRenderer,
200 ListModel dataModel,
201 ListSelectionModel selModel,
202 int leadIndex)
203 {
204 Object value = dataModel.getElementAt(row);
205 boolean cellHasFocus = list.hasFocus() && (row == leadIndex);
206 boolean isSelected = selModel.isSelectedIndex(row);
207
208 Component rendererComponent =
209 cellRenderer.getListCellRendererComponent(list, value, row, isSelected, cellHasFocus);
210
211 int cx = rowBounds.x;
212 int cy = rowBounds.y;
213 int cw = rowBounds.width;
214 int ch = rowBounds.height;
215
216 if (isFileList) {
217 // Shrink renderer to preferred size. This is mostly used on Windows
218 // where selection is only shown around the file name, instead of
219 // across the whole list cell.
220 int w = Math.min(cw, rendererComponent.getPreferredSize().width + 4);
221 if (!isLeftToRight) {
222 cx += (cw - w);
223 }
224 cw = w;
225 }
226
227 rendererPane.paintComponent(g, rendererComponent, list, cx, cy, cw, ch, true);
228 }
229
230
231 /**
232 * Paint the rows that intersect the Graphics objects clipRect. This
233 * method calls paintCell as necessary. Subclasses
234 * may want to override these methods.
235 *
236 * @see #paintCell
237 */
238 public void paint(Graphics g, JComponent c) {
239 Shape clip = g.getClip();
240 paintImpl(g, c);
241 g.setClip(clip);
242
243 paintDropLine(g);
244 }
245
246 private void paintImpl(Graphics g, JComponent c)
247 {
248 switch (layoutOrientation) {
249 case JList.VERTICAL_WRAP:
250 if (list.getHeight() != listHeight) {
251 updateLayoutStateNeeded |= heightChanged;
252 redrawList();
253 }
254 break;
255 case JList.HORIZONTAL_WRAP:
256 if (list.getWidth() != listWidth) {
257 updateLayoutStateNeeded |= widthChanged;
258 redrawList();
259 }
260 break;
261 default:
262 break;
263 }
264 maybeUpdateLayoutState();
265
266 ListCellRenderer renderer = list.getCellRenderer();
267 ListModel dataModel = list.getModel();
268 ListSelectionModel selModel = list.getSelectionModel();
269 int size;
270
271 if ((renderer == null) || (size = dataModel.getSize()) == 0) {
272 return;
273 }
274
275 // Determine how many columns we need to paint
276 Rectangle paintBounds = g.getClipBounds();
277
278 int startColumn, endColumn;
279 if (c.getComponentOrientation().isLeftToRight()) {
280 startColumn = convertLocationToColumn(paintBounds.x,
281 paintBounds.y);
282 endColumn = convertLocationToColumn(paintBounds.x +
283 paintBounds.width,
284 paintBounds.y);
285 } else {
286 startColumn = convertLocationToColumn(paintBounds.x +
287 paintBounds.width,
288 paintBounds.y);
289 endColumn = convertLocationToColumn(paintBounds.x,
290 paintBounds.y);
291 }
292 int maxY = paintBounds.y + paintBounds.height;
293 int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
294 int rowIncrement = (layoutOrientation == JList.HORIZONTAL_WRAP) ?
295 columnCount : 1;
296
297
298 for (int colCounter = startColumn; colCounter <= endColumn;
299 colCounter++) {
300 // And then how many rows in this columnn
301 int row = convertLocationToRowInColumn(paintBounds.y, colCounter);
302 int rowCount = getRowCount(colCounter);
303 int index = getModelIndex(colCounter, row);
304 Rectangle rowBounds = getCellBounds(list, index, index);
305
306 if (rowBounds == null) {
307 // Not valid, bail!
308 return;
309 }
310 while (row < rowCount && rowBounds.y < maxY &&
311 index < size) {
312 rowBounds.height = getHeight(colCounter, row);
313 g.setClip(rowBounds.x, rowBounds.y, rowBounds.width,
314 rowBounds.height);
315 g.clipRect(paintBounds.x, paintBounds.y, paintBounds.width,
316 paintBounds.height);
317 paintCell(g, index, rowBounds, renderer, dataModel, selModel,
318 leadIndex);
319 rowBounds.y += rowBounds.height;
320 index += rowIncrement;
321 row++;
322 }
323 }
324 // Empty out the renderer pane, allowing renderers to be gc'ed.
325 rendererPane.removeAll();
326 }
327
328 private void paintDropLine(Graphics g) {
329 JList.DropLocation loc = list.getDropLocation();
330 if (loc == null || !loc.isInsert()) {
331 return;
332 }
333
334 Color c = DefaultLookup.getColor(list, this, "List.dropLineColor", null);
335 if (c != null) {
336 g.setColor(c);
337 Rectangle rect = getDropLineRect(loc);
338 g.fillRect(rect.x, rect.y, rect.width, rect.height);
339 }
340 }
341
342 private Rectangle getDropLineRect(JList.DropLocation loc) {
343 int size = list.getModel().getSize();
344
345 if (size == 0) {
346 Insets insets = list.getInsets();
347 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
348 if (isLeftToRight) {
349 return new Rectangle(insets.left, insets.top, DROP_LINE_THICKNESS, 20);
350 } else {
351 return new Rectangle(list.getWidth() - DROP_LINE_THICKNESS - insets.right,
352 insets.top, DROP_LINE_THICKNESS, 20);
353 }
354 } else {
355 return new Rectangle(insets.left, insets.top,
356 list.getWidth() - insets.left - insets.right,
357 DROP_LINE_THICKNESS);
358 }
359 }
360
361 Rectangle rect = null;
362 int index = loc.getIndex();
363 boolean decr = false;
364
365 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
366 if (index == size) {
367 decr = true;
368 } else if (index != 0 && convertModelToRow(index)
369 != convertModelToRow(index - 1)) {
370
371 Rectangle prev = getCellBounds(list, index - 1);
372 Rectangle me = getCellBounds(list, index);
373 Point p = loc.getDropPoint();
374
375 if (isLeftToRight) {
376 decr = Point2D.distance(prev.x + prev.width,
377 prev.y + (int)(prev.height / 2.0),
378 p.x, p.y)
379 < Point2D.distance(me.x,
380 me.y + (int)(me.height / 2.0),
381 p.x, p.y);
382 } else {
383 decr = Point2D.distance(prev.x,
384 prev.y + (int)(prev.height / 2.0),
385 p.x, p.y)
386 < Point2D.distance(me.x + me.width,
387 me.y + (int)(prev.height / 2.0),
388 p.x, p.y);
389 }
390 }
391
392 if (decr) {
393 index--;
394 rect = getCellBounds(list, index);
395 if (isLeftToRight) {
396 rect.x += rect.width;
397 } else {
398 rect.x -= DROP_LINE_THICKNESS;
399 }
400 } else {
401 rect = getCellBounds(list, index);
402 if (!isLeftToRight) {
403 rect.x += rect.width - DROP_LINE_THICKNESS;
404 }
405 }
406
407 if (rect.x >= list.getWidth()) {
408 rect.x = list.getWidth() - DROP_LINE_THICKNESS;
409 } else if (rect.x < 0) {
410 rect.x = 0;
411 }
412
413 rect.width = DROP_LINE_THICKNESS;
414 } else if (layoutOrientation == JList.VERTICAL_WRAP) {
415 if (index == size) {
416 index--;
417 rect = getCellBounds(list, index);
418 rect.y += rect.height;
419 } else if (index != 0 && convertModelToColumn(index)
420 != convertModelToColumn(index - 1)) {
421
422 Rectangle prev = getCellBounds(list, index - 1);
423 Rectangle me = getCellBounds(list, index);
424 Point p = loc.getDropPoint();
425 if (Point2D.distance(prev.x + (int)(prev.width / 2.0),
426 prev.y + prev.height,
427 p.x, p.y)
428 < Point2D.distance(me.x + (int)(me.width / 2.0),
429 me.y,
430 p.x, p.y)) {
431
432 index--;
433 rect = getCellBounds(list, index);
434 rect.y += rect.height;
435 } else {
436 rect = getCellBounds(list, index);
437 }
438 } else {
439 rect = getCellBounds(list, index);
440 }
441
442 if (rect.y >= list.getHeight()) {
443 rect.y = list.getHeight() - DROP_LINE_THICKNESS;
444 }
445
446 rect.height = DROP_LINE_THICKNESS;
447 } else {
448 if (index == size) {
449 index--;
450 rect = getCellBounds(list, index);
451 rect.y += rect.height;
452 } else {
453 rect = getCellBounds(list, index);
454 }
455
456 if (rect.y >= list.getHeight()) {
457 rect.y = list.getHeight() - DROP_LINE_THICKNESS;
458 }
459
460 rect.height = DROP_LINE_THICKNESS;
461 }
462
463 return rect;
464 }
465
466 /**
467 * Returns the baseline.
468 *
469 * @throws NullPointerException {@inheritDoc}
470 * @throws IllegalArgumentException {@inheritDoc}
471 * @see javax.swing.JComponent#getBaseline(int, int)
472 * @since 1.6
473 */
474 public int getBaseline(JComponent c, int width, int height) {
475 super.getBaseline(c, width, height);
476 int rowHeight = list.getFixedCellHeight();
477 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
478 Component renderer = (Component)lafDefaults.get(
479 BASELINE_COMPONENT_KEY);
480 if (renderer == null) {
481 ListCellRenderer lcr = (ListCellRenderer)UIManager.get(
482 "List.cellRenderer");
483 renderer = lcr.getListCellRendererComponent(
484 list, "a", -1, false, false);
485 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
486 }
487 renderer.setFont(list.getFont());
488 // JList actually has much more complex behavior here.
489 // If rowHeight != -1 the rowHeight is either the max of all cell
490 // heights (layout orientation != VERTICAL), or is variable depending
491 // upon the cell. We assume a default size.
492 // We could theoretically query the real renderer, but that would
493 // not work for an empty model and the results may vary with
494 // the content.
495 if (rowHeight == -1) {
496 rowHeight = renderer.getPreferredSize().height;
497 }
498 return renderer.getBaseline(Integer.MAX_VALUE, rowHeight) +
499 list.getInsets().top;
500 }
501
502 /**
503 * Returns an enum indicating how the baseline of the component
504 * changes as the size changes.
505 *
506 * @throws NullPointerException {@inheritDoc}
507 * @see javax.swing.JComponent#getBaseline(int, int)
508 * @since 1.6
509 */
510 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
511 JComponent c) {
512 super.getBaselineResizeBehavior(c);
513 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
514 }
515
516 /**
517 * The preferredSize of the list depends upon the layout orientation.
518 * <table summary="Describes the preferred size for each layout orientation">
519 * <tr><th>Layout Orientation</th><th>Preferred Size</th></tr>
520 * <tr>
521 * <td>JList.VERTICAL
522 * <td>The preferredSize of the list is total height of the rows
523 * and the maximum width of the cells. If JList.fixedCellHeight
524 * is specified then the total height of the rows is just
525 * (cellVerticalMargins + fixedCellHeight) * model.getSize() where
526 * rowVerticalMargins is the space we allocate for drawing
527 * the yellow focus outline. Similarly if fixedCellWidth is
528 * specified then we just use that.
529 * </td>
530 * <tr>
531 * <td>JList.VERTICAL_WRAP
532 * <td>If the visible row count is greater than zero, the preferredHeight
533 * is the maximum cell height * visibleRowCount. If the visible row
534 * count is <= 0, the preferred height is either the current height
535 * of the list, or the maximum cell height, whichever is
536 * bigger. The preferred width is than the maximum cell width *
537 * number of columns needed. Where the number of columns needs is
538 * list.height / max cell height. Max cell height is either the fixed
539 * cell height, or is determined by iterating through all the cells
540 * to find the maximum height from the ListCellRenderer.
541 * <tr>
542 * <td>JList.HORIZONTAL_WRAP
543 * <td>If the visible row count is greater than zero, the preferredHeight
544 * is the maximum cell height * adjustedRowCount. Where
545 * visibleRowCount is used to determine the number of columns.
546 * Because this lays out horizontally the number of rows is
547 * then determined from the column count. For example, lets say
548 * you have a model with 10 items and the visible row count is 8.
549 * The number of columns needed to display this is 2, but you no
550 * longer need 8 rows to display this, you only need 5, thus
551 * the adjustedRowCount is 5.
552 * <p>If the visible row
553 * count is <= 0, the preferred height is dictated by the
554 * number of columns, which will be as many as can fit in the width
555 * of the <code>JList</code> (width / max cell width), with at
556 * least one column. The preferred height then becomes the
557 * model size / number of columns * maximum cell height.
558 * Max cell height is either the fixed
559 * cell height, or is determined by iterating through all the cells
560 * to find the maximum height from the ListCellRenderer.
561 * </table>
562 * The above specifies the raw preferred width and height. The resulting
563 * preferred width is the above width + insets.left + insets.right and
564 * the resulting preferred height is the above height + insets.top +
565 * insets.bottom. Where the <code>Insets</code> are determined from
566 * <code>list.getInsets()</code>.
567 *
568 * @param c The JList component.
569 * @return The total size of the list.
570 */
571 public Dimension getPreferredSize(JComponent c) {
572 maybeUpdateLayoutState();
573
574 int lastRow = list.getModel().getSize() - 1;
575 if (lastRow < 0) {
576 return new Dimension(0, 0);
577 }
578
579 Insets insets = list.getInsets();
580 int width = cellWidth * columnCount + insets.left + insets.right;
581 int height;
582
583 if (layoutOrientation != JList.VERTICAL) {
584 height = preferredHeight;
585 }
586 else {
587 Rectangle bounds = getCellBounds(list, lastRow);
588
589 if (bounds != null) {
590 height = bounds.y + bounds.height + insets.bottom;
591 }
592 else {
593 height = 0;
594 }
595 }
596 return new Dimension(width, height);
597 }
598
599
600 /**
601 * Selected the previous row and force it to be visible.
602 *
603 * @see JList#ensureIndexIsVisible
604 */
605 protected void selectPreviousIndex() {
606 int s = list.getSelectedIndex();
607 if(s > 0) {
608 s -= 1;
609 list.setSelectedIndex(s);
610 list.ensureIndexIsVisible(s);
611 }
612 }
613
614
615 /**
616 * Selected the previous row and force it to be visible.
617 *
618 * @see JList#ensureIndexIsVisible
619 */
620 protected void selectNextIndex()
621 {
622 int s = list.getSelectedIndex();
623 if((s + 1) < list.getModel().getSize()) {
624 s += 1;
625 list.setSelectedIndex(s);
626 list.ensureIndexIsVisible(s);
627 }
628 }
629
630
631 /**
632 * Registers the keyboard bindings on the <code>JList</code> that the
633 * <code>BasicListUI</code> is associated with. This method is called at
634 * installUI() time.
635 *
636 * @see #installUI
637 */
638 protected void installKeyboardActions() {
639 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
640
641 SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
642 inputMap);
643
644 LazyActionMap.installLazyActionMap(list, BasicListUI.class,
645 "List.actionMap");
646 }
647
648 InputMap getInputMap(int condition) {
649 if (condition == JComponent.WHEN_FOCUSED) {
650 InputMap keyMap = (InputMap)DefaultLookup.get(
651 list, this, "List.focusInputMap");
652 InputMap rtlKeyMap;
653
654 if (isLeftToRight ||
655 ((rtlKeyMap = (InputMap)DefaultLookup.get(list, this,
656 "List.focusInputMap.RightToLeft")) == null)) {
657 return keyMap;
658 } else {
659 rtlKeyMap.setParent(keyMap);
660 return rtlKeyMap;
661 }
662 }
663 return null;
664 }
665
666 /**
667 * Unregisters keyboard actions installed from
668 * <code>installKeyboardActions</code>.
669 * This method is called at uninstallUI() time - subclassess should
670 * ensure that all of the keyboard actions registered at installUI
671 * time are removed here.
672 *
673 * @see #installUI
674 */
675 protected void uninstallKeyboardActions() {
676 SwingUtilities.replaceUIActionMap(list, null);
677 SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED, null);
678 }
679
680
681 /**
682 * Create and install the listeners for the JList, its model, and its
683 * selectionModel. This method is called at installUI() time.
684 *
685 * @see #installUI
686 * @see #uninstallListeners
687 */
688 protected void installListeners()
689 {
690 TransferHandler th = list.getTransferHandler();
691 if (th == null || th instanceof UIResource) {
692 list.setTransferHandler(defaultTransferHandler);
693 // default TransferHandler doesn't support drop
694 // so we don't want drop handling
695 if (list.getDropTarget() instanceof UIResource) {
696 list.setDropTarget(null);
697 }
698 }
699
700 focusListener = createFocusListener();
701 mouseInputListener = createMouseInputListener();
702 propertyChangeListener = createPropertyChangeListener();
703 listSelectionListener = createListSelectionListener();
704 listDataListener = createListDataListener();
705
706 list.addFocusListener(focusListener);
707 list.addMouseListener(mouseInputListener);
708 list.addMouseMotionListener(mouseInputListener);
709 list.addPropertyChangeListener(propertyChangeListener);
710 list.addKeyListener(getHandler());
711
712 ListModel model = list.getModel();
713 if (model != null) {
714 model.addListDataListener(listDataListener);
715 }
716
717 ListSelectionModel selectionModel = list.getSelectionModel();
718 if (selectionModel != null) {
719 selectionModel.addListSelectionListener(listSelectionListener);
720 }
721 }
722
723
724 /**
725 * Remove the listeners for the JList, its model, and its
726 * selectionModel. All of the listener fields, are reset to
727 * null here. This method is called at uninstallUI() time,
728 * it should be kept in sync with installListeners.
729 *
730 * @see #uninstallUI
731 * @see #installListeners
732 */
733 protected void uninstallListeners()
734 {
735 list.removeFocusListener(focusListener);
736 list.removeMouseListener(mouseInputListener);
737 list.removeMouseMotionListener(mouseInputListener);
738 list.removePropertyChangeListener(propertyChangeListener);
739 list.removeKeyListener(getHandler());
740
741 ListModel model = list.getModel();
742 if (model != null) {
743 model.removeListDataListener(listDataListener);
744 }
745
746 ListSelectionModel selectionModel = list.getSelectionModel();
747 if (selectionModel != null) {
748 selectionModel.removeListSelectionListener(listSelectionListener);
749 }
750
751 focusListener = null;
752 mouseInputListener = null;
753 listSelectionListener = null;
754 listDataListener = null;
755 propertyChangeListener = null;
756 handler = null;
757 }
758
759
760 /**
761 * Initialize JList properties, e.g. font, foreground, and background,
762 * and add the CellRendererPane. The font, foreground, and background
763 * properties are only set if their current value is either null
764 * or a UIResource, other properties are set if the current
765 * value is null.
766 *
767 * @see #uninstallDefaults
768 * @see #installUI
769 * @see CellRendererPane
770 */
771 protected void installDefaults()
772 {
773 list.setLayout(null);
774
775 LookAndFeel.installBorder(list, "List.border");
776
777 LookAndFeel.installColorsAndFont(list, "List.background", "List.foreground", "List.font");
778
779 LookAndFeel.installProperty(list, "opaque", Boolean.TRUE);
780
781 if (list.getCellRenderer() == null) {
782 list.setCellRenderer((ListCellRenderer)(UIManager.get("List.cellRenderer")));
783 }
784
785 Color sbg = list.getSelectionBackground();
786 if (sbg == null || sbg instanceof UIResource) {
787 list.setSelectionBackground(UIManager.getColor("List.selectionBackground"));
788 }
789
790 Color sfg = list.getSelectionForeground();
791 if (sfg == null || sfg instanceof UIResource) {
792 list.setSelectionForeground(UIManager.getColor("List.selectionForeground"));
793 }
794
795 Long l = (Long)UIManager.get("List.timeFactor");
796 timeFactor = (l!=null) ? l.longValue() : 1000L;
797
798 updateIsFileList();
799 }
800
801 private void updateIsFileList() {
802 boolean b = Boolean.TRUE.equals(list.getClientProperty("List.isFileList"));
803 if (b != isFileList) {
804 isFileList = b;
805 Font oldFont = list.getFont();
806 if (oldFont == null || oldFont instanceof UIResource) {
807 Font newFont = UIManager.getFont(b ? "FileChooser.listFont" : "List.font");
808 if (newFont != null && newFont != oldFont) {
809 list.setFont(newFont);
810 }
811 }
812 }
813 }
814
815
816 /**
817 * Set the JList properties that haven't been explicitly overridden to
818 * null. A property is considered overridden if its current value
819 * is not a UIResource.
820 *
821 * @see #installDefaults
822 * @see #uninstallUI
823 * @see CellRendererPane
824 */
825 protected void uninstallDefaults()
826 {
827 LookAndFeel.uninstallBorder(list);
828 if (list.getFont() instanceof UIResource) {
829 list.setFont(null);
830 }
831 if (list.getForeground() instanceof UIResource) {
832 list.setForeground(null);
833 }
834 if (list.getBackground() instanceof UIResource) {
835 list.setBackground(null);
836 }
837 if (list.getSelectionBackground() instanceof UIResource) {
838 list.setSelectionBackground(null);
839 }
840 if (list.getSelectionForeground() instanceof UIResource) {
841 list.setSelectionForeground(null);
842 }
843 if (list.getCellRenderer() instanceof UIResource) {
844 list.setCellRenderer(null);
845 }
846 if (list.getTransferHandler() instanceof UIResource) {
847 list.setTransferHandler(null);
848 }
849 }
850
851
852 /**
853 * Initializes <code>this.list</code> by calling <code>installDefaults()</code>,
854 * <code>installListeners()</code>, and <code>installKeyboardActions()</code>
855 * in order.
856 *
857 * @see #installDefaults
858 * @see #installListeners
859 * @see #installKeyboardActions
860 */
861 public void installUI(JComponent c)
862 {
863 list = (JList)c;
864
865 layoutOrientation = list.getLayoutOrientation();
866
867 rendererPane = new CellRendererPane();
868 list.add(rendererPane);
869
870 columnCount = 1;
871
872 updateLayoutStateNeeded = modelChanged;
873 isLeftToRight = list.getComponentOrientation().isLeftToRight();
874
875 installDefaults();
876 installListeners();
877 installKeyboardActions();
878 }
879
880
881 /**
882 * Uninitializes <code>this.list</code> by calling <code>uninstallListeners()</code>,
883 * <code>uninstallKeyboardActions()</code>, and <code>uninstallDefaults()</code>
884 * in order. Sets this.list to null.
885 *
886 * @see #uninstallListeners
887 * @see #uninstallKeyboardActions
888 * @see #uninstallDefaults
889 */
890 public void uninstallUI(JComponent c)
891 {
892 uninstallListeners();
893 uninstallDefaults();
894 uninstallKeyboardActions();
895
896 cellWidth = cellHeight = -1;
897 cellHeights = null;
898
899 listWidth = listHeight = -1;
900
901 list.remove(rendererPane);
902 rendererPane = null;
903 list = null;
904 }
905
906
907 /**
908 * Returns a new instance of BasicListUI. BasicListUI delegates are
909 * allocated one per JList.
910 *
911 * @return A new ListUI implementation for the Windows look and feel.
912 */
913 public static ComponentUI createUI(JComponent list) {
914 return new BasicListUI();
915 }
916
917
918 /**
919 * {@inheritDoc}
920 * @throws NullPointerException {@inheritDoc}
921 */
922 public int locationToIndex(JList list, Point location) {
923 maybeUpdateLayoutState();
924 return convertLocationToModel(location.x, location.y);
925 }
926
927
928 /**
929 * {@inheritDoc}
930 */
931 public Point indexToLocation(JList list, int index) {
932 maybeUpdateLayoutState();
933 Rectangle rect = getCellBounds(list, index, index);
934
935 if (rect != null) {
936 return new Point(rect.x, rect.y);
937 }
938 return null;
939 }
940
941
942 /**
943 * {@inheritDoc}
944 */
945 public Rectangle getCellBounds(JList list, int index1, int index2) {
946 maybeUpdateLayoutState();
947
948 int minIndex = Math.min(index1, index2);
949 int maxIndex = Math.max(index1, index2);
950
951 if (minIndex >= list.getModel().getSize()) {
952 return null;
953 }
954
955 Rectangle minBounds = getCellBounds(list, minIndex);
956
957 if (minBounds == null) {
958 return null;
959 }
960 if (minIndex == maxIndex) {
961 return minBounds;
962 }
963 Rectangle maxBounds = getCellBounds(list, maxIndex);
964
965 if (maxBounds != null) {
966 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
967 int minRow = convertModelToRow(minIndex);
968 int maxRow = convertModelToRow(maxIndex);
969
970 if (minRow != maxRow) {
971 minBounds.x = 0;
972 minBounds.width = list.getWidth();
973 }
974 }
975 else if (minBounds.x != maxBounds.x) {
976 // Different columns
977 minBounds.y = 0;
978 minBounds.height = list.getHeight();
979 }
980 minBounds.add(maxBounds);
981 }
982 return minBounds;
983 }
984
985 /**
986 * Gets the bounds of the specified model index, returning the resulting
987 * bounds, or null if <code>index</code> is not valid.
988 */
989 private Rectangle getCellBounds(JList list, int index) {
990 maybeUpdateLayoutState();
991
992 int row = convertModelToRow(index);
993 int column = convertModelToColumn(index);
994
995 if (row == -1 || column == -1) {
996 return null;
997 }
998
999 Insets insets = list.getInsets();
1000 int x;
1001 int w = cellWidth;
1002 int y = insets.top;
1003 int h;
1004 switch (layoutOrientation) {
1005 case JList.VERTICAL_WRAP:
1006 case JList.HORIZONTAL_WRAP:
1007 if (isLeftToRight) {
1008 x = insets.left + column * cellWidth;
1009 } else {
1010 x = list.getWidth() - insets.right - (column+1) * cellWidth;
1011 }
1012 y += cellHeight * row;
1013 h = cellHeight;
1014 break;
1015 default:
1016 x = insets.left;
1017 if (cellHeights == null) {
1018 y += (cellHeight * row);
1019 }
1020 else if (row >= cellHeights.length) {
1021 y = 0;
1022 }
1023 else {
1024 for(int i = 0; i < row; i++) {
1025 y += cellHeights[i];
1026 }
1027 }
1028 w = list.getWidth() - (insets.left + insets.right);
1029 h = getRowHeight(index);
1030 break;
1031 }
1032 return new Rectangle(x, y, w, h);
1033 }
1034
1035 /**
1036 * Returns the height of the specified row based on the current layout.
1037 *
1038 * @return The specified row height or -1 if row isn't valid.
1039 * @see #convertYToRow
1040 * @see #convertRowToY
1041 * @see #updateLayoutState
1042 */
1043 protected int getRowHeight(int row)
1044 {
1045 return getHeight(0, row);
1046 }
1047
1048
1049 /**
1050 * Convert the JList relative coordinate to the row that contains it,
1051 * based on the current layout. If y0 doesn't fall within any row,
1052 * return -1.
1053 *
1054 * @return The row that contains y0, or -1.
1055 * @see #getRowHeight
1056 * @see #updateLayoutState
1057 */
1058 protected int convertYToRow(int y0)
1059 {
1060 return convertLocationToRow(0, y0, false);
1061 }
1062
1063
1064 /**
1065 * Return the JList relative Y coordinate of the origin of the specified
1066 * row or -1 if row isn't valid.
1067 *
1068 * @return The Y coordinate of the origin of row, or -1.
1069 * @see #getRowHeight
1070 * @see #updateLayoutState
1071 */
1072 protected int convertRowToY(int row)
1073 {
1074 if (row >= getRowCount(0) || row < 0) {
1075 return -1;
1076 }
1077 Rectangle bounds = getCellBounds(list, row, row);
1078 return bounds.y;
1079 }
1080
1081 /**
1082 * Returns the height of the cell at the passed in location.
1083 */
1084 private int getHeight(int column, int row) {
1085 if (column < 0 || column > columnCount || row < 0) {
1086 return -1;
1087 }
1088 if (layoutOrientation != JList.VERTICAL) {
1089 return cellHeight;
1090 }
1091 if (row >= list.getModel().getSize()) {
1092 return -1;
1093 }
1094 return (cellHeights == null) ? cellHeight :
1095 ((row < cellHeights.length) ? cellHeights[row] : -1);
1096 }
1097
1098 /**
1099 * Returns the row at location x/y.
1100 *
1101 * @param closest If true and the location doesn't exactly match a
1102 * particular location, this will return the closest row.
1103 */
1104 private int convertLocationToRow(int x, int y0, boolean closest) {
1105 int size = list.getModel().getSize();
1106
1107 if (size <= 0) {
1108 return -1;
1109 }
1110 Insets insets = list.getInsets();
1111 if (cellHeights == null) {
1112 int row = (cellHeight == 0) ? 0 :
1113 ((y0 - insets.top) / cellHeight);
1114 if (closest) {
1115 if (row < 0) {
1116 row = 0;
1117 }
1118 else if (row >= size) {
1119 row = size - 1;
1120 }
1121 }
1122 return row;
1123 }
1124 else if (size > cellHeights.length) {
1125 return -1;
1126 }
1127 else {
1128 int y = insets.top;
1129 int row = 0;
1130
1131 if (closest && y0 < y) {
1132 return 0;
1133 }
1134 int i;
1135 for (i = 0; i < size; i++) {
1136 if ((y0 >= y) && (y0 < y + cellHeights[i])) {
1137 return row;
1138 }
1139 y += cellHeights[i];
1140 row += 1;
1141 }
1142 return i - 1;
1143 }
1144 }
1145
1146 /**
1147 * Returns the closest row that starts at the specified y-location
1148 * in the passed in column.
1149 */
1150 private int convertLocationToRowInColumn(int y, int column) {
1151 int x = 0;
1152
1153 if (layoutOrientation != JList.VERTICAL) {
1154 if (isLeftToRight) {
1155 x = column * cellWidth;
1156 } else {
1157 x = list.getWidth() - (column+1)*cellWidth - list.getInsets().right;
1158 }
1159 }
1160 return convertLocationToRow(x, y, true);
1161 }
1162
1163 /**
1164 * Returns the closest location to the model index of the passed in
1165 * location.
1166 */
1167 private int convertLocationToModel(int x, int y) {
1168 int row = convertLocationToRow(x, y, true);
1169 int column = convertLocationToColumn(x, y);
1170
1171 if (row >= 0 && column >= 0) {
1172 return getModelIndex(column, row);
1173 }
1174 return -1;
1175 }
1176
1177 /**
1178 * Returns the number of rows in the given column.
1179 */
1180 private int getRowCount(int column) {
1181 if (column < 0 || column >= columnCount) {
1182 return -1;
1183 }
1184 if (layoutOrientation == JList.VERTICAL ||
1185 (column == 0 && columnCount == 1)) {
1186 return list.getModel().getSize();
1187 }
1188 if (column >= columnCount) {
1189 return -1;
1190 }
1191 if (layoutOrientation == JList.VERTICAL_WRAP) {
1192 if (column < (columnCount - 1)) {
1193 return rowsPerColumn;
1194 }
1195 return list.getModel().getSize() - (columnCount - 1) *
1196 rowsPerColumn;
1197 }
1198 // JList.HORIZONTAL_WRAP
1199 int diff = columnCount - (columnCount * rowsPerColumn -
1200 list.getModel().getSize());
1201
1202 if (column >= diff) {
1203 return Math.max(0, rowsPerColumn - 1);
1204 }
1205 return rowsPerColumn;
1206 }
1207
1208 /**
1209 * Returns the model index for the specified display location.
1210 * If <code>column</code>x<code>row</code> is beyond the length of the
1211 * model, this will return the model size - 1.
1212 */
1213 private int getModelIndex(int column, int row) {
1214 switch (layoutOrientation) {
1215 case JList.VERTICAL_WRAP:
1216 return Math.min(list.getModel().getSize() - 1, rowsPerColumn *
1217 column + Math.min(row, rowsPerColumn-1));
1218 case JList.HORIZONTAL_WRAP:
1219 return Math.min(list.getModel().getSize() - 1, row * columnCount +
1220 column);
1221 default:
1222 return row;
1223 }
1224 }
1225
1226 /**
1227 * Returns the closest column to the passed in location.
1228 */
1229 private int convertLocationToColumn(int x, int y) {
1230 if (cellWidth > 0) {
1231 if (layoutOrientation == JList.VERTICAL) {
1232 return 0;
1233 }
1234 Insets insets = list.getInsets();
1235 int col;
1236 if (isLeftToRight) {
1237 col = (x - insets.left) / cellWidth;
1238 } else {
1239 col = (list.getWidth() - x - insets.right - 1) / cellWidth;
1240 }
1241 if (col < 0) {
1242 return 0;
1243 }
1244 else if (col >= columnCount) {
1245 return columnCount - 1;
1246 }
1247 return col;
1248 }
1249 return 0;
1250 }
1251
1252 /**
1253 * Returns the row that the model index <code>index</code> will be
1254 * displayed in..
1255 */
1256 private int convertModelToRow(int index) {
1257 int size = list.getModel().getSize();
1258
1259 if ((index < 0) || (index >= size)) {
1260 return -1;
1261 }
1262
1263 if (layoutOrientation != JList.VERTICAL && columnCount > 1 &&
1264 rowsPerColumn > 0) {
1265 if (layoutOrientation == JList.VERTICAL_WRAP) {
1266 return index % rowsPerColumn;
1267 }
1268 return index / columnCount;
1269 }
1270 return index;
1271 }
1272
1273 /**
1274 * Returns the column that the model index <code>index</code> will be
1275 * displayed in.
1276 */
1277 private int convertModelToColumn(int index) {
1278 int size = list.getModel().getSize();
1279
1280 if ((index < 0) || (index >= size)) {
1281 return -1;
1282 }
1283
1284 if (layoutOrientation != JList.VERTICAL && rowsPerColumn > 0 &&
1285 columnCount > 1) {
1286 if (layoutOrientation == JList.VERTICAL_WRAP) {
1287 return index / rowsPerColumn;
1288 }
1289 return index % columnCount;
1290 }
1291 return 0;
1292 }
1293
1294 /**
1295 * If updateLayoutStateNeeded is non zero, call updateLayoutState() and reset
1296 * updateLayoutStateNeeded. This method should be called by methods
1297 * before doing any computation based on the geometry of the list.
1298 * For example it's the first call in paint() and getPreferredSize().
1299 *
1300 * @see #updateLayoutState
1301 */
1302 protected void maybeUpdateLayoutState()
1303 {
1304 if (updateLayoutStateNeeded != 0) {
1305 updateLayoutState();
1306 updateLayoutStateNeeded = 0;
1307 }
1308 }
1309
1310
1311 /**
1312 * Recompute the value of cellHeight or cellHeights based
1313 * and cellWidth, based on the current font and the current
1314 * values of fixedCellWidth, fixedCellHeight, and prototypeCellValue.
1315 *
1316 * @see #maybeUpdateLayoutState
1317 */
1318 protected void updateLayoutState()
1319 {
1320 /* If both JList fixedCellWidth and fixedCellHeight have been
1321 * set, then initialize cellWidth and cellHeight, and set
1322 * cellHeights to null.
1323 */
1324
1325 int fixedCellHeight = list.getFixedCellHeight();
1326 int fixedCellWidth = list.getFixedCellWidth();
1327
1328 cellWidth = (fixedCellWidth != -1) ? fixedCellWidth : -1;
1329
1330 if (fixedCellHeight != -1) {
1331 cellHeight = fixedCellHeight;
1332 cellHeights = null;
1333 }
1334 else {
1335 cellHeight = -1;
1336 cellHeights = new int[list.getModel().getSize()];
1337 }
1338
1339 /* If either of JList fixedCellWidth and fixedCellHeight haven't
1340 * been set, then initialize cellWidth and cellHeights by
1341 * scanning through the entire model. Note: if the renderer is
1342 * null, we just set cellWidth and cellHeights[*] to zero,
1343 * if they're not set already.
1344 */
1345
1346 if ((fixedCellWidth == -1) || (fixedCellHeight == -1)) {
1347
1348 ListModel dataModel = list.getModel();
1349 int dataModelSize = dataModel.getSize();
1350 ListCellRenderer renderer = list.getCellRenderer();
1351
1352 if (renderer != null) {
1353 for(int index = 0; index < dataModelSize; index++) {
1354 Object value = dataModel.getElementAt(index);
1355 Component c = renderer.getListCellRendererComponent(list, value, index, false, false);
1356 rendererPane.add(c);
1357 Dimension cellSize = c.getPreferredSize();
1358 if (fixedCellWidth == -1) {
1359 cellWidth = Math.max(cellSize.width, cellWidth);
1360 }
1361 if (fixedCellHeight == -1) {
1362 cellHeights[index] = cellSize.height;
1363 }
1364 }
1365 }
1366 else {
1367 if (cellWidth == -1) {
1368 cellWidth = 0;
1369 }
1370 if (cellHeights == null) {
1371 cellHeights = new int[dataModelSize];
1372 }
1373 for(int index = 0; index < dataModelSize; index++) {
1374 cellHeights[index] = 0;
1375 }
1376 }
1377 }
1378
1379 columnCount = 1;
1380 if (layoutOrientation != JList.VERTICAL) {
1381 updateHorizontalLayoutState(fixedCellWidth, fixedCellHeight);
1382 }
1383 }
1384
1385 /**
1386 * Invoked when the list is layed out horizontally to determine how
1387 * many columns to create.
1388 * <p>
1389 * This updates the <code>rowsPerColumn, </code><code>columnCount</code>,
1390 * <code>preferredHeight</code> and potentially <code>cellHeight</code>
1391 * instance variables.
1392 */
1393 private void updateHorizontalLayoutState(int fixedCellWidth,
1394 int fixedCellHeight) {
1395 int visRows = list.getVisibleRowCount();
1396 int dataModelSize = list.getModel().getSize();
1397 Insets insets = list.getInsets();
1398
1399 listHeight = list.getHeight();
1400 listWidth = list.getWidth();
1401
1402 if (dataModelSize == 0) {
1403 rowsPerColumn = columnCount = 0;
1404 preferredHeight = insets.top + insets.bottom;
1405 return;
1406 }
1407
1408 int height;
1409
1410 if (fixedCellHeight != -1) {
1411 height = fixedCellHeight;
1412 }
1413 else {
1414 // Determine the max of the renderer heights.
1415 int maxHeight = 0;
1416 if (cellHeights.length > 0) {
1417 maxHeight = cellHeights[cellHeights.length - 1];
1418 for (int counter = cellHeights.length - 2;
1419 counter >= 0; counter--) {
1420 maxHeight = Math.max(maxHeight, cellHeights[counter]);
1421 }
1422 }
1423 height = cellHeight = maxHeight;
1424 cellHeights = null;
1425 }
1426 // The number of rows is either determined by the visible row
1427 // count, or by the height of the list.
1428 rowsPerColumn = dataModelSize;
1429 if (visRows > 0) {
1430 rowsPerColumn = visRows;
1431 columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1432 if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1433 dataModelSize % rowsPerColumn != 0) {
1434 columnCount++;
1435 }
1436 if (layoutOrientation == JList.HORIZONTAL_WRAP) {
1437 // Because HORIZONTAL_WRAP flows differently, the
1438 // rowsPerColumn needs to be adjusted.
1439 rowsPerColumn = (dataModelSize / columnCount);
1440 if (dataModelSize % columnCount > 0) {
1441 rowsPerColumn++;
1442 }
1443 }
1444 }
1445 else if (layoutOrientation == JList.VERTICAL_WRAP && height != 0) {
1446 rowsPerColumn = Math.max(1, (listHeight - insets.top -
1447 insets.bottom) / height);
1448 columnCount = Math.max(1, dataModelSize / rowsPerColumn);
1449 if (dataModelSize > 0 && dataModelSize > rowsPerColumn &&
1450 dataModelSize % rowsPerColumn != 0) {
1451 columnCount++;
1452 }
1453 }
1454 else if (layoutOrientation == JList.HORIZONTAL_WRAP && cellWidth > 0 &&
1455 listWidth > 0) {
1456 columnCount = Math.max(1, (listWidth - insets.left -
1457 insets.right) / cellWidth);
1458 rowsPerColumn = dataModelSize / columnCount;
1459 if (dataModelSize % columnCount > 0) {
1460 rowsPerColumn++;
1461 }
1462 }
1463 preferredHeight = rowsPerColumn * cellHeight + insets.top +
1464 insets.bottom;
1465 }
1466
1467 private Handler getHandler() {
1468 if (handler == null) {
1469 handler = new Handler();
1470 }
1471 return handler;
1472 }
1473
1474 /**
1475 * Mouse input, and focus handling for JList. An instance of this
1476 * class is added to the appropriate java.awt.Component lists
1477 * at installUI() time. Note keyboard input is handled with JComponent
1478 * KeyboardActions, see installKeyboardActions().
1479 * <p>
1480 * <strong>Warning:</strong>
1481 * Serialized objects of this class will not be compatible with
1482 * future Swing releases. The current serialization support is
1483 * appropriate for short term storage or RMI between applications running
1484 * the same version of Swing. As of 1.4, support for long term storage
1485 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1486 * has been added to the <code>java.beans</code> package.
1487 * Please see {@link java.beans.XMLEncoder}.
1488 *
1489 * @see #createMouseInputListener
1490 * @see #installKeyboardActions
1491 * @see #installUI
1492 */
1493 public class MouseInputHandler implements MouseInputListener
1494 {
1495 public void mouseClicked(MouseEvent e) {
1496 getHandler().mouseClicked(e);
1497 }
1498
1499 public void mouseEntered(MouseEvent e) {
1500 getHandler().mouseEntered(e);
1501 }
1502
1503 public void mouseExited(MouseEvent e) {
1504 getHandler().mouseExited(e);
1505 }
1506
1507 public void mousePressed(MouseEvent e) {
1508 getHandler().mousePressed(e);
1509 }
1510
1511 public void mouseDragged(MouseEvent e) {
1512 getHandler().mouseDragged(e);
1513 }
1514
1515 public void mouseMoved(MouseEvent e) {
1516 getHandler().mouseMoved(e);
1517 }
1518
1519 public void mouseReleased(MouseEvent e) {
1520 getHandler().mouseReleased(e);
1521 }
1522 }
1523
1524
1525 /**
1526 * Creates a delegate that implements MouseInputListener.
1527 * The delegate is added to the corresponding java.awt.Component listener
1528 * lists at installUI() time. Subclasses can override this method to return
1529 * a custom MouseInputListener, e.g.
1530 * <pre>
1531 * class MyListUI extends BasicListUI {
1532 * protected MouseInputListener <b>createMouseInputListener</b>() {
1533 * return new MyMouseInputHandler();
1534 * }
1535 * public class MyMouseInputHandler extends MouseInputHandler {
1536 * public void mouseMoved(MouseEvent e) {
1537 * // do some extra work when the mouse moves
1538 * super.mouseMoved(e);
1539 * }
1540 * }
1541 * }
1542 * </pre>
1543 *
1544 * @see MouseInputHandler
1545 * @see #installUI
1546 */
1547 protected MouseInputListener createMouseInputListener() {
1548 return getHandler();
1549 }
1550
1551 /**
1552 * This inner class is marked &quot;public&quot; due to a compiler bug.
1553 * This class should be treated as a &quot;protected&quot; inner class.
1554 * Instantiate it only within subclasses of BasicTableUI.
1555 */
1556 public class FocusHandler implements FocusListener
1557 {
1558 protected void repaintCellFocus()
1559 {
1560 getHandler().repaintCellFocus();
1561 }
1562
1563 /* The focusGained() focusLost() methods run when the JList
1564 * focus changes.
1565 */
1566
1567 public void focusGained(FocusEvent e) {
1568 getHandler().focusGained(e);
1569 }
1570
1571 public void focusLost(FocusEvent e) {
1572 getHandler().focusLost(e);
1573 }
1574 }
1575
1576 protected FocusListener createFocusListener() {
1577 return getHandler();
1578 }
1579
1580 /**
1581 * The ListSelectionListener that's added to the JLists selection
1582 * model at installUI time, and whenever the JList.selectionModel property
1583 * changes. When the selection changes we repaint the affected rows.
1584 * <p>
1585 * <strong>Warning:</strong>
1586 * Serialized objects of this class will not be compatible with
1587 * future Swing releases. The current serialization support is
1588 * appropriate for short term storage or RMI between applications running
1589 * the same version of Swing. As of 1.4, support for long term storage
1590 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1591 * has been added to the <code>java.beans</code> package.
1592 * Please see {@link java.beans.XMLEncoder}.
1593 *
1594 * @see #createListSelectionListener
1595 * @see #getCellBounds
1596 * @see #installUI
1597 */
1598 public class ListSelectionHandler implements ListSelectionListener
1599 {
1600 public void valueChanged(ListSelectionEvent e)
1601 {
1602 getHandler().valueChanged(e);
1603 }
1604 }
1605
1606
1607 /**
1608 * Creates an instance of ListSelectionHandler that's added to
1609 * the JLists by selectionModel as needed. Subclasses can override
1610 * this method to return a custom ListSelectionListener, e.g.
1611 * <pre>
1612 * class MyListUI extends BasicListUI {
1613 * protected ListSelectionListener <b>createListSelectionListener</b>() {
1614 * return new MySelectionListener();
1615 * }
1616 * public class MySelectionListener extends ListSelectionHandler {
1617 * public void valueChanged(ListSelectionEvent e) {
1618 * // do some extra work when the selection changes
1619 * super.valueChange(e);
1620 * }
1621 * }
1622 * }
1623 * </pre>
1624 *
1625 * @see ListSelectionHandler
1626 * @see #installUI
1627 */
1628 protected ListSelectionListener createListSelectionListener() {
1629 return getHandler();
1630 }
1631
1632
1633 private void redrawList() {
1634 list.revalidate();
1635 list.repaint();
1636 }
1637
1638
1639 /**
1640 * The ListDataListener that's added to the JLists model at
1641 * installUI time, and whenever the JList.model property changes.
1642 * <p>
1643 * <strong>Warning:</strong>
1644 * Serialized objects of this class will not be compatible with
1645 * future Swing releases. The current serialization support is
1646 * appropriate for short term storage or RMI between applications running
1647 * the same version of Swing. As of 1.4, support for long term storage
1648 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1649 * has been added to the <code>java.beans</code> package.
1650 * Please see {@link java.beans.XMLEncoder}.
1651 *
1652 * @see JList#getModel
1653 * @see #maybeUpdateLayoutState
1654 * @see #createListDataListener
1655 * @see #installUI
1656 */
1657 public class ListDataHandler implements ListDataListener
1658 {
1659 public void intervalAdded(ListDataEvent e) {
1660 getHandler().intervalAdded(e);
1661 }
1662
1663
1664 public void intervalRemoved(ListDataEvent e)
1665 {
1666 getHandler().intervalRemoved(e);
1667 }
1668
1669
1670 public void contentsChanged(ListDataEvent e) {
1671 getHandler().contentsChanged(e);
1672 }
1673 }
1674
1675
1676 /**
1677 * Creates an instance of ListDataListener that's added to
1678 * the JLists by model as needed. Subclasses can override
1679 * this method to return a custom ListDataListener, e.g.
1680 * <pre>
1681 * class MyListUI extends BasicListUI {
1682 * protected ListDataListener <b>createListDataListener</b>() {
1683 * return new MyListDataListener();
1684 * }
1685 * public class MyListDataListener extends ListDataHandler {
1686 * public void contentsChanged(ListDataEvent e) {
1687 * // do some extra work when the models contents change
1688 * super.contentsChange(e);
1689 * }
1690 * }
1691 * }
1692 * </pre>
1693 *
1694 * @see ListDataListener
1695 * @see JList#getModel
1696 * @see #installUI
1697 */
1698 protected ListDataListener createListDataListener() {
1699 return getHandler();
1700 }
1701
1702
1703 /**
1704 * The PropertyChangeListener that's added to the JList at
1705 * installUI time. When the value of a JList property that
1706 * affects layout changes, we set a bit in updateLayoutStateNeeded.
1707 * If the JLists model changes we additionally remove our listeners
1708 * from the old model. Likewise for the JList selectionModel.
1709 * <p>
1710 * <strong>Warning:</strong>
1711 * Serialized objects of this class will not be compatible with
1712 * future Swing releases. The current serialization support is
1713 * appropriate for short term storage or RMI between applications running
1714 * the same version of Swing. As of 1.4, support for long term storage
1715 * of all JavaBeans<sup><font size="-2">TM</font></sup>
1716 * has been added to the <code>java.beans</code> package.
1717 * Please see {@link java.beans.XMLEncoder}.
1718 *
1719 * @see #maybeUpdateLayoutState
1720 * @see #createPropertyChangeListener
1721 * @see #installUI
1722 */
1723 public class PropertyChangeHandler implements PropertyChangeListener
1724 {
1725 public void propertyChange(PropertyChangeEvent e)
1726 {
1727 getHandler().propertyChange(e);
1728 }
1729 }
1730
1731
1732 /**
1733 * Creates an instance of PropertyChangeHandler that's added to
1734 * the JList by installUI(). Subclasses can override this method
1735 * to return a custom PropertyChangeListener, e.g.
1736 * <pre>
1737 * class MyListUI extends BasicListUI {
1738 * protected PropertyChangeListener <b>createPropertyChangeListener</b>() {
1739 * return new MyPropertyChangeListener();
1740 * }
1741 * public class MyPropertyChangeListener extends PropertyChangeHandler {
1742 * public void propertyChange(PropertyChangeEvent e) {
1743 * if (e.getPropertyName().equals("model")) {
1744 * // do some extra work when the model changes
1745 * }
1746 * super.propertyChange(e);
1747 * }
1748 * }
1749 * }
1750 * </pre>
1751 *
1752 * @see PropertyChangeListener
1753 * @see #installUI
1754 */
1755 protected PropertyChangeListener createPropertyChangeListener() {
1756 return getHandler();
1757 }
1758
1759 /** Used by IncrementLeadSelectionAction. Indicates the action should
1760 * change the lead, and not select it. */
1761 private static final int CHANGE_LEAD = 0;
1762 /** Used by IncrementLeadSelectionAction. Indicates the action should
1763 * change the selection and lead. */
1764 private static final int CHANGE_SELECTION = 1;
1765 /** Used by IncrementLeadSelectionAction. Indicates the action should
1766 * extend the selection from the anchor to the next index. */
1767 private static final int EXTEND_SELECTION = 2;
1768
1769
1770 private static class Actions extends UIAction {
1771 private static final String SELECT_PREVIOUS_COLUMN =
1772 "selectPreviousColumn";
1773 private static final String SELECT_PREVIOUS_COLUMN_EXTEND =
1774 "selectPreviousColumnExtendSelection";
1775 private static final String SELECT_PREVIOUS_COLUMN_CHANGE_LEAD =
1776 "selectPreviousColumnChangeLead";
1777 private static final String SELECT_NEXT_COLUMN = "selectNextColumn";
1778 private static final String SELECT_NEXT_COLUMN_EXTEND =
1779 "selectNextColumnExtendSelection";
1780 private static final String SELECT_NEXT_COLUMN_CHANGE_LEAD =
1781 "selectNextColumnChangeLead";
1782 private static final String SELECT_PREVIOUS_ROW = "selectPreviousRow";
1783 private static final String SELECT_PREVIOUS_ROW_EXTEND =
1784 "selectPreviousRowExtendSelection";
1785 private static final String SELECT_PREVIOUS_ROW_CHANGE_LEAD =
1786 "selectPreviousRowChangeLead";
1787 private static final String SELECT_NEXT_ROW = "selectNextRow";
1788 private static final String SELECT_NEXT_ROW_EXTEND =
1789 "selectNextRowExtendSelection";
1790 private static final String SELECT_NEXT_ROW_CHANGE_LEAD =
1791 "selectNextRowChangeLead";
1792 private static final String SELECT_FIRST_ROW = "selectFirstRow";
1793 private static final String SELECT_FIRST_ROW_EXTEND =
1794 "selectFirstRowExtendSelection";
1795 private static final String SELECT_FIRST_ROW_CHANGE_LEAD =
1796 "selectFirstRowChangeLead";
1797 private static final String SELECT_LAST_ROW = "selectLastRow";
1798 private static final String SELECT_LAST_ROW_EXTEND =
1799 "selectLastRowExtendSelection";
1800 private static final String SELECT_LAST_ROW_CHANGE_LEAD =
1801 "selectLastRowChangeLead";
1802 private static final String SCROLL_UP = "scrollUp";
1803 private static final String SCROLL_UP_EXTEND =
1804 "scrollUpExtendSelection";
1805 private static final String SCROLL_UP_CHANGE_LEAD =
1806 "scrollUpChangeLead";
1807 private static final String SCROLL_DOWN = "scrollDown";
1808 private static final String SCROLL_DOWN_EXTEND =
1809 "scrollDownExtendSelection";
1810 private static final String SCROLL_DOWN_CHANGE_LEAD =
1811 "scrollDownChangeLead";
1812 private static final String SELECT_ALL = "selectAll";
1813 private static final String CLEAR_SELECTION = "clearSelection";
1814
1815 // add the lead item to the selection without changing lead or anchor
1816 private static final String ADD_TO_SELECTION = "addToSelection";
1817
1818 // toggle the selected state of the lead item and move the anchor to it
1819 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
1820
1821 // extend the selection to the lead item
1822 private static final String EXTEND_TO = "extendTo";
1823
1824 // move the anchor to the lead and ensure only that item is selected
1825 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
1826
1827 Actions(String name) {
1828 super(name);
1829 }
1830 public void actionPerformed(ActionEvent e) {
1831 String name = getName();
1832 JList list = (JList)e.getSource();
1833 BasicListUI ui = (BasicListUI)BasicLookAndFeel.getUIOfType(
1834 list.getUI(), BasicListUI.class);
1835
1836 if (name == SELECT_PREVIOUS_COLUMN) {
1837 changeSelection(list, CHANGE_SELECTION,
1838 getNextColumnIndex(list, ui, -1), -1);
1839 }
1840 else if (name == SELECT_PREVIOUS_COLUMN_EXTEND) {
1841 changeSelection(list, EXTEND_SELECTION,
1842 getNextColumnIndex(list, ui, -1), -1);
1843 }
1844 else if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD) {
1845 changeSelection(list, CHANGE_LEAD,
1846 getNextColumnIndex(list, ui, -1), -1);
1847 }
1848 else if (name == SELECT_NEXT_COLUMN) {
1849 changeSelection(list, CHANGE_SELECTION,
1850 getNextColumnIndex(list, ui, 1), 1);
1851 }
1852 else if (name == SELECT_NEXT_COLUMN_EXTEND) {
1853 changeSelection(list, EXTEND_SELECTION,
1854 getNextColumnIndex(list, ui, 1), 1);
1855 }
1856 else if (name == SELECT_NEXT_COLUMN_CHANGE_LEAD) {
1857 changeSelection(list, CHANGE_LEAD,
1858 getNextColumnIndex(list, ui, 1), 1);
1859 }
1860 else if (name == SELECT_PREVIOUS_ROW) {
1861 changeSelection(list, CHANGE_SELECTION,
1862 getNextIndex(list, ui, -1), -1);
1863 }
1864 else if (name == SELECT_PREVIOUS_ROW_EXTEND) {
1865 changeSelection(list, EXTEND_SELECTION,
1866 getNextIndex(list, ui, -1), -1);
1867 }
1868 else if (name == SELECT_PREVIOUS_ROW_CHANGE_LEAD) {
1869 changeSelection(list, CHANGE_LEAD,
1870 getNextIndex(list, ui, -1), -1);
1871 }
1872 else if (name == SELECT_NEXT_ROW) {
1873 changeSelection(list, CHANGE_SELECTION,
1874 getNextIndex(list, ui, 1), 1);
1875 }
1876 else if (name == SELECT_NEXT_ROW_EXTEND) {
1877 changeSelection(list, EXTEND_SELECTION,
1878 getNextIndex(list, ui, 1), 1);
1879 }
1880 else if (name == SELECT_NEXT_ROW_CHANGE_LEAD) {
1881 changeSelection(list, CHANGE_LEAD,
1882 getNextIndex(list, ui, 1), 1);
1883 }
1884 else if (name == SELECT_FIRST_ROW) {
1885 changeSelection(list, CHANGE_SELECTION, 0, -1);
1886 }
1887 else if (name == SELECT_FIRST_ROW_EXTEND) {
1888 changeSelection(list, EXTEND_SELECTION, 0, -1);
1889 }
1890 else if (name == SELECT_FIRST_ROW_CHANGE_LEAD) {
1891 changeSelection(list, CHANGE_LEAD, 0, -1);
1892 }
1893 else if (name == SELECT_LAST_ROW) {
1894 changeSelection(list, CHANGE_SELECTION,
1895 list.getModel().getSize() - 1, 1);
1896 }
1897 else if (name == SELECT_LAST_ROW_EXTEND) {
1898 changeSelection(list, EXTEND_SELECTION,
1899 list.getModel().getSize() - 1, 1);
1900 }
1901 else if (name == SELECT_LAST_ROW_CHANGE_LEAD) {
1902 changeSelection(list, CHANGE_LEAD,
1903 list.getModel().getSize() - 1, 1);
1904 }
1905 else if (name == SCROLL_UP) {
1906 changeSelection(list, CHANGE_SELECTION,
1907 getNextPageIndex(list, -1), -1);
1908 }
1909 else if (name == SCROLL_UP_EXTEND) {
1910 changeSelection(list, EXTEND_SELECTION,
1911 getNextPageIndex(list, -1), -1);
1912 }
1913 else if (name == SCROLL_UP_CHANGE_LEAD) {
1914 changeSelection(list, CHANGE_LEAD,
1915 getNextPageIndex(list, -1), -1);
1916 }
1917 else if (name == SCROLL_DOWN) {
1918 changeSelection(list, CHANGE_SELECTION,
1919 getNextPageIndex(list, 1), 1);
1920 }
1921 else if (name == SCROLL_DOWN_EXTEND) {
1922 changeSelection(list, EXTEND_SELECTION,
1923 getNextPageIndex(list, 1), 1);
1924 }
1925 else if (name == SCROLL_DOWN_CHANGE_LEAD) {
1926 changeSelection(list, CHANGE_LEAD,
1927 getNextPageIndex(list, 1), 1);
1928 }
1929 else if (name == SELECT_ALL) {
1930 selectAll(list);
1931 }
1932 else if (name == CLEAR_SELECTION) {
1933 clearSelection(list);
1934 }
1935 else if (name == ADD_TO_SELECTION) {
1936 int index = adjustIndex(
1937 list.getSelectionModel().getLeadSelectionIndex(), list);
1938
1939 if (!list.isSelectedIndex(index)) {
1940 int oldAnchor = list.getSelectionModel().getAnchorSelectionIndex();
1941 list.setValueIsAdjusting(true);
1942 list.addSelectionInterval(index, index);
1943 list.getSelectionModel().setAnchorSelectionIndex(oldAnchor);
1944 list.setValueIsAdjusting(false);
1945 }
1946 }
1947 else if (name == TOGGLE_AND_ANCHOR) {
1948 int index = adjustIndex(
1949 list.getSelectionModel().getLeadSelectionIndex(), list);
1950
1951 if (list.isSelectedIndex(index)) {
1952 list.removeSelectionInterval(index, index);
1953 } else {
1954 list.addSelectionInterval(index, index);
1955 }
1956 }
1957 else if (name == EXTEND_TO) {
1958 changeSelection(
1959 list, EXTEND_SELECTION,
1960 adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
1961 0);
1962 }
1963 else if (name == MOVE_SELECTION_TO) {
1964 changeSelection(
1965 list, CHANGE_SELECTION,
1966 adjustIndex(list.getSelectionModel().getLeadSelectionIndex(), list),
1967 0);
1968 }
1969 }
1970
1971 public boolean isEnabled(Object c) {
1972 Object name = getName();
1973 if (name == SELECT_PREVIOUS_COLUMN_CHANGE_LEAD ||
1974 name == SELECT_NEXT_COLUMN_CHANGE_LEAD ||
1975 name == SELECT_PREVIOUS_ROW_CHANGE_LEAD ||
1976 name == SELECT_NEXT_ROW_CHANGE_LEAD ||
1977 name == SELECT_FIRST_ROW_CHANGE_LEAD ||
1978 name == SELECT_LAST_ROW_CHANGE_LEAD ||
1979 name == SCROLL_UP_CHANGE_LEAD ||
1980 name == SCROLL_DOWN_CHANGE_LEAD) {
1981
1982 // discontinuous selection actions are only enabled for
1983 // DefaultListSelectionModel
1984 return c != null && ((JList)c).getSelectionModel()
1985 instanceof DefaultListSelectionModel;
1986 }
1987
1988 return true;
1989 }
1990
1991 private void clearSelection(JList list) {
1992 list.clearSelection();
1993 }
1994
1995 private void selectAll(JList list) {
1996 int size = list.getModel().getSize();
1997 if (size > 0) {
1998 ListSelectionModel lsm = list.getSelectionModel();
1999 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2000
2001 if (lsm.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION) {
2002 if (lead == -1) {
2003 int min = adjustIndex(list.getMinSelectionIndex(), list);
2004 lead = (min == -1 ? 0 : min);
2005 }
2006
2007 list.setSelectionInterval(lead, lead);
2008 list.ensureIndexIsVisible(lead);
2009 } else {
2010 list.setValueIsAdjusting(true);
2011
2012 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2013
2014 list.setSelectionInterval(0, size - 1);
2015
2016 // this is done to restore the anchor and lead
2017 SwingUtilities2.setLeadAnchorWithoutSelection(lsm, anchor, lead);
2018
2019 list.setValueIsAdjusting(false);
2020 }
2021 }
2022 }
2023
2024 private int getNextPageIndex(JList list, int direction) {
2025 if (list.getModel().getSize() == 0) {
2026 return -1;
2027 }
2028
2029 int index = -1;
2030 Rectangle visRect = list.getVisibleRect();
2031 ListSelectionModel lsm = list.getSelectionModel();
2032 int lead = adjustIndex(lsm.getLeadSelectionIndex(), list);
2033 Rectangle leadRect =
2034 (lead==-1) ? new Rectangle() : list.getCellBounds(lead, lead);
2035
2036 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2037 list.getVisibleRowCount() <= 0) {
2038 if (!list.getComponentOrientation().isLeftToRight()) {
2039 direction = -direction;
2040 }
2041 // apply for horizontal scrolling: the step for next
2042 // page index is number of visible columns
2043 if (direction < 0) {
2044 // left
2045 visRect.x = leadRect.x + leadRect.width - visRect.width;
2046 Point p = new Point(visRect.x - 1, leadRect.y);
2047 index = list.locationToIndex(p);
2048 Rectangle cellBounds = list.getCellBounds(index, index);
2049 if (visRect.intersects(cellBounds)) {
2050 p.x = cellBounds.x - 1;
2051 index = list.locationToIndex(p);
2052 cellBounds = list.getCellBounds(index, index);
2053 }
2054 // this is necessary for right-to-left orientation only
2055 if (cellBounds.y != leadRect.y) {
2056 p.x = cellBounds.x + cellBounds.width;
2057 index = list.locationToIndex(p);
2058 }
2059 }
2060 else {
2061 // right
2062 visRect.x = leadRect.x;
2063 Point p = new Point(visRect.x + visRect.width, leadRect.y);
2064 index = list.locationToIndex(p);
2065 Rectangle cellBounds = list.getCellBounds(index, index);
2066 if (visRect.intersects(cellBounds)) {
2067 p.x = cellBounds.x + cellBounds.width;
2068 index = list.locationToIndex(p);
2069 cellBounds = list.getCellBounds(index, index);
2070 }
2071 if (cellBounds.y != leadRect.y) {
2072 p.x = cellBounds.x - 1;
2073 index = list.locationToIndex(p);
2074 }
2075 }
2076 }
2077 else {
2078 if (direction < 0) {
2079 // up
2080 // go to the first visible cell
2081 Point p = new Point(leadRect.x, visRect.y);
2082 index = list.locationToIndex(p);
2083 if (lead <= index) {
2084 // if lead is the first visible cell (or above it)
2085 // adjust the visible rect up
2086 visRect.y = leadRect.y + leadRect.height - visRect.height;
2087 p.y = visRect.y;
2088 index = list.locationToIndex(p);
2089 Rectangle cellBounds = list.getCellBounds(index, index);
2090 // go one cell down if first visible cell doesn't fit
2091 // into adjasted visible rectangle
2092 if (cellBounds.y < visRect.y) {
2093 p.y = cellBounds.y + cellBounds.height;
2094 index = list.locationToIndex(p);
2095 cellBounds = list.getCellBounds(index, index);
2096 }
2097 // if index isn't less then lead
2098 // try to go to cell previous to lead
2099 if (cellBounds.y >= leadRect.y) {
2100 p.y = leadRect.y - 1;
2101 index = list.locationToIndex(p);
2102 }
2103 }
2104 }
2105 else {
2106 // down
2107 // go to the last completely visible cell
2108 Point p = new Point(leadRect.x,
2109 visRect.y + visRect.height - 1);
2110 index = list.locationToIndex(p);
2111 Rectangle cellBounds = list.getCellBounds(index, index);
2112 // go up one cell if last visible cell doesn't fit
2113 // into visible rectangle
2114 if (cellBounds.y + cellBounds.height >
2115 visRect.y + visRect.height) {
2116 p.y = cellBounds.y - 1;
2117 index = list.locationToIndex(p);
2118 cellBounds = list.getCellBounds(index, index);
2119 index = Math.max(index, lead);
2120 }
2121
2122 if (lead >= index) {
2123 // if lead is the last completely visible index
2124 // (or below it) adjust the visible rect down
2125 visRect.y = leadRect.y;
2126 p.y = visRect.y + visRect.height - 1;
2127 index = list.locationToIndex(p);
2128 cellBounds = list.getCellBounds(index, index);
2129 // go one cell up if last visible cell doesn't fit
2130 // into adjasted visible rectangle
2131 if (cellBounds.y + cellBounds.height >
2132 visRect.y + visRect.height) {
2133 p.y = cellBounds.y - 1;
2134 index = list.locationToIndex(p);
2135 cellBounds = list.getCellBounds(index, index);
2136 }
2137 // if index isn't greater then lead
2138 // try to go to cell next after lead
2139 if (cellBounds.y <= leadRect.y) {
2140 p.y = leadRect.y + leadRect.height;
2141 index = list.locationToIndex(p);
2142 }
2143 }
2144 }
2145 }
2146 return index;
2147 }
2148
2149 private void changeSelection(JList list, int type,
2150 int index, int direction) {
2151 if (index >= 0 && index < list.getModel().getSize()) {
2152 ListSelectionModel lsm = list.getSelectionModel();
2153
2154 // CHANGE_LEAD is only valid with multiple interval selection
2155 if (type == CHANGE_LEAD &&
2156 list.getSelectionMode()
2157 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) {
2158
2159 type = CHANGE_SELECTION;
2160 }
2161
2162 // IMPORTANT - This needs to happen before the index is changed.
2163 // This is because JFileChooser, which uses JList, also scrolls
2164 // the selected item into view. If that happens first, then
2165 // this method becomes a no-op.
2166 adjustScrollPositionIfNecessary(list, index, direction);
2167
2168 if (type == EXTEND_SELECTION) {
2169 int anchor = adjustIndex(lsm.getAnchorSelectionIndex(), list);
2170 if (anchor == -1) {
2171 anchor = 0;
2172 }
2173
2174 list.setSelectionInterval(anchor, index);
2175 }
2176 else if (type == CHANGE_SELECTION) {
2177 list.setSelectedIndex(index);
2178 }
2179 else {
2180 // casting should be safe since the action is only enabled
2181 // for DefaultListSelectionModel
2182 ((DefaultListSelectionModel)lsm).moveLeadSelectionIndex(index);
2183 }
2184 }
2185 }
2186
2187 /**
2188 * When scroll down makes selected index the last completely visible
2189 * index. When scroll up makes selected index the first visible index.
2190 * Adjust visible rectangle respect to list's component orientation.
2191 */
2192 private void adjustScrollPositionIfNecessary(JList list, int index,
2193 int direction) {
2194 if (direction == 0) {
2195 return;
2196 }
2197 Rectangle cellBounds = list.getCellBounds(index, index);
2198 Rectangle visRect = list.getVisibleRect();
2199 if (cellBounds != null && !visRect.contains(cellBounds)) {
2200 if (list.getLayoutOrientation() == JList.VERTICAL_WRAP &&
2201 list.getVisibleRowCount() <= 0) {
2202 // horizontal
2203 if (list.getComponentOrientation().isLeftToRight()) {
2204 if (direction > 0) {
2205 // right for left-to-right
2206 int x =Math.max(0,
2207 cellBounds.x + cellBounds.width - visRect.width);
2208 int startIndex =
2209 list.locationToIndex(new Point(x, cellBounds.y));
2210 Rectangle startRect = list.getCellBounds(startIndex,
2211 startIndex);
2212 if (startRect.x < x && startRect.x < cellBounds.x) {
2213 startRect.x += startRect.width;
2214 startIndex =
2215 list.locationToIndex(startRect.getLocation());
2216 startRect = list.getCellBounds(startIndex,
2217 startIndex);
2218 }
2219 cellBounds = startRect;
2220 }
2221 cellBounds.width = visRect.width;
2222 }
2223 else {
2224 if (direction > 0) {
2225 // left for right-to-left
2226 int x = cellBounds.x + visRect.width;
2227 int rightIndex =
2228 list.locationToIndex(new Point(x, cellBounds.y));
2229 Rectangle rightRect = list.getCellBounds(rightIndex,
2230 rightIndex);
2231 if (rightRect.x + rightRect.width > x &&
2232 rightRect.x > cellBounds.x) {
2233 rightRect.width = 0;
2234 }
2235 cellBounds.x = Math.max(0,
2236 rightRect.x + rightRect.width - visRect.width);
2237 cellBounds.width = visRect.width;
2238 }
2239 else {
2240 cellBounds.x += Math.max(0,
2241 cellBounds.width - visRect.width);
2242 // adjust width to fit into visible rectangle
2243 cellBounds.width = Math.min(cellBounds.width,
2244 visRect.width);
2245 }
2246 }
2247 }
2248 else {
2249 // vertical
2250 if (direction > 0 &&
2251 (cellBounds.y < visRect.y ||
2252 cellBounds.y + cellBounds.height
2253 > visRect.y + visRect.height)) {
2254 //down
2255 int y = Math.max(0,
2256 cellBounds.y + cellBounds.height - visRect.height);
2257 int startIndex =
2258 list.locationToIndex(new Point(cellBounds.x, y));
2259 Rectangle startRect = list.getCellBounds(startIndex,
2260 startIndex);
2261 if (startRect.y < y && startRect.y < cellBounds.y) {
2262 startRect.y += startRect.height;
2263 startIndex =
2264 list.locationToIndex(startRect.getLocation());
2265 startRect =
2266 list.getCellBounds(startIndex, startIndex);
2267 }
2268 cellBounds = startRect;
2269 cellBounds.height = visRect.height;
2270 }
2271 else {
2272 // adjust height to fit into visible rectangle
2273 cellBounds.height = Math.min(cellBounds.height, visRect.height);
2274 }
2275 }
2276 list.scrollRectToVisible(cellBounds);
2277 }
2278 }
2279
2280 private int getNextColumnIndex(JList list, BasicListUI ui,
2281 int amount) {
2282 if (list.getLayoutOrientation() != JList.VERTICAL) {
2283 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2284 int size = list.getModel().getSize();
2285
2286 if (index == -1) {
2287 return 0;
2288 } else if (size == 1) {
2289 // there's only one item so we should select it
2290 return 0;
2291 } else if (ui == null || ui.columnCount <= 1) {
2292 return -1;
2293 }
2294
2295 int column = ui.convertModelToColumn(index);
2296 int row = ui.convertModelToRow(index);
2297
2298 column += amount;
2299 if (column >= ui.columnCount || column < 0) {
2300 // No wrapping.
2301 return -1;
2302 }
2303 int maxRowCount = ui.getRowCount(column);
2304 if (row >= maxRowCount) {
2305 return -1;
2306 }
2307 return ui.getModelIndex(column, row);
2308 }
2309 // Won't change the selection.
2310 return -1;
2311 }
2312
2313 private int getNextIndex(JList list, BasicListUI ui, int amount) {
2314 int index = adjustIndex(list.getLeadSelectionIndex(), list);
2315 int size = list.getModel().getSize();
2316
2317 if (index == -1) {
2318 if (size > 0) {
2319 if (amount > 0) {
2320 index = 0;
2321 }
2322 else {
2323 index = size - 1;
2324 }
2325 }
2326 } else if (size == 1) {
2327 // there's only one item so we should select it
2328 index = 0;
2329 } else if (list.getLayoutOrientation() == JList.HORIZONTAL_WRAP) {
2330 if (ui != null) {
2331 index += ui.columnCount * amount;
2332 }
2333 } else {
2334 index += amount;
2335 }
2336
2337 return index;
2338 }
2339 }
2340
2341
2342 private class Handler implements FocusListener, KeyListener,
2343 ListDataListener, ListSelectionListener,
2344 MouseInputListener, PropertyChangeListener,
2345 BeforeDrag {
2346 //
2347 // KeyListener
2348 //
2349 private String prefix = "";
2350 private String typedString = "";
2351 private long lastTime = 0L;
2352
2353 /**
2354 * Invoked when a key has been typed.
2355 *
2356 * Moves the keyboard focus to the first element whose prefix matches the
2357 * sequence of alphanumeric keys pressed by the user with delay less
2358 * than value of <code>timeFactor</code> property (or 1000 milliseconds
2359 * if it is not defined). Subsequent same key presses move the keyboard
2360 * focus to the next object that starts with the same letter until another
2361 * key is pressed, then it is treated as the prefix with appropriate number
2362 * of the same letters followed by first typed another letter.
2363 */
2364 public void keyTyped(KeyEvent e) {
2365 JList src = (JList)e.getSource();
2366 ListModel model = src.getModel();
2367
2368 if (model.getSize() == 0 || e.isAltDown() || e.isControlDown() || e.isMetaDown() ||
2369 isNavigationKey(e)) {
2370 // Nothing to select
2371 return;
2372 }
2373 boolean startingFromSelection = true;
2374
2375 char c = e.getKeyChar();
2376
2377 long time = e.getWhen();
2378 int startIndex = adjustIndex(src.getLeadSelectionIndex(), list);
2379 if (time - lastTime < timeFactor) {
2380 typedString += c;
2381 if((prefix.length() == 1) && (c == prefix.charAt(0))) {
2382 // Subsequent same key presses move the keyboard focus to the next
2383 // object that starts with the same letter.
2384 startIndex++;
2385 } else {
2386 prefix = typedString;
2387 }
2388 } else {
2389 startIndex++;
2390 typedString = "" + c;
2391 prefix = typedString;
2392 }
2393 lastTime = time;
2394
2395 if (startIndex < 0 || startIndex >= model.getSize()) {
2396 startingFromSelection = false;
2397 startIndex = 0;
2398 }
2399 int index = src.getNextMatch(prefix, startIndex,
2400 Position.Bias.Forward);
2401 if (index >= 0) {
2402 src.setSelectedIndex(index);
2403 src.ensureIndexIsVisible(index);
2404 } else if (startingFromSelection) { // wrap
2405 index = src.getNextMatch(prefix, 0,
2406 Position.Bias.Forward);
2407 if (index >= 0) {
2408 src.setSelectedIndex(index);
2409 src.ensureIndexIsVisible(index);
2410 }
2411 }
2412 }
2413
2414 /**
2415 * Invoked when a key has been pressed.
2416 *
2417 * Checks to see if the key event is a navigation key to prevent
2418 * dispatching these keys for the first letter navigation.
2419 */
2420 public void keyPressed(KeyEvent e) {
2421 if ( isNavigationKey(e) ) {
2422 prefix = "";
2423 typedString = "";
2424 lastTime = 0L;
2425 }
2426 }
2427
2428 /**
2429 * Invoked when a key has been released.
2430 * See the class description for {@link KeyEvent} for a definition of
2431 * a key released event.
2432 */
2433 public void keyReleased(KeyEvent e) {
2434 }
2435
2436 /**
2437 * Returns whether or not the supplied key event maps to a key that is used for
2438 * navigation. This is used for optimizing key input by only passing non-
2439 * navigation keys to the first letter navigation mechanism.
2440 */
2441 private boolean isNavigationKey(KeyEvent event) {
2442 InputMap inputMap = list.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
2443 KeyStroke key = KeyStroke.getKeyStrokeForEvent(event);
2444
2445 if (inputMap != null && inputMap.get(key) != null) {
2446 return true;
2447 }
2448 return false;
2449 }
2450
2451 //
2452 // PropertyChangeListener
2453 //
2454 public void propertyChange(PropertyChangeEvent e) {
2455 String propertyName = e.getPropertyName();
2456
2457 /* If the JList.model property changes, remove our listener,
2458 * listDataListener from the old model and add it to the new one.
2459 */
2460 if (propertyName == "model") {
2461 ListModel oldModel = (ListModel)e.getOldValue();
2462 ListModel newModel = (ListModel)e.getNewValue();
2463 if (oldModel != null) {
2464 oldModel.removeListDataListener(listDataListener);
2465 }
2466 if (newModel != null) {
2467 newModel.addListDataListener(listDataListener);
2468 }
2469 updateLayoutStateNeeded |= modelChanged;
2470 redrawList();
2471 }
2472
2473 /* If the JList.selectionModel property changes, remove our listener,
2474 * listSelectionListener from the old selectionModel and add it to the new one.
2475 */
2476 else if (propertyName == "selectionModel") {
2477 ListSelectionModel oldModel = (ListSelectionModel)e.getOldValue();
2478 ListSelectionModel newModel = (ListSelectionModel)e.getNewValue();
2479 if (oldModel != null) {
2480 oldModel.removeListSelectionListener(listSelectionListener);
2481 }
2482 if (newModel != null) {
2483 newModel.addListSelectionListener(listSelectionListener);
2484 }
2485 updateLayoutStateNeeded |= modelChanged;
2486 redrawList();
2487 }
2488 else if (propertyName == "cellRenderer") {
2489 updateLayoutStateNeeded |= cellRendererChanged;
2490 redrawList();
2491 }
2492 else if (propertyName == "font") {
2493 updateLayoutStateNeeded |= fontChanged;
2494 redrawList();
2495 }
2496 else if (propertyName == "prototypeCellValue") {
2497 updateLayoutStateNeeded |= prototypeCellValueChanged;
2498 redrawList();
2499 }
2500 else if (propertyName == "fixedCellHeight") {
2501 updateLayoutStateNeeded |= fixedCellHeightChanged;
2502 redrawList();
2503 }
2504 else if (propertyName == "fixedCellWidth") {
2505 updateLayoutStateNeeded |= fixedCellWidthChanged;
2506 redrawList();
2507 }
2508 else if (propertyName == "selectionForeground") {
2509 list.repaint();
2510 }
2511 else if (propertyName == "selectionBackground") {
2512 list.repaint();
2513 }
2514 else if ("layoutOrientation" == propertyName) {
2515 updateLayoutStateNeeded |= layoutOrientationChanged;
2516 layoutOrientation = list.getLayoutOrientation();
2517 redrawList();
2518 }
2519 else if ("visibleRowCount" == propertyName) {
2520 if (layoutOrientation != JList.VERTICAL) {
2521 updateLayoutStateNeeded |= layoutOrientationChanged;
2522 redrawList();
2523 }
2524 }
2525 else if ("componentOrientation" == propertyName) {
2526 isLeftToRight = list.getComponentOrientation().isLeftToRight();
2527 updateLayoutStateNeeded |= componentOrientationChanged;
2528 redrawList();
2529
2530 InputMap inputMap = getInputMap(JComponent.WHEN_FOCUSED);
2531 SwingUtilities.replaceUIInputMap(list, JComponent.WHEN_FOCUSED,
2532 inputMap);
2533 } else if ("List.isFileList" == propertyName) {
2534 updateIsFileList();
2535 redrawList();
2536 } else if ("dropLocation" == propertyName) {
2537 JList.DropLocation oldValue = (JList.DropLocation)e.getOldValue();
2538 repaintDropLocation(oldValue);
2539 repaintDropLocation(list.getDropLocation());
2540 }
2541 }
2542
2543 private void repaintDropLocation(JList.DropLocation loc) {
2544 if (loc == null) {
2545 return;
2546 }
2547
2548 Rectangle r;
2549
2550 if (loc.isInsert()) {
2551 r = getDropLineRect(loc);
2552 } else {
2553 r = getCellBounds(list, loc.getIndex());
2554 }
2555
2556 if (r != null) {
2557 list.repaint(r);
2558 }
2559 }
2560
2561 //
2562 // ListDataListener
2563 //
2564 public void intervalAdded(ListDataEvent e) {
2565 updateLayoutStateNeeded = modelChanged;
2566
2567 int minIndex = Math.min(e.getIndex0(), e.getIndex1());
2568 int maxIndex = Math.max(e.getIndex0(), e.getIndex1());
2569
2570 /* Sync the SelectionModel with the DataModel.
2571 */
2572
2573 ListSelectionModel sm = list.getSelectionModel();
2574 if (sm != null) {
2575 sm.insertIndexInterval(minIndex, maxIndex - minIndex+1, true);
2576 }
2577
2578 /* Repaint the entire list, from the origin of
2579 * the first added cell, to the bottom of the
2580 * component.
2581 */
2582 redrawList();
2583 }
2584
2585
2586 public void intervalRemoved(ListDataEvent e)
2587 {
2588 updateLayoutStateNeeded = modelChanged;
2589
2590 /* Sync the SelectionModel with the DataModel.
2591 */
2592
2593 ListSelectionModel sm = list.getSelectionModel();
2594 if (sm != null) {
2595 sm.removeIndexInterval(e.getIndex0(), e.getIndex1());
2596 }
2597
2598 /* Repaint the entire list, from the origin of
2599 * the first removed cell, to the bottom of the
2600 * component.
2601 */
2602
2603 redrawList();
2604 }
2605
2606
2607 public void contentsChanged(ListDataEvent e) {
2608 updateLayoutStateNeeded = modelChanged;
2609 redrawList();
2610 }
2611
2612
2613 //
2614 // ListSelectionListener
2615 //
2616 public void valueChanged(ListSelectionEvent e) {
2617 maybeUpdateLayoutState();
2618
2619 int size = list.getModel().getSize();
2620 int firstIndex = Math.min(size - 1, Math.max(e.getFirstIndex(), 0));
2621 int lastIndex = Math.min(size - 1, Math.max(e.getLastIndex(), 0));
2622
2623 Rectangle bounds = getCellBounds(list, firstIndex, lastIndex);
2624
2625 if (bounds != null) {
2626 list.repaint(bounds.x, bounds.y, bounds.width, bounds.height);
2627 }
2628 }
2629
2630 //
2631 // MouseListener
2632 //
2633 public void mouseClicked(MouseEvent e) {
2634 }
2635
2636 public void mouseEntered(MouseEvent e) {
2637 }
2638
2639 public void mouseExited(MouseEvent e) {
2640 }
2641
2642 // Whether or not the mouse press (which is being considered as part
2643 // of a drag sequence) also caused the selection change to be fully
2644 // processed.
2645 private boolean dragPressDidSelection;
2646
2647 public void mousePressed(MouseEvent e) {
2648 if (SwingUtilities2.shouldIgnore(e, list)) {
2649 return;
2650 }
2651
2652 boolean dragEnabled = list.getDragEnabled();
2653 boolean grabFocus = true;
2654
2655 // different behavior if drag is enabled
2656 if (dragEnabled) {
2657 int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2658 // if we have a valid row and this is a drag initiating event
2659 if (row != -1 && DragRecognitionSupport.mousePressed(e)) {
2660 dragPressDidSelection = false;
2661
2662 if (e.isControlDown()) {
2663 // do nothing for control - will be handled on release
2664 // or when drag starts
2665 return;
2666 } else if (!e.isShiftDown() && list.isSelectedIndex(row)) {
2667 // clicking on something that's already selected
2668 // and need to make it the lead now
2669 list.addSelectionInterval(row, row);
2670 return;
2671 }
2672
2673 // could be a drag initiating event - don't grab focus
2674 grabFocus = false;
2675
2676 dragPressDidSelection = true;
2677 }
2678 } else {
2679 // When drag is enabled mouse drags won't change the selection
2680 // in the list, so we only set the isAdjusting flag when it's
2681 // not enabled
2682 list.setValueIsAdjusting(true);
2683 }
2684
2685 if (grabFocus) {
2686 SwingUtilities2.adjustFocus(list);
2687 }
2688
2689 adjustSelection(e);
2690 }
2691
2692 private void adjustSelection(MouseEvent e) {
2693 int row = SwingUtilities2.loc2IndexFileList(list, e.getPoint());
2694 if (row < 0) {
2695 // If shift is down in multi-select, we should do nothing.
2696 // For single select or non-shift-click, clear the selection
2697 if (isFileList &&
2698 e.getID() == MouseEvent.MOUSE_PRESSED &&
2699 (!e.isShiftDown() ||
2700 list.getSelectionMode() == ListSelectionModel.SINGLE_SELECTION)) {
2701 list.clearSelection();
2702 }
2703 }
2704 else {
2705 int anchorIndex = adjustIndex(list.getAnchorSelectionIndex(), list);
2706 boolean anchorSelected;
2707 if (anchorIndex == -1) {
2708 anchorIndex = 0;
2709 anchorSelected = false;
2710 } else {
2711 anchorSelected = list.isSelectedIndex(anchorIndex);
2712 }
2713
2714 if (e.isControlDown()) {
2715 if (e.isShiftDown()) {
2716 if (anchorSelected) {
2717 list.addSelectionInterval(anchorIndex, row);
2718 } else {
2719 list.removeSelectionInterval(anchorIndex, row);
2720 if (isFileList) {
2721 list.addSelectionInterval(row, row);
2722 list.getSelectionModel().setAnchorSelectionIndex(anchorIndex);
2723 }
2724 }
2725 } else if (list.isSelectedIndex(row)) {
2726 list.removeSelectionInterval(row, row);
2727 } else {
2728 list.addSelectionInterval(row, row);
2729 }
2730 } else if (e.isShiftDown()) {
2731 list.setSelectionInterval(anchorIndex, row);
2732 } else {
2733 list.setSelectionInterval(row, row);
2734 }
2735 }
2736 }
2737
2738 public void dragStarting(MouseEvent me) {
2739 if (me.isControlDown()) {
2740 int row = SwingUtilities2.loc2IndexFileList(list, me.getPoint());
2741 list.addSelectionInterval(row, row);
2742 }
2743 }
2744
2745 public void mouseDragged(MouseEvent e) {
2746 if (SwingUtilities2.shouldIgnore(e, list)) {
2747 return;
2748 }
2749
2750 if (list.getDragEnabled()) {
2751 DragRecognitionSupport.mouseDragged(e, this);
2752 return;
2753 }
2754
2755 if (e.isShiftDown() || e.isControlDown()) {
2756 return;
2757 }
2758
2759 int row = locationToIndex(list, e.getPoint());
2760 if (row != -1) {
2761 // 4835633. Dragging onto a File should not select it.
2762 if (isFileList) {
2763 return;
2764 }
2765 Rectangle cellBounds = getCellBounds(list, row, row);
2766 if (cellBounds != null) {
2767 list.scrollRectToVisible(cellBounds);
2768 list.setSelectionInterval(row, row);
2769 }
2770 }
2771 }
2772
2773 public void mouseMoved(MouseEvent e) {
2774 }
2775
2776 public void mouseReleased(MouseEvent e) {
2777 if (SwingUtilities2.shouldIgnore(e, list)) {
2778 return;
2779 }
2780
2781 if (list.getDragEnabled()) {
2782 MouseEvent me = DragRecognitionSupport.mouseReleased(e);
2783 if (me != null) {
2784 SwingUtilities2.adjustFocus(list);
2785 if (!dragPressDidSelection) {
2786 adjustSelection(me);
2787 }
2788 }
2789 } else {
2790 list.setValueIsAdjusting(false);
2791 }
2792 }
2793
2794 //
2795 // FocusListener
2796 //
2797 protected void repaintCellFocus()
2798 {
2799 int leadIndex = adjustIndex(list.getLeadSelectionIndex(), list);
2800 if (leadIndex != -1) {
2801 Rectangle r = getCellBounds(list, leadIndex, leadIndex);
2802 if (r != null) {
2803 list.repaint(r.x, r.y, r.width, r.height);
2804 }
2805 }
2806 }
2807
2808 /* The focusGained() focusLost() methods run when the JList
2809 * focus changes.
2810 */
2811
2812 public void focusGained(FocusEvent e) {
2813 repaintCellFocus();
2814 }
2815
2816 public void focusLost(FocusEvent e) {
2817 repaintCellFocus();
2818 }
2819 }
2820
2821 private static int adjustIndex(int index, JList list) {
2822 return index < list.getModel().getSize() ? index : -1;
2823 }
2824
2825 private static final TransferHandler defaultTransferHandler = new ListTransferHandler();
2826
2827 static class ListTransferHandler extends TransferHandler implements UIResource {
2828
2829 /**
2830 * Create a Transferable to use as the source for a data transfer.
2831 *
2832 * @param c The component holding the data to be transfered. This
2833 * argument is provided to enable sharing of TransferHandlers by
2834 * multiple components.
2835 * @return The representation of the data to be transfered.
2836 *
2837 */
2838 protected Transferable createTransferable(JComponent c) {
2839 if (c instanceof JList) {
2840 JList list = (JList) c;
2841 Object[] values = list.getSelectedValues();
2842
2843 if (values == null || values.length == 0) {
2844 return null;
2845 }
2846
2847 StringBuffer plainBuf = new StringBuffer();
2848 StringBuffer htmlBuf = new StringBuffer();
2849
2850 htmlBuf.append("<html>\n<body>\n<ul>\n");
2851
2852 for (int i = 0; i < values.length; i++) {
2853 Object obj = values[i];
2854 String val = ((obj == null) ? "" : obj.toString());
2855 plainBuf.append(val + "\n");
2856 htmlBuf.append(" <li>" + val + "\n");
2857 }
2858
2859 // remove the last newline
2860 plainBuf.deleteCharAt(plainBuf.length() - 1);
2861 htmlBuf.append("</ul>\n</body>\n</html>");
2862
2863 return new BasicTransferable(plainBuf.toString(), htmlBuf.toString());
2864 }
2865
2866 return null;
2867 }
2868
2869 public int getSourceActions(JComponent c) {
2870 return COPY;
2871 }
2872
2873 }
2874}