jdk/src/solaris/classes/sun/awt/X11/ListHelper.java
changeset 2 90ce3da70b43
child 3938 ef327bd847c0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/solaris/classes/sun/awt/X11/ListHelper.java	Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,698 @@
+/*
+ * Copyright 2003-2006 Sun Microsystems, Inc.  All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.  Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.awt.X11;
+
+import java.awt.*;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.AdjustmentEvent;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Iterator;
+import sun.awt.motif.X11FontMetrics;
+import java.util.logging.*;
+
+// FIXME: implement multi-select
+/*
+ * Class to paint a list of items, possibly with scrollbars
+ * This class paints all items with the same font
+ * For now, this class manages the list of items and painting thereof, but not
+ * posting of Item or ActionEvents
+ */
+public class ListHelper implements XScrollbarClient {
+    private static final Logger log = Logger.getLogger("sun.awt.X11.ListHelper");
+
+    private final int FOCUS_INSET = 1;
+
+    private final int BORDER_WIDTH; // Width of border drawn around the list
+                                    // of items
+    private final int ITEM_MARGIN;  // Margin between the border of the list
+                                    // of items and and item's bg, and between
+                                    // items
+    private final int TEXT_SPACE;   // Space between the edge of an item and
+                                    // the text
+
+    private final int SCROLLBAR_WIDTH;  // Width of a scrollbar
+
+    private java.util.List items;        // List of items
+
+    // TODO: maybe this would be better as a simple int[]
+    private java.util.List selected;     // List of selected items
+    private boolean multiSelect;         // Can multiple items be selected
+                                         // at once?
+    private int focusedIndex;
+
+    private int maxVisItems;             // # items visible without a vsb
+    private XVerticalScrollbar vsb;      // null if unsupported
+    private boolean vsbVis;
+    private XHorizontalScrollbar hsb;    // null if unsupported
+    private boolean hsbVis;
+
+    private Font font;
+    private FontMetrics fm;
+
+    private XWindow peer;   // So far, only needed for painting
+                            // on notifyValue()
+    private Color[] colors; // Passed in for painting on notifyValue()
+
+    // Holds the true if mouse is dragging outside of the area of the list
+    // The flag is used at the moment of the dragging and releasing mouse
+    // See 6243382 for more information
+    boolean mouseDraggedOutVertically = false;
+    private volatile boolean vsbVisibilityChanged = false;
+
+    /*
+     * Comment
+     */
+    public ListHelper(XWindow peer,
+                      Color[] colors,
+                      int initialSize,
+                      boolean multiSelect,
+                      boolean scrollVert,
+                      boolean scrollHoriz,
+                      Font font,
+                      int maxVisItems,
+                      int SPACE,
+                      int MARGIN,
+                      int BORDER,
+                      int SCROLLBAR) {
+        this.peer = peer;
+        this.colors = colors;
+        this.multiSelect = multiSelect;
+        items = new ArrayList(initialSize);
+        selected = new ArrayList(1);
+        selected.add(Integer.valueOf(-1));
+
+        this.maxVisItems = maxVisItems;
+        if (scrollVert) {
+            vsb = new XVerticalScrollbar(this);
+            vsb.setValues(0, 0, 0, 0, 1, maxVisItems - 1);
+        }
+        if (scrollHoriz) {
+            hsb = new XHorizontalScrollbar(this);
+            hsb.setValues(0, 0, 0, 0, 1, 1);
+        }
+
+        setFont(font);
+        TEXT_SPACE = SPACE;
+        ITEM_MARGIN = MARGIN;
+        BORDER_WIDTH = BORDER;
+        SCROLLBAR_WIDTH = SCROLLBAR;
+    }
+
+    public Component getEventSource() {
+        return peer.getEventSource();
+    }
+
+    /**********************************************************************/
+    /* List management methods                                            */
+    /**********************************************************************/
+
+    public void add(String item) {
+        items.add(item);
+        updateScrollbars();
+    }
+
+    public void add(String item, int index) {
+        items.add(index, item);
+        updateScrollbars();
+    }
+
+    public void remove(String item) {
+        // FIXME: need to clean up select list, too?
+        items.remove(item);
+        updateScrollbars();
+        // Is vsb visible now?
+    }
+
+    public void remove(int index) {
+        // FIXME: need to clean up select list, too?
+        items.remove(index);
+        updateScrollbars();
+        // Is vsb visible now?
+    }
+
+    public void removeAll() {
+        items.removeAll(items);
+        updateScrollbars();
+    }
+
+    public void setMultiSelect(boolean ms) {
+        multiSelect = ms;
+    }
+
+    /*
+     * docs.....definitely docs
+     * merely keeps internal track of which items are selected for painting
+     * dealing with target Components happens elsewhere
+     */
+    public void select(int index) {
+        if (index > getItemCount() - 1) {
+            index = (isEmpty() ? -1 : 0);
+        }
+        if (multiSelect) {
+            assert false : "Implement ListHelper.select() for multiselect";
+        }
+        else if (getSelectedIndex() != index) {
+            selected.remove(0);
+            selected.add(Integer.valueOf(index));
+            makeVisible(index);
+        }
+    }
+
+    /* docs */
+    public void deselect(int index) {
+        assert(false);
+    }
+
+    /* docs */
+    /* if called for multiselect, return -1 */
+    public int getSelectedIndex() {
+        if (!multiSelect) {
+            Integer val = (Integer)selected.get(0);
+            return val.intValue();
+        }
+        return -1;
+    }
+
+    int[] getSelectedIndexes() { assert(false); return null;}
+
+    /*
+     * A getter method for XChoicePeer.
+     * Returns vsbVisiblityChanged value and sets it to false.
+     */
+    public boolean checkVsbVisibilityChangedAndReset(){
+        boolean returnVal = vsbVisibilityChanged;
+        vsbVisibilityChanged = false;
+        return returnVal;
+    }
+
+    public boolean isEmpty() {
+        return items.isEmpty();
+    }
+
+    public int getItemCount() {
+        return items.size();
+    }
+
+    public String getItem(int index) {
+        return (String) items.get(index);
+    }
+
+    /**********************************************************************/
+    /* GUI-related methods                                                */
+    /**********************************************************************/
+
+    public void setFocusedIndex(int index) {
+        focusedIndex = index;
+    }
+
+    public boolean isFocusedIndex(int index) {
+        return index == focusedIndex;
+    }
+
+    public void setFont(Font newFont) {
+        if (newFont != font) {
+            font = newFont;
+            fm = Toolkit.getDefaultToolkit().getFontMetrics(font);
+            // Also cache stuff like fontHeight?
+        }
+    }
+
+    /*
+     * Returns width of the text of the longest item
+     */
+    public int getMaxItemWidth() {
+        int m = 0;
+        int end = getItemCount();
+        for(int i = 0 ; i < end ; i++) {
+            int l = fm.stringWidth(getItem(i));
+            m = Math.max(m, l);
+        }
+        return m;
+    }
+
+    /*
+     * Height of an item (this doesn't include ITEM_MARGIN)
+     */
+    int getItemHeight() {
+        return fm.getHeight() + (2*TEXT_SPACE);
+    }
+
+    public int y2index(int y) {
+        if (log.isLoggable(Level.FINE)) {
+            log.fine("y=" + y +", firstIdx=" + firstDisplayedIndex() +", itemHeight=" + getItemHeight()
+                     + ",item_margin=" + ITEM_MARGIN);
+        }
+        // See 6243382 for more information
+        int newIdx = firstDisplayedIndex() + ((y - 2*ITEM_MARGIN) / (getItemHeight() + 2*ITEM_MARGIN));
+        return newIdx;
+    }
+
+    /* write these
+    int index2y(int);
+    public int numItemsDisplayed() {}
+    */
+
+    public int firstDisplayedIndex() {
+        if (vsbVis) {
+            return vsb.getValue();
+        }
+        return 0;
+    }
+
+    public int lastDisplayedIndex() {
+        // FIXME: need to account for horiz scroll bar
+        if (hsbVis) {
+            assert false : "Implement for horiz scroll bar";
+        }
+
+        return vsbVis ? vsb.getValue() + maxVisItems - 1: getItemCount() - 1;
+    }
+
+    /*
+     * If the given index is not visible in the List, scroll so that it is.
+     */
+    public void makeVisible(int index) {
+        if (vsbVis) {
+            if (index < firstDisplayedIndex()) {
+                vsb.setValue(index);
+            }
+            else if (index > lastDisplayedIndex()) {
+                vsb.setValue(index - maxVisItems + 1);
+            }
+        }
+    }
+
+    // FIXME: multi-select needs separate focused index
+    public void up() {
+        int curIdx = getSelectedIndex();
+        int numItems = getItemCount();
+        int newIdx;
+
+        assert curIdx >= 0;
+
+        if (curIdx == 0) {
+            newIdx = numItems - 1;
+        }
+        else {
+            newIdx = --curIdx;
+        }
+        // focus(newIdx);
+        select(newIdx);
+    }
+
+    public void down() {
+        int newIdx = (getSelectedIndex() + 1) % getItemCount();
+        select(newIdx);
+    }
+
+    public void pageUp() {
+        // FIXME: for multi-select, move the focused item, not the selected item
+        if (vsbVis && firstDisplayedIndex() > 0) {
+            if (multiSelect) {
+                assert false : "Implement pageUp() for multiSelect";
+            }
+            else {
+                int selectionOffset = getSelectedIndex() - firstDisplayedIndex();
+                // the vsb does bounds checking
+                int newIdx = firstDisplayedIndex() - vsb.getBlockIncrement();
+                vsb.setValue(newIdx);
+                select(firstDisplayedIndex() + selectionOffset);
+            }
+        }
+    }
+    public void pageDown() {
+        if (vsbVis && lastDisplayedIndex() < getItemCount() - 1) {
+            if (multiSelect) {
+                assert false : "Implement pageDown() for multiSelect";
+            }
+            else {
+                int selectionOffset = getSelectedIndex() - firstDisplayedIndex();
+                // the vsb does bounds checking
+                int newIdx = lastDisplayedIndex();
+                vsb.setValue(newIdx);
+                select(firstDisplayedIndex() + selectionOffset);
+            }
+        }
+    }
+    public void home() {}
+    public void end() {}
+
+
+    public boolean isVSBVisible() { return vsbVis; }
+    public boolean isHSBVisible() { return hsbVis; }
+
+    public XVerticalScrollbar getVSB() { return vsb; }
+    public XHorizontalScrollbar getHSB() { return hsb; }
+
+    public boolean isInVertSB(Rectangle bounds, int x, int y) {
+        if (vsbVis) {
+            assert vsb != null : "Vert scrollbar is visible, yet is null?";
+            int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height;
+            return (x <= bounds.width) &&
+                   (x >= bounds.width - SCROLLBAR_WIDTH) &&
+                   (y >= 0) &&
+                   (y <= sbHeight);
+        }
+        return false;
+    }
+
+    public boolean isInHorizSB(Rectangle bounds, int x, int y) {
+        if (hsbVis) {
+            assert hsb != null : "Horiz scrollbar is visible, yet is null?";
+
+            int sbWidth = vsbVis ? bounds.width - SCROLLBAR_WIDTH : bounds.width;
+            return (x <= sbWidth) &&
+                   (x >= 0) &&
+                   (y >= bounds.height - SCROLLBAR_WIDTH) &&
+                   (y <= bounds.height);
+        }
+        return false;
+    }
+
+    public void handleVSBEvent(MouseEvent e, Rectangle bounds, int x, int y) {
+        int sbHeight = hsbVis ? bounds.height - SCROLLBAR_WIDTH : bounds.height;
+
+        vsb.handleMouseEvent(e.getID(),
+                             e.getModifiers(),
+                             x - (bounds.width - SCROLLBAR_WIDTH),
+                             y);
+    }
+
+    /*
+     * Called when items are added/removed.
+     * Update whether the scrollbar is visible or not, scrollbar values
+     */
+    void updateScrollbars() {
+        boolean oldVsbVis = vsbVis;
+        vsbVis = vsb != null && items.size() > maxVisItems;
+        if (vsbVis) {
+            vsb.setValues(vsb.getValue(), getNumItemsDisplayed(),
+                          vsb.getMinimum(), items.size());
+        }
+
+        // 6405689. If Vert Scrollbar gets disappeared from the dropdown menu we should repaint whole dropdown even if
+        // no actual resize gets invoked. This is needed because some painting artifacts remained between dropdown items
+        // but draw3DRect doesn't clear the area inside. Instead it just paints lines as borders.
+        vsbVisibilityChanged = (vsbVis != oldVsbVis);
+        // FIXME: check if added item makes a hsb necessary (if supported, that of course)
+    }
+
+    public int getNumItemsDisplayed() {
+        return items.size() > maxVisItems ? maxVisItems : items.size();
+    }
+
+    public void repaintScrollbarRequest(XScrollbar sb) {
+        Graphics g = peer.getGraphics();
+        Rectangle bounds = peer.getBounds();
+        if ((sb == vsb) && vsbVis) {
+            paintVSB(g, XComponentPeer.getSystemColors(), bounds);
+        }
+        else if ((sb == hsb) && hsbVis) {
+            paintHSB(g, XComponentPeer.getSystemColors(), bounds);
+        }
+        g.dispose();
+    }
+
+    public void notifyValue(XScrollbar obj, int type, int v, boolean isAdjusting) {
+        if (obj == vsb) {
+            int oldScrollValue = vsb.getValue();
+            vsb.setValue(v);
+            boolean needRepaint = (oldScrollValue != vsb.getValue());
+            // See 6243382 for more information
+            if (mouseDraggedOutVertically){
+                int oldItemValue = getSelectedIndex();
+                int newItemValue = getSelectedIndex() + v - oldScrollValue;
+                select(newItemValue);
+                needRepaint = needRepaint || (getSelectedIndex() != oldItemValue);
+            }
+
+            // FIXME: how are we going to paint!?
+            Graphics g = peer.getGraphics();
+            Rectangle bounds = peer.getBounds();
+            int first = v;
+            int last = Math.min(getItemCount() - 1,
+                                v + maxVisItems);
+            if (needRepaint) {
+                paintItems(g, colors, bounds, first, last);
+            }
+            g.dispose();
+
+        }
+        else if ((XHorizontalScrollbar)obj == hsb) {
+            hsb.setValue(v);
+            // FIXME: how are we going to paint!?
+        }
+    }
+
+    public void updateColors(Color[] newColors) {
+        colors = newColors;
+    }
+
+    /*
+    public void paintItems(Graphics g,
+                           Color[] colors,
+                           Rectangle bounds,
+                           Font font,
+                           int first,
+                           int last,
+                           XVerticalScrollbar vsb,
+                           XHorizontalScrollbar hsb) {
+    */
+    public void paintItems(Graphics g,
+                           Color[] colors,
+                           Rectangle bounds) {
+        // paint border
+        // paint items
+        // paint scrollbars
+        // paint focus?
+
+    }
+    public void paintAllItems(Graphics g,
+                           Color[] colors,
+                           Rectangle bounds) {
+        paintItems(g, colors, bounds,
+                   firstDisplayedIndex(), lastDisplayedIndex());
+    }
+    public void paintItems(Graphics g,
+                           Color[] colors,
+                           Rectangle bounds,
+                           int first,
+                           int last) {
+        peer.flush();
+        int x = BORDER_WIDTH + ITEM_MARGIN;
+        int width = bounds.width - 2*ITEM_MARGIN - 2*BORDER_WIDTH - (vsbVis ? SCROLLBAR_WIDTH : 0);
+        int height = getItemHeight();
+        int y = BORDER_WIDTH + ITEM_MARGIN;
+
+        for (int i = first; i <= last ; i++) {
+            paintItem(g, colors, getItem(i),
+                      x, y, width, height,
+                      isItemSelected(i),
+                      isFocusedIndex(i));
+            y += height + 2*ITEM_MARGIN;
+        }
+
+        if (vsbVis) {
+            paintVSB(g, XComponentPeer.getSystemColors(), bounds);
+        }
+        if (hsbVis) {
+            paintHSB(g, XComponentPeer.getSystemColors(), bounds);
+        }
+        peer.flush();
+        // FIXME: if none of the items were focused, paint focus around the
+        // entire list.  This is how java.awt.List should work.
+    }
+
+    /*
+     * comment about what is painted (i.e. the focus rect
+     */
+    public void paintItem(Graphics g,
+                          Color[] colors,
+                          String string,
+                          int x, int y, int width, int height,
+                          boolean selected,
+                          boolean focused) {
+        //System.out.println("LP.pI(): x="+x+" y="+y+" w="+width+" h="+height);
+        //g.setColor(colors[BACKGROUND_COLOR]);
+
+        // FIXME: items shouldn't draw into the scrollbar
+
+        if (selected) {
+            g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
+        }
+        else {
+            g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
+        }
+        g.fillRect(x, y, width, height);
+
+        if (focused) {
+            //g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
+            g.setColor(Color.BLACK);
+            g.drawRect(x + FOCUS_INSET,
+                       y + FOCUS_INSET,
+                       width - 2*FOCUS_INSET,
+                       height - 2*FOCUS_INSET);
+        }
+
+        if (selected) {
+            g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
+        }
+        else {
+            g.setColor(colors[XComponentPeer.FOREGROUND_COLOR]);
+        }
+        g.setFont(font);
+        //Rectangle clip = g.getClipBounds();
+        //g.clipRect(x, y, width, height);
+        //g.drawString(string, x + TEXT_SPACE, y + TEXT_SPACE + ITEM_MARGIN);
+
+        int fontAscent = fm.getAscent();
+        int fontDescent = fm.getDescent();
+
+        g.drawString(string, x + TEXT_SPACE, y + (height + fm.getMaxAscent() - fm.getMaxDescent())/2);
+        //g.clipRect(clip.x, clip.y, clip.width, clip.height);
+    }
+
+    boolean isItemSelected(int index) {
+        Iterator itr = selected.iterator();
+        while (itr.hasNext()) {
+            Integer val = (Integer)itr.next();
+            if (val.intValue() == index) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void paintVSB(Graphics g, Color colors[], Rectangle bounds) {
+        int height = bounds.height - 2*BORDER_WIDTH - (hsbVis ? (SCROLLBAR_WIDTH-2) : 0);
+        Graphics ng = g.create();
+
+        g.setColor(colors[XComponentPeer.BACKGROUND_COLOR]);
+        try {
+            ng.translate(bounds.width - BORDER_WIDTH - SCROLLBAR_WIDTH,
+                         BORDER_WIDTH);
+            // Update scrollbar's size
+            vsb.setSize(SCROLLBAR_WIDTH, bounds.height);
+            vsb.paint(ng, colors, true);
+        } finally {
+            ng.dispose();
+        }
+    }
+
+    public void paintHSB(Graphics g, Color colors[], Rectangle bounds) {
+
+    }
+
+    /*
+     * Helper method for Components with integrated scrollbars.
+     * Pass in the vertical and horizontal scroll bar (or null for none/hidden)
+     * and the MouseWheelEvent, and the appropriate scrollbar will be scrolled
+     * correctly.
+     * Returns whether or not scrolling actually took place.  This will indicate
+     * whether or not repainting is required.
+     */
+    static boolean doWheelScroll(XVerticalScrollbar vsb,
+                                     XHorizontalScrollbar hsb,
+                                     MouseWheelEvent e) {
+        XScrollbar scroll = null;
+        int wheelRotation;
+
+        // Determine which, if any, sb to scroll
+        if (vsb != null) {
+            scroll = vsb;
+        }
+        else if (hsb != null) {
+            scroll = hsb;
+        }
+        else { // Neither scrollbar is showing
+            return false;
+        }
+
+        wheelRotation = e.getWheelRotation();
+
+        // Check if scroll is necessary
+        if ((wheelRotation < 0 && scroll.getValue() > scroll.getMinimum()) ||
+            (wheelRotation > 0 && scroll.getValue() < scroll.getMaximum()) ||
+            wheelRotation != 0) {
+
+            int type = e.getScrollType();
+            int incr;
+            if (type == MouseWheelEvent.WHEEL_BLOCK_SCROLL) {
+                incr = wheelRotation * scroll.getBlockIncrement();
+            }
+            else { // type is WHEEL_UNIT_SCROLL
+                incr = e.getUnitsToScroll() * scroll.getUnitIncrement();
+            }
+            scroll.setValue(scroll.getValue() + incr);
+            return true;
+        }
+        return false;
+    }
+
+    /*
+     * Helper method for XChoicePeer with integrated vertical scrollbar.
+     * Start or stop vertical scrolling when mouse dragged in / out the area of the list if it's required
+     * Restoring Motif behavior
+     * See 6243382 for more information
+     */
+    void trackMouseDraggedScroll(int mouseX, int mouseY, int listWidth, int listHeight){
+
+        if (!mouseDraggedOutVertically){
+            if (vsb.beforeThumb(mouseX, mouseY)) {
+                vsb.setMode(AdjustmentEvent.UNIT_DECREMENT);
+            } else {
+                vsb.setMode(AdjustmentEvent.UNIT_INCREMENT);
+            }
+        }
+
+        if(!mouseDraggedOutVertically && (mouseY < 0 || mouseY >= listHeight)){
+            mouseDraggedOutVertically = true;
+            vsb.startScrollingInstance();
+        }
+
+        if (mouseDraggedOutVertically && mouseY >= 0 && mouseY < listHeight && mouseX >= 0 && mouseX < listWidth){
+            mouseDraggedOutVertically = false;
+            vsb.stopScrollingInstance();
+        }
+    }
+
+    /*
+     * Helper method for XChoicePeer with integrated vertical scrollbar.
+     * Stop vertical scrolling when mouse released in / out the area of the list if it's required
+     * Restoring Motif behavior
+     * see 6243382 for more information
+     */
+    void trackMouseReleasedScroll(){
+
+        if (mouseDraggedOutVertically){
+            mouseDraggedOutVertically = false;
+            vsb.stopScrollingInstance();
+        }
+
+    }
+}