001: /*
002: * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package sun.awt.X11;
027:
028: import java.awt.*;
029: import java.awt.event.MouseEvent;
030: import java.awt.event.MouseWheelEvent;
031: import java.awt.event.AdjustmentEvent;
032: import java.util.List;
033: import java.util.ArrayList;
034: import java.util.Iterator;
035: import sun.awt.motif.X11FontMetrics;
036: import java.util.logging.*;
037:
038: // FIXME: implement multi-select
039: /*
040: * Class to paint a list of items, possibly with scrollbars
041: * This class paints all items with the same font
042: * For now, this class manages the list of items and painting thereof, but not
043: * posting of Item or ActionEvents
044: */
045: public class ListHelper implements XScrollbarClient {
046: private static final Logger log = Logger
047: .getLogger("sun.awt.X11.ListHelper");
048:
049: private final int FOCUS_INSET = 1;
050:
051: private final int BORDER_WIDTH; // Width of border drawn around the list
052: // of items
053: private final int ITEM_MARGIN; // Margin between the border of the list
054: // of items and and item's bg, and between
055: // items
056: private final int TEXT_SPACE; // Space between the edge of an item and
057: // the text
058:
059: private final int SCROLLBAR_WIDTH; // Width of a scrollbar
060:
061: private java.util.List items; // List of items
062:
063: // TODO: maybe this would be better as a simple int[]
064: private java.util.List selected; // List of selected items
065: private boolean multiSelect; // Can multiple items be selected
066: // at once?
067: private int focusedIndex;
068:
069: private int maxVisItems; // # items visible without a vsb
070: private XVerticalScrollbar vsb; // null if unsupported
071: private boolean vsbVis;
072: private XHorizontalScrollbar hsb; // null if unsupported
073: private boolean hsbVis;
074:
075: private Font font;
076: private FontMetrics fm;
077:
078: private XWindow peer; // So far, only needed for painting
079: // on notifyValue()
080: private Color[] colors; // Passed in for painting on notifyValue()
081:
082: // Holds the true if mouse is dragging outside of the area of the list
083: // The flag is used at the moment of the dragging and releasing mouse
084: // See 6243382 for more information
085: boolean mouseDraggedOutVertically = false;
086: private volatile boolean vsbVisibilityChanged = false;
087:
088: /*
089: * Comment
090: */
091: public ListHelper(XWindow peer, Color[] colors, int initialSize,
092: boolean multiSelect, boolean scrollVert,
093: boolean scrollHoriz, Font font, int maxVisItems, int SPACE,
094: int MARGIN, int BORDER, int SCROLLBAR) {
095: this .peer = peer;
096: this .colors = colors;
097: this .multiSelect = multiSelect;
098: items = new ArrayList(initialSize);
099: selected = new ArrayList(1);
100: selected.add(Integer.valueOf(-1));
101:
102: this .maxVisItems = maxVisItems;
103: if (scrollVert) {
104: vsb = new XVerticalScrollbar(this );
105: vsb.setValues(0, 0, 0, 0, 1, maxVisItems - 1);
106: }
107: if (scrollHoriz) {
108: hsb = new XHorizontalScrollbar(this );
109: hsb.setValues(0, 0, 0, 0, 1, 1);
110: }
111:
112: setFont(font);
113: TEXT_SPACE = SPACE;
114: ITEM_MARGIN = MARGIN;
115: BORDER_WIDTH = BORDER;
116: SCROLLBAR_WIDTH = SCROLLBAR;
117: }
118:
119: public Component getEventSource() {
120: return peer.getEventSource();
121: }
122:
123: /**********************************************************************/
124: /* List management methods */
125: /**********************************************************************/
126:
127: public void add(String item) {
128: items.add(item);
129: updateScrollbars();
130: }
131:
132: public void add(String item, int index) {
133: items.add(index, item);
134: updateScrollbars();
135: }
136:
137: public void remove(String item) {
138: // FIXME: need to clean up select list, too?
139: items.remove(item);
140: updateScrollbars();
141: // Is vsb visible now?
142: }
143:
144: public void remove(int index) {
145: // FIXME: need to clean up select list, too?
146: items.remove(index);
147: updateScrollbars();
148: // Is vsb visible now?
149: }
150:
151: public void removeAll() {
152: items.removeAll(items);
153: updateScrollbars();
154: }
155:
156: public void setMultiSelect(boolean ms) {
157: multiSelect = ms;
158: }
159:
160: /*
161: * docs.....definitely docs
162: * merely keeps internal track of which items are selected for painting
163: * dealing with target Components happens elsewhere
164: */
165: public void select(int index) {
166: if (index > getItemCount() - 1) {
167: index = (isEmpty() ? -1 : 0);
168: }
169: if (multiSelect) {
170: assert false : "Implement ListHelper.select() for multiselect";
171: } else if (getSelectedIndex() != index) {
172: selected.remove(0);
173: selected.add(Integer.valueOf(index));
174: makeVisible(index);
175: }
176: }
177:
178: /* docs */
179: public void deselect(int index) {
180: assert (false);
181: }
182:
183: /* docs */
184: /* if called for multiselect, return -1 */
185: public int getSelectedIndex() {
186: if (!multiSelect) {
187: Integer val = (Integer) selected.get(0);
188: return val.intValue();
189: }
190: return -1;
191: }
192:
193: int[] getSelectedIndexes() {
194: assert (false);
195: return null;
196: }
197:
198: /*
199: * A getter method for XChoicePeer.
200: * Returns vsbVisiblityChanged value and sets it to false.
201: */
202: public boolean checkVsbVisibilityChangedAndReset() {
203: boolean returnVal = vsbVisibilityChanged;
204: vsbVisibilityChanged = false;
205: return returnVal;
206: }
207:
208: public boolean isEmpty() {
209: return items.isEmpty();
210: }
211:
212: public int getItemCount() {
213: return items.size();
214: }
215:
216: public String getItem(int index) {
217: return (String) items.get(index);
218: }
219:
220: /**********************************************************************/
221: /* GUI-related methods */
222: /**********************************************************************/
223:
224: public void setFocusedIndex(int index) {
225: focusedIndex = index;
226: }
227:
228: public boolean isFocusedIndex(int index) {
229: return index == focusedIndex;
230: }
231:
232: public void setFont(Font newFont) {
233: if (newFont != font) {
234: font = newFont;
235: fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
236: // Also cache stuff like fontHeight?
237: }
238: }
239:
240: /*
241: * Returns width of the text of the longest item
242: */
243: public int getMaxItemWidth() {
244: int m = 0;
245: int end = getItemCount();
246: for (int i = 0; i < end; i++) {
247: int l = fm.stringWidth(getItem(i));
248: m = Math.max(m, l);
249: }
250: return m;
251: }
252:
253: /*
254: * Height of an item (this doesn't include ITEM_MARGIN)
255: */
256: int getItemHeight() {
257: return fm.getHeight() + (2 * TEXT_SPACE);
258: }
259:
260: public int y2index(int y) {
261: if (log.isLoggable(Level.FINE)) {
262: log.fine("y=" + y + ", firstIdx=" + firstDisplayedIndex()
263: + ", itemHeight=" + getItemHeight()
264: + ",item_margin=" + ITEM_MARGIN);
265: }
266: // See 6243382 for more information
267: int newIdx = firstDisplayedIndex()
268: + ((y - 2 * ITEM_MARGIN) / (getItemHeight() + 2 * ITEM_MARGIN));
269: return newIdx;
270: }
271:
272: /* write these
273: int index2y(int);
274: public int numItemsDisplayed() {}
275: */
276:
277: public int firstDisplayedIndex() {
278: if (vsbVis) {
279: return vsb.getValue();
280: }
281: return 0;
282: }
283:
284: public int lastDisplayedIndex() {
285: // FIXME: need to account for horiz scroll bar
286: if (hsbVis) {
287: assert false : "Implement for horiz scroll bar";
288: }
289:
290: return vsbVis ? vsb.getValue() + maxVisItems - 1
291: : 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: } else if (index > lastDisplayedIndex()) {
302: vsb.setValue(index - maxVisItems + 1);
303: }
304: }
305: }
306:
307: // FIXME: multi-select needs separate focused index
308: public void up() {
309: int curIdx = getSelectedIndex();
310: int numItems = getItemCount();
311: int newIdx;
312:
313: assert curIdx >= 0;
314:
315: if (curIdx == 0) {
316: newIdx = numItems - 1;
317: } else {
318: newIdx = --curIdx;
319: }
320: // focus(newIdx);
321: select(newIdx);
322: }
323:
324: public void down() {
325: int newIdx = (getSelectedIndex() + 1) % getItemCount();
326: select(newIdx);
327: }
328:
329: public void pageUp() {
330: // FIXME: for multi-select, move the focused item, not the selected item
331: if (vsbVis && firstDisplayedIndex() > 0) {
332: if (multiSelect) {
333: assert false : "Implement pageUp() for multiSelect";
334: } else {
335: int selectionOffset = getSelectedIndex()
336: - firstDisplayedIndex();
337: // the vsb does bounds checking
338: int newIdx = firstDisplayedIndex()
339: - vsb.getBlockIncrement();
340: vsb.setValue(newIdx);
341: select(firstDisplayedIndex() + selectionOffset);
342: }
343: }
344: }
345:
346: public void pageDown() {
347: if (vsbVis && lastDisplayedIndex() < getItemCount() - 1) {
348: if (multiSelect) {
349: assert false : "Implement pageDown() for multiSelect";
350: } else {
351: int selectionOffset = getSelectedIndex()
352: - firstDisplayedIndex();
353: // the vsb does bounds checking
354: int newIdx = lastDisplayedIndex();
355: vsb.setValue(newIdx);
356: select(firstDisplayedIndex() + selectionOffset);
357: }
358: }
359: }
360:
361: public void home() {
362: }
363:
364: public void end() {
365: }
366:
367: public boolean isVSBVisible() {
368: return vsbVis;
369: }
370:
371: public boolean isHSBVisible() {
372: return hsbVis;
373: }
374:
375: public XVerticalScrollbar getVSB() {
376: return vsb;
377: }
378:
379: public XHorizontalScrollbar getHSB() {
380: return hsb;
381: }
382:
383: public boolean isInVertSB(Rectangle bounds, int x, int y) {
384: if (vsbVis) {
385: assert vsb != null : "Vert scrollbar is visible, yet is null?";
386: int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH
387: : bounds.height;
388: return (x <= bounds.width)
389: && (x >= bounds.width - SCROLLBAR_WIDTH)
390: && (y >= 0) && (y <= sbHeight);
391: }
392: return false;
393: }
394:
395: public boolean isInHorizSB(Rectangle bounds, int x, int y) {
396: if (hsbVis) {
397: assert hsb != null : "Horiz scrollbar is visible, yet is null?";
398:
399: int sbWidth = vsbVis ? bounds.width - SCROLLBAR_WIDTH
400: : bounds.width;
401: return (x <= sbWidth) && (x >= 0)
402: && (y >= bounds.height - SCROLLBAR_WIDTH)
403: && (y <= bounds.height);
404: }
405: return false;
406: }
407:
408: public void handleVSBEvent(MouseEvent e, Rectangle bounds, int x,
409: int y) {
410: int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH
411: : bounds.height;
412:
413: vsb.handleMouseEvent(e.getID(), e.getModifiers(), x
414: - (bounds.width - SCROLLBAR_WIDTH), y);
415: }
416:
417: /*
418: * Called when items are added/removed.
419: * Update whether the scrollbar is visible or not, scrollbar values
420: */
421: void updateScrollbars() {
422: boolean oldVsbVis = vsbVis;
423: vsbVis = vsb != null && items.size() > maxVisItems;
424: if (vsbVis) {
425: vsb.setValues(vsb.getValue(), getNumItemsDisplayed(), vsb
426: .getMinimum(), items.size());
427: }
428:
429: // 6405689. If Vert Scrollbar gets disappeared from the dropdown menu we should repaint whole dropdown even if
430: // no actual resize gets invoked. This is needed because some painting artifacts remained between dropdown items
431: // but draw3DRect doesn't clear the area inside. Instead it just paints lines as borders.
432: vsbVisibilityChanged = (vsbVis != oldVsbVis);
433: // FIXME: check if added item makes a hsb necessary (if supported, that of course)
434: }
435:
436: public int getNumItemsDisplayed() {
437: return items.size() > maxVisItems ? maxVisItems : items.size();
438: }
439:
440: public void repaintScrollbarRequest(XScrollbar sb) {
441: Graphics g = peer.getGraphics();
442: Rectangle bounds = peer.getBounds();
443: if ((sb == vsb) && vsbVis) {
444: paintVSB(g, XComponentPeer.getSystemColors(), bounds);
445: } else if ((sb == hsb) && hsbVis) {
446: paintHSB(g, XComponentPeer.getSystemColors(), bounds);
447: }
448: g.dispose();
449: }
450:
451: public void notifyValue(XScrollbar obj, int type, int v,
452: boolean isAdjusting) {
453: if (obj == vsb) {
454: int oldScrollValue = vsb.getValue();
455: vsb.setValue(v);
456: boolean needRepaint = (oldScrollValue != vsb.getValue());
457: // See 6243382 for more information
458: if (mouseDraggedOutVertically) {
459: int oldItemValue = getSelectedIndex();
460: int newItemValue = getSelectedIndex() + v
461: - oldScrollValue;
462: select(newItemValue);
463: needRepaint = needRepaint
464: || (getSelectedIndex() != oldItemValue);
465: }
466:
467: // FIXME: how are we going to paint!?
468: Graphics g = peer.getGraphics();
469: Rectangle bounds = peer.getBounds();
470: int first = v;
471: int last = Math.min(getItemCount() - 1, v + maxVisItems);
472: if (needRepaint) {
473: paintItems(g, colors, bounds, first, last);
474: }
475: g.dispose();
476:
477: } else if ((XHorizontalScrollbar) obj == hsb) {
478: hsb.setValue(v);
479: // FIXME: how are we going to paint!?
480: }
481: }
482:
483: public void updateColors(Color[] newColors) {
484: colors = newColors;
485: }
486:
487: /*
488: public void paintItems(Graphics g,
489: Color[] colors,
490: Rectangle bounds,
491: Font font,
492: int first,
493: int last,
494: XVerticalScrollbar vsb,
495: XHorizontalScrollbar hsb) {
496: */
497: public void paintItems(Graphics g, Color[] colors, Rectangle bounds) {
498: // paint border
499: // paint items
500: // paint scrollbars
501: // paint focus?
502:
503: }
504:
505: public void paintAllItems(Graphics g, Color[] colors,
506: Rectangle bounds) {
507: paintItems(g, colors, bounds, firstDisplayedIndex(),
508: lastDisplayedIndex());
509: }
510:
511: public void paintItems(Graphics g, Color[] colors,
512: Rectangle bounds, int first, int last) {
513: peer.flush();
514: int x = BORDER_WIDTH + ITEM_MARGIN;
515: int width = bounds.width - 2 * ITEM_MARGIN - 2 * BORDER_WIDTH
516: - (vsbVis ? SCROLLBAR_WIDTH : 0);
517: int height = getItemHeight();
518: int y = BORDER_WIDTH + ITEM_MARGIN;
519:
520: for (int i = first; i <= last; i++) {
521: paintItem(g, colors, getItem(i), x, y, width, height,
522: isItemSelected(i), isFocusedIndex(i));
523: y += height + 2 * ITEM_MARGIN;
524: }
525:
526: if (vsbVis) {
527: paintVSB(g, XComponentPeer.getSystemColors(), bounds);
528: }
529: if (hsbVis) {
530: paintHSB(g, XComponentPeer.getSystemColors(), bounds);
531: }
532: peer.flush();
533: // FIXME: if none of the items were focused, paint focus around the
534: // entire list. This is how java.awt.List should work.
535: }
536:
537: /*
538: * comment about what is painted (i.e. the focus rect
539: */
540: public void paintItem(Graphics g, Color[] colors, String string,
541: int x, int y, int width, int height, boolean selected,
542: boolean focused) {
543: //System.out.println("LP.pI(): x="+x+" y="+y+" w="+width+" h="+height);
544: //g.setColor(colors[BACKGROUND_COLOR]);
545:
546: // FIXME: items shouldn't draw into the scrollbar
547:
548: if (selected) {
549: g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
550: } else {
551: g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
552: }
553: g.fillRect(x, y, width, height);
554:
555: if (focused) {
556: //g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
557: g.setColor(Color.BLACK);
558: g.drawRect(x + FOCUS_INSET, y + FOCUS_INSET, width - 2
559: * FOCUS_INSET, height - 2 * FOCUS_INSET);
560: }
561:
562: if (selected) {
563: g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
564: } else {
565: g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
566: }
567: g.setFont(font);
568: //Rectangle clip = g.getClipBounds();
569: //g.clipRect(x, y, width, height);
570: //g.drawString(string, x + TEXT_SPACE, y + TEXT_SPACE + ITEM_MARGIN);
571:
572: int fontAscent = fm.getAscent();
573: int fontDescent = fm.getDescent();
574:
575: g
576: .drawString(string, x + TEXT_SPACE, y
577: + (height + fm.getMaxAscent() - fm
578: .getMaxDescent()) / 2);
579: //g.clipRect(clip.x, clip.y, clip.width, clip.height);
580: }
581:
582: boolean isItemSelected(int index) {
583: Iterator itr = selected.iterator();
584: while (itr.hasNext()) {
585: Integer val = (Integer) itr.next();
586: if (val.intValue() == index) {
587: return true;
588: }
589: }
590: return false;
591: }
592:
593: public void paintVSB(Graphics g, Color colors[], Rectangle bounds) {
594: int height = bounds.height - 2 * BORDER_WIDTH
595: - (hsbVis ? (SCROLLBAR_WIDTH - 2) : 0);
596: Graphics ng = g.create();
597:
598: g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
599: try {
600: ng.translate(bounds.width - BORDER_WIDTH - SCROLLBAR_WIDTH,
601: BORDER_WIDTH);
602: // Update scrollbar's size
603: vsb.setSize(SCROLLBAR_WIDTH, bounds.height);
604: vsb.paint(ng, colors, true);
605: } finally {
606: ng.dispose();
607: }
608: }
609:
610: public void paintHSB(Graphics g, Color colors[], Rectangle bounds) {
611:
612: }
613:
614: /*
615: * Helper method for Components with integrated scrollbars.
616: * Pass in the vertical and horizontal scroll bar (or null for none/hidden)
617: * and the MouseWheelEvent, and the appropriate scrollbar will be scrolled
618: * correctly.
619: * Returns whether or not scrolling actually took place. This will indicate
620: * whether or not repainting is required.
621: */
622: static boolean doWheelScroll(XVerticalScrollbar vsb,
623: XHorizontalScrollbar hsb, MouseWheelEvent e) {
624: XScrollbar scroll = null;
625: int wheelRotation;
626:
627: // Determine which, if any, sb to scroll
628: if (vsb != null) {
629: scroll = vsb;
630: } else if (hsb != null) {
631: scroll = hsb;
632: } else { // Neither scrollbar is showing
633: return false;
634: }
635:
636: wheelRotation = e.getWheelRotation();
637:
638: // Check if scroll is necessary
639: if ((wheelRotation < 0 && scroll.getValue() > scroll
640: .getMinimum())
641: || (wheelRotation > 0 && scroll.getValue() < scroll
642: .getMaximum()) || wheelRotation != 0) {
643:
644: int type = e.getScrollType();
645: int incr;
646: if (type == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
647: incr = wheelRotation * scroll.getBlockIncrement();
648: } else { // type is WHEEL_UNIT_SCROLL
649: incr = e.getUnitsToScroll() * scroll.getUnitIncrement();
650: }
651: scroll.setValue(scroll.getValue() + incr);
652: return true;
653: }
654: return false;
655: }
656:
657: /*
658: * Helper method for XChoicePeer with integrated vertical scrollbar.
659: * Start or stop vertical scrolling when mouse dragged in / out the area of the list if it's required
660: * Restoring Motif behavior
661: * See 6243382 for more information
662: */
663: void trackMouseDraggedScroll(int mouseX, int mouseY, int listWidth,
664: int listHeight) {
665:
666: if (!mouseDraggedOutVertically) {
667: if (vsb.beforeThumb(mouseX, mouseY)) {
668: vsb.setMode(AdjustmentEvent.UNIT_DECREMENT);
669: } else {
670: vsb.setMode(AdjustmentEvent.UNIT_INCREMENT);
671: }
672: }
673:
674: if (!mouseDraggedOutVertically
675: && (mouseY < 0 || mouseY >= listHeight)) {
676: mouseDraggedOutVertically = true;
677: vsb.startScrollingInstance();
678: }
679:
680: if (mouseDraggedOutVertically && mouseY >= 0
681: && mouseY < listHeight && mouseX >= 0
682: && mouseX < listWidth) {
683: mouseDraggedOutVertically = false;
684: vsb.stopScrollingInstance();
685: }
686: }
687:
688: /*
689: * Helper method for XChoicePeer with integrated vertical scrollbar.
690: * Stop vertical scrolling when mouse released in / out the area of the list if it's required
691: * Restoring Motif behavior
692: * see 6243382 for more information
693: */
694: void trackMouseReleasedScroll() {
695:
696: if (mouseDraggedOutVertically) {
697: mouseDraggedOutVertically = false;
698: vsb.stopScrollingInstance();
699: }
700:
701: }
702: }
|