History

My first initial commit with all of mine experimental changes.

Dmitry Kabak (userdima committed 13 years ago

Commit d37b88d1 parent 3f5b46618352 files · +2598093 −-8690Browse files

Showing the changes to java/pkg/javax/swing/plaf/basic/BasicListUI.java (1 of 18352 files). Show all

Unified · Split

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