1 /*
2 * Copyright (c) 2003, 2006, 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
26 package sun.awt.X11;
27
28 import java.awt.*;
29 import java.awt.event.MouseEvent;
30 import java.awt.event.MouseWheelEvent;
31 import java.awt.event.AdjustmentEvent;
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import sun.util.logging.PlatformLogger;
35
36 // FIXME: implement multi-select
37 /*
38 * Class to paint a list of items, possibly with scrollbars
39 * This class paints all items with the same font
40 * For now, this class manages the list of items and painting thereof, but not
41 * posting of Item or ActionEvents
42 */
43 public class ListHelper implements XScrollbarClient {
44 private static final PlatformLogger log = PlatformLogger.getLogger("sun.awt.X11.ListHelper");
45
46 private final int FOCUS_INSET = 1;
47
48 private final int BORDER_WIDTH; // Width of border drawn around the list
49 // of items
50 private final int ITEM_MARGIN; // Margin between the border of the list
51 // of items and and item's bg, and between
52 // items
53 private final int TEXT_SPACE; // Space between the edge of an item and
54 // the text
55
56 private final int SCROLLBAR_WIDTH; // Width of a scrollbar
57
58 private java.util.List items; // List of items
59
60 // TODO: maybe this would be better as a simple int[]
61 private java.util.List selected; // List of selected items
62 private boolean multiSelect; // Can multiple items be selected
63 // at once?
64 private int focusedIndex;
65
66 private int maxVisItems; // # items visible without a vsb
67 private XVerticalScrollbar vsb; // null if unsupported
68 private boolean vsbVis;
69 private XHorizontalScrollbar hsb; // null if unsupported
70 private boolean hsbVis;
71
72 private Font font;
73 private FontMetrics fm;
74
75 private XWindow peer; // So far, only needed for painting
76 // on notifyValue()
77 private Color[] colors; // Passed in for painting on notifyValue()
78
79 // Holds the true if mouse is dragging outside of the area of the list
80 // The flag is used at the moment of the dragging and releasing mouse
81 // See 6243382 for more information
82 boolean mouseDraggedOutVertically = false;
83 private volatile boolean vsbVisibilityChanged = false;
84
85 /*
86 * Comment
87 */
88 public ListHelper(XWindow peer,
89 Color[] colors,
90 int initialSize,
91 boolean multiSelect,
92 boolean scrollVert,
93 boolean scrollHoriz,
94 Font font,
95 int maxVisItems,
96 int SPACE,
97 int MARGIN,
98 int BORDER,
99 int SCROLLBAR) {
100 this.peer = peer;
101 this.colors = colors;
102 this.multiSelect = multiSelect;
103 items = new ArrayList(initialSize);
104 selected = new ArrayList(1);
105 selected.add(Integer.valueOf(-1));
106
107 this.maxVisItems = maxVisItems;
108 if (scrollVert) {
109 vsb = new XVerticalScrollbar(this);
110 vsb.setValues(0, 0, 0, 0, 1, maxVisItems - 1);
111 }
112 if (scrollHoriz) {
113 hsb = new XHorizontalScrollbar(this);
114 hsb.setValues(0, 0, 0, 0, 1, 1);
115 }
116
117 setFont(font);
118 TEXT_SPACE = SPACE;
119 ITEM_MARGIN = MARGIN;
120 BORDER_WIDTH = BORDER;
121 SCROLLBAR_WIDTH = SCROLLBAR;
122 }
123
124 public Component getEventSource() {
125 return peer.getEventSource();
126 }
127
128 /**********************************************************************/
129 /* List management methods */
130 /**********************************************************************/
131
132 public void add(String item) {
133 items.add(item);
134 updateScrollbars();
135 }
136
137 public void add(String item, int index) {
138 items.add(index, item);
139 updateScrollbars();
140 }
141
142 public void remove(String item) {
143 // FIXME: need to clean up select list, too?
144 items.remove(item);
145 updateScrollbars();
146 // Is vsb visible now?
147 }
148
149 public void remove(int index) {
150 // FIXME: need to clean up select list, too?
151 items.remove(index);
152 updateScrollbars();
153 // Is vsb visible now?
154 }
155
156 public void removeAll() {
157 items.removeAll(items);
158 updateScrollbars();
159 }
160
161 public void setMultiSelect(boolean ms) {
162 multiSelect = ms;
163 }
164
165 /*
166 * docs.....definitely docs
167 * merely keeps internal track of which items are selected for painting
168 * dealing with target Components happens elsewhere
169 */
170 public void select(int index) {
171 if (index > getItemCount() - 1) {
172 index = (isEmpty() ? -1 : 0);
173 }
174 if (multiSelect) {
175 assert false : "Implement ListHelper.select() for multiselect";
176 }
177 else if (getSelectedIndex() != index) {
178 selected.remove(0);
179 selected.add(Integer.valueOf(index));
180 makeVisible(index);
181 }
182 }
183
184 /* docs */
185 public void deselect(int index) {
186 assert(false);
187 }
188
189 /* docs */
190 /* if called for multiselect, return -1 */
191 public int getSelectedIndex() {
192 if (!multiSelect) {
193 Integer val = (Integer)selected.get(0);
194 return val.intValue();
195 }
196 return -1;
197 }
198
199 int[] getSelectedIndexes() { assert(false); return null;}
200
201 /*
202 * A getter method for XChoicePeer.
203 * Returns vsbVisiblityChanged value and sets it to false.
204 */
205 public boolean checkVsbVisibilityChangedAndReset(){
206 boolean returnVal = vsbVisibilityChanged;
207 vsbVisibilityChanged = false;
208 return returnVal;
209 }
210
211 public boolean isEmpty() {
212 return items.isEmpty();
213 }
214
215 public int getItemCount() {
216 return items.size();
217 }
218
219 public String getItem(int index) {
220 return (String) items.get(index);
221 }
222
223 /**********************************************************************/
224 /* GUI-related methods */
225 /**********************************************************************/
226
227 public void setFocusedIndex(int index) {
228 focusedIndex = index;
229 }
230
231 public boolean isFocusedIndex(int index) {
232 return index == focusedIndex;
233 }
234
235 public void setFont(Font newFont) {
236 if (newFont != font) {
237 font = newFont;
238 fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
239 // Also cache stuff like fontHeight?
240 }
241 }
242
243 /*
244 * Returns width of the text of the longest item
245 */
246 public int getMaxItemWidth() {
247 int m = 0;
248 int end = getItemCount();
249 for(int i = 0 ; i < end ; i++) {
250 int l = fm.stringWidth(getItem(i));
251 m = Math.max(m, l);
252 }
253 return m;
254 }
255
256 /*
257 * Height of an item (this doesn't include ITEM_MARGIN)
258 */
259 int getItemHeight() {
260 return fm.getHeight() + (2*TEXT_SPACE);
261 }
262
263 public int y2index(int y) {
264 if (log.isLoggable(PlatformLogger.Level.FINE)) {
265 log.fine("y=" + y +", firstIdx=" + firstDisplayedIndex() +", itemHeight=" + getItemHeight()
266 + ",item_margin=" + ITEM_MARGIN);
267 }
268 // See 6243382 for more information
269 int newIdx = firstDisplayedIndex() + ((y - 2*ITEM_MARGIN) / (getItemHeight() + 2*ITEM_MARGIN));
270 return newIdx;
271 }
272
273 /* write these
274 int index2y(int);
275 public int numItemsDisplayed() {}
276 */
277
278 public int firstDisplayedIndex() {
279 if (vsbVis) {
280 return vsb.getValue();
281 }
282 return 0;
283 }
284
285 public int lastDisplayedIndex() {
286 // FIXME: need to account for horiz scroll bar
287 if (hsbVis) {
288 assert false : "Implement for horiz scroll bar";
289 }
290
291 return vsbVis ? vsb.getValue() + maxVisItems - 1: getItemCount() - 1;
292 }
293
294 /*
295 * If the given index is not visible in the List, scroll so that it is.
296 */
297 public void makeVisible(int index) {
298 if (vsbVis) {
299 if (index < firstDisplayedIndex()) {
300 vsb.setValue(index);
301 }
302 else if (index > lastDisplayedIndex()) {
303 vsb.setValue(index - maxVisItems + 1);
304 }
305 }
306 }
307
308 // FIXME: multi-select needs separate focused index
309 public void up() {
310 int curIdx = getSelectedIndex();
311 int numItems = getItemCount();
312 int newIdx;
313
314 assert curIdx >= 0;
315
316 if (curIdx == 0) {
317 newIdx = numItems - 1;
318 }
319 else {
320 newIdx = --curIdx;
321 }
322 // focus(newIdx);
323 select(newIdx);
324 }
325
326 public void down() {
327 int newIdx = (getSelectedIndex() + 1) % getItemCount();
328 select(newIdx);
329 }
330
331 public void pageUp() {
332 // FIXME: for multi-select, move the focused item, not the selected item
333 if (vsbVis && firstDisplayedIndex() > 0) {
334 if (multiSelect) {
335 assert false : "Implement pageUp() for multiSelect";
336 }
337 else {
338 int selectionOffset = getSelectedIndex() - firstDisplayedIndex();
339 // the vsb does bounds checking
340 int newIdx = firstDisplayedIndex() - vsb.getBlockIncrement();
341 vsb.setValue(newIdx);
342 select(firstDisplayedIndex() + selectionOffset);
343 }
344 }
345 }
346 public void pageDown() {
347 if (vsbVis && lastDisplayedIndex() < getItemCount() - 1) {
348 if (multiSelect) {
349 assert false : "Implement pageDown() for multiSelect";
350 }
351 else {
352 int selectionOffset = getSelectedIndex() - firstDisplayedIndex();
353 // the vsb does bounds checking
354 int newIdx = lastDisplayedIndex();
355 vsb.setValue(newIdx);
356 select(firstDisplayedIndex() + selectionOffset);
357 }
358 }
359 }
360 public void home() {}
361 public void end() {}
362
363
364 public boolean isVSBVisible() { return vsbVis; }
365 public boolean isHSBVisible() { return hsbVis; }
366
367 public XVerticalScrollbar getVSB() { return vsb; }
368 public XHorizontalScrollbar getHSB() { return hsb; }
369
370 public boolean isInVertSB(Rectangle bounds, int x, int y) {
371 if (vsbVis) {
372 assert vsb != null : "Vert scrollbar is visible, yet is null?";
373 int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height;
374 return (x <= bounds.width) &&
375 (x >= bounds.width - SCROLLBAR_WIDTH) &&
376 (y >= 0) &&
377 (y <= sbHeight);
378 }
379 return false;
380 }
381
382 public boolean isInHorizSB(Rectangle bounds, int x, int y) {
383 if (hsbVis) {
384 assert hsb != null : "Horiz scrollbar is visible, yet is null?";
385
386 int sbWidth = vsbVis ? bounds.width - SCROLLBAR_WIDTH : bounds.width;
387 return (x <= sbWidth) &&
388 (x >= 0) &&
389 (y >= bounds.height - SCROLLBAR_WIDTH) &&
390 (y <= bounds.height);
391 }
392 return false;
393 }
394
395 public void handleVSBEvent(MouseEvent e, Rectangle bounds, int x, int y) {
396 int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height;
397
398 vsb.handleMouseEvent(e.getID(),
399 e.getModifiers(),
400 x - (bounds.width - SCROLLBAR_WIDTH),
401 y);
402 }
403
404 /*
405 * Called when items are added/removed.
406 * Update whether the scrollbar is visible or not, scrollbar values
407 */
408 void updateScrollbars() {
409 boolean oldVsbVis = vsbVis;
410 vsbVis = vsb != null && items.size() > maxVisItems;
411 if (vsbVis) {
412 vsb.setValues(vsb.getValue(), getNumItemsDisplayed(),
413 vsb.getMinimum(), items.size());
414 }
415
416 // 6405689. If Vert Scrollbar gets disappeared from the dropdown menu we should repaint whole dropdown even if
417 // no actual resize gets invoked. This is needed because some painting artifacts remained between dropdown items
418 // but draw3DRect doesn't clear the area inside. Instead it just paints lines as borders.
419 vsbVisibilityChanged = (vsbVis != oldVsbVis);
420 // FIXME: check if added item makes a hsb necessary (if supported, that of course)
421 }
422
423 public int getNumItemsDisplayed() {
424 return items.size() > maxVisItems ? maxVisItems : items.size();
425 }
426
427 public void repaintScrollbarRequest(XScrollbar sb) {
428 Graphics g = peer.getGraphics();
429 Rectangle bounds = peer.getBounds();
430 if ((sb == vsb) && vsbVis) {
431 paintVSB(g, XComponentPeer.getSystemColors(), bounds);
432 }
433 else if ((sb == hsb) && hsbVis) {
434 paintHSB(g, XComponentPeer.getSystemColors(), bounds);
435 }
436 g.dispose();
437 }
438
439 public void notifyValue(XScrollbar obj, int type, int v, boolean isAdjusting) {
440 if (obj == vsb) {
441 int oldScrollValue = vsb.getValue();
442 vsb.setValue(v);
443 boolean needRepaint = (oldScrollValue != vsb.getValue());
444 // See 6243382 for more information
445 if (mouseDraggedOutVertically){
446 int oldItemValue = getSelectedIndex();
447 int newItemValue = getSelectedIndex() + v - oldScrollValue;
448 select(newItemValue);
449 needRepaint = needRepaint || (getSelectedIndex() != oldItemValue);
450 }
451
452 // FIXME: how are we going to paint!?
453 Graphics g = peer.getGraphics();
454 Rectangle bounds = peer.getBounds();
455 int first = v;
456 int last = Math.min(getItemCount() - 1,
457 v + maxVisItems);
458 if (needRepaint) {
459 paintItems(g, colors, bounds, first, last);
460 }
461 g.dispose();
462
463 }
464 else if ((XHorizontalScrollbar)obj == hsb) {
465 hsb.setValue(v);
466 // FIXME: how are we going to paint!?
467 }
468 }
469
470 public void updateColors(Color[] newColors) {
471 colors = newColors;
472 }
473
474 /*
475 public void paintItems(Graphics g,
476 Color[] colors,
477 Rectangle bounds,
478 Font font,
479 int first,
480 int last,
481 XVerticalScrollbar vsb,
482 XHorizontalScrollbar hsb) {
483 */
484 public void paintItems(Graphics g,
485 Color[] colors,
486 Rectangle bounds) {
487 // paint border
488 // paint items
489 // paint scrollbars
490 // paint focus?
491
492 }
493 public void paintAllItems(Graphics g,
494 Color[] colors,
495 Rectangle bounds) {
496 paintItems(g, colors, bounds,
497 firstDisplayedIndex(), lastDisplayedIndex());
498 }
499 public void paintItems(Graphics g,
500 Color[] colors,
501 Rectangle bounds,
502 int first,
503 int last) {
504 peer.flush();
505 int x = BORDER_WIDTH + ITEM_MARGIN;
506 int width = bounds.width - 2*ITEM_MARGIN - 2*BORDER_WIDTH - (vsbVis ? SCROLLBAR_WIDTH : 0);
507 int height = getItemHeight();
508 int y = BORDER_WIDTH + ITEM_MARGIN;
509
510 for (int i = first; i <= last ; i++) {
511 paintItem(g, colors, getItem(i),
512 x, y, width, height,
513 isItemSelected(i),
514 isFocusedIndex(i));
515 y += height + 2*ITEM_MARGIN;
516 }
517
518 if (vsbVis) {
519 paintVSB(g, XComponentPeer.getSystemColors(), bounds);
520 }
521 if (hsbVis) {
522 paintHSB(g, XComponentPeer.getSystemColors(), bounds);
523 }
524 peer.flush();
525 // FIXME: if none of the items were focused, paint focus around the
526 // entire list. This is how java.awt.List should work.
527 }
528
529 /*
530 * comment about what is painted (i.e. the focus rect
531 */
532 public void paintItem(Graphics g,
533 Color[] colors,
534 String string,
535 int x, int y, int width, int height,
536 boolean selected,
537 boolean focused) {
538 //System.out.println("LP.pI(): x="+x+" y="+y+" w="+width+" h="+height);
539 //g.setColor(colors[BACKGROUND_COLOR]);
540
541 // FIXME: items shouldn't draw into the scrollbar
542
543 if (selected) {
544 g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
545 }
546 else {
547 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
548 }
549 g.fillRect(x, y, width, height);
550
551 if (focused) {
552 //g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
553 g.setColor(Color.BLACK);
554 g.drawRect(x + FOCUS_INSET,
555 y + FOCUS_INSET,
556 width - 2*FOCUS_INSET,
557 height - 2*FOCUS_INSET);
558 }
559
560 if (selected) {
561 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
562 }
563 else {
564 g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
565 }
566 g.setFont(font);
567 //Rectangle clip = g.getClipBounds();
568 //g.clipRect(x, y, width, height);
569 //g.drawString(string, x + TEXT_SPACE, y + TEXT_SPACE + ITEM_MARGIN);
570
571 int fontAscent = fm.getAscent();
572 int fontDescent = fm.getDescent();
573
574 g.drawString(string, x + TEXT_SPACE, y + (height + fm.getMaxAscent() - fm.getMaxDescent())/2);
575 //g.clipRect(clip.x, clip.y, clip.width, clip.height);
576 }
577
578 boolean isItemSelected(int index) {
579 Iterator itr = selected.iterator();
580 while (itr.hasNext()) {
581 Integer val = (Integer)itr.next();
582 if (val.intValue() == index) {
583 return true;
584 }
585 }
586 return false;
587 }
588
589 public void paintVSB(Graphics g, Color colors[], Rectangle bounds) {
590 int height = bounds.height - 2*BORDER_WIDTH - (hsbVis ? (SCROLLBAR_WIDTH-2) : 0);
591 Graphics ng = g.create();
592
593 g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
594 try {
595 ng.translate(bounds.width - BORDER_WIDTH - SCROLLBAR_WIDTH,
596 BORDER_WIDTH);
597 // Update scrollbar's size
598 vsb.setSize(SCROLLBAR_WIDTH, bounds.height);
599 vsb.paint(ng, colors, true);
600 } finally {
601 ng.dispose();
602 }
603 }
604
605 public void paintHSB(Graphics g, Color colors[], Rectangle bounds) {
606
607 }
608
609 /*
610 * Helper method for Components with integrated scrollbars.
611 * Pass in the vertical and horizontal scroll bar (or null for none/hidden)
612 * and the MouseWheelEvent, and the appropriate scrollbar will be scrolled
613 * correctly.
614 * Returns whether or not scrolling actually took place. This will indicate
615 * whether or not repainting is required.
616 */
617 static boolean doWheelScroll(XVerticalScrollbar vsb,
618 XHorizontalScrollbar hsb,
619 MouseWheelEvent e) {
620 XScrollbar scroll = null;
621 int wheelRotation;
622
623 // Determine which, if any, sb to scroll
624 if (vsb != null) {
625 scroll = vsb;
626 }
627 else if (hsb != null) {
628 scroll = hsb;
629 }
630 else { // Neither scrollbar is showing
631 return false;
632 }
633
634 wheelRotation = e.getWheelRotation();
635
636 // Check if scroll is necessary
637 if ((wheelRotation < 0 && scroll.getValue() > scroll.getMinimum()) ||
638 (wheelRotation > 0 && scroll.getValue() < scroll.getMaximum()) ||
639 wheelRotation != 0) {
640
641 int type = e.getScrollType();
642 int incr;
643 if (type == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
644 incr = wheelRotation * scroll.getBlockIncrement();
645 }
646 else { // type is WHEEL_UNIT_SCROLL
647 incr = e.getUnitsToScroll() * scroll.getUnitIncrement();
648 }
649 scroll.setValue(scroll.getValue() + incr);
650 return true;
651 }
652 return false;
653 }
654
655 /*
656 * Helper method for XChoicePeer with integrated vertical scrollbar.
657 * Start or stop vertical scrolling when mouse dragged in / out the area of the list if it's required
658 * Restoring Motif behavior
659 * See 6243382 for more information
660 */
661 void trackMouseDraggedScroll(int mouseX, int mouseY, int listWidth, int listHeight){
662
663 if (!mouseDraggedOutVertically){
664 if (vsb.beforeThumb(mouseX, mouseY)) {
665 vsb.setMode(AdjustmentEvent.UNIT_DECREMENT);
666 } else {
667 vsb.setMode(AdjustmentEvent.UNIT_INCREMENT);
668 }
669 }
670
671 if(!mouseDraggedOutVertically && (mouseY < 0 || mouseY >= listHeight)){
672 mouseDraggedOutVertically = true;
673 vsb.startScrollingInstance();
674 }
675
676 if (mouseDraggedOutVertically && mouseY >= 0 && mouseY < listHeight && mouseX >= 0 && mouseX < listWidth){
677 mouseDraggedOutVertically = false;
678 vsb.stopScrollingInstance();
679 }
680 }
681
682 /*
683 * Helper method for XChoicePeer with integrated vertical scrollbar.
684 * Stop vertical scrolling when mouse released in / out the area of the list if it's required
685 * Restoring Motif behavior
686 * see 6243382 for more information
687 */
688 void trackMouseReleasedScroll(){
689
690 if (mouseDraggedOutVertically){
691 mouseDraggedOutVertically = false;
692 vsb.stopScrollingInstance();
693 }
694
695 }
696 }
--- EOF ---