0001: /*
0002: * Copyright 2003-2007 Sun Microsystems, Inc. All Rights Reserved.
0003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
0004: *
0005: * This code is free software; you can redistribute it and/or modify it
0006: * under the terms of the GNU General Public License version 2 only, as
0007: * published by the Free Software Foundation. Sun designates this
0008: * particular file as subject to the "Classpath" exception as provided
0009: * by Sun in the LICENSE file that accompanied this code.
0010: *
0011: * This code is distributed in the hope that it will be useful, but WITHOUT
0012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
0014: * version 2 for more details (a copy is included in the LICENSE file that
0015: * accompanied this code).
0016: *
0017: * You should have received a copy of the GNU General Public License version
0018: * 2 along with this work; if not, write to the Free Software Foundation,
0019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
0020: *
0021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
0022: * CA 95054 USA or visit www.sun.com if you need additional information or
0023: * have any questions.
0024: */
0025:
0026: package sun.awt.X11;
0027:
0028: import java.awt.*;
0029: import java.awt.peer.*;
0030: import java.awt.event.*;
0031: import java.util.logging.*;
0032:
0033: // FIXME: tab traversal should be disabled when mouse is captured (4816336)
0034:
0035: // FIXME: key and mouse events should not be delivered to listeners when the Choice is unfurled. Must override handleNativeKey/MouseEvent (4816336)
0036:
0037: // FIXME: test programmatic add/remove/clear/etc
0038:
0039: // FIXME: account for unfurling at the edge of the screen
0040: // Note: can't set x,y on layout(), 'cause moving the top-level to the
0041: // edge of the screen won't call layout(). Just do it on paint, I guess
0042:
0043: // TODO: make painting more efficient (i.e. when down arrow is pressed, only two items should need to be repainted.
0044:
0045: public class XChoicePeer extends XComponentPeer implements ChoicePeer,
0046: ToplevelStateListener {
0047: private static final Logger log = Logger
0048: .getLogger("sun.awt.X11.XChoicePeer");
0049:
0050: private static final int MAX_UNFURLED_ITEMS = 10; // Maximum number of
0051: // items to be displayed
0052: // at a time in an
0053: // unfurled Choice
0054: // Description of these constants in ListHelper
0055: public final static int TEXT_SPACE = 1;
0056: public final static int BORDER_WIDTH = 1;
0057: public final static int ITEM_MARGIN = 1;
0058: public final static int SCROLLBAR_WIDTH = 15;
0059:
0060: // SHARE THESE!
0061: private static final Insets focusInsets = new Insets(0, 0, 0, 0);
0062:
0063: static final int WIDGET_OFFSET = 18;
0064:
0065: // Stolen from Tiny
0066: static final int TEXT_XPAD = 8;
0067: static final int TEXT_YPAD = 6;
0068:
0069: // FIXME: Motif uses a different focus color for the item within
0070: // the unfurled Choice list and for when the Choice itself is focused and
0071: // popped up.
0072: static final Color focusColor = Color.black;
0073:
0074: // TODO: there is a time value that the mouse is held down. If short
0075: // enough, the Choice stays popped down. If long enough, Choice
0076: // is furled when the mouse is released
0077:
0078: private boolean unfurled = false; // Choice list is popped down
0079:
0080: private boolean dragging = false; // Mouse was pressed and is being
0081: // dragged over the (unfurled)
0082: // Choice
0083:
0084: private boolean mouseInSB = false; // Mouse is interacting with the
0085: // scrollbar
0086:
0087: private boolean firstPress = false; // mouse was pressed on
0088: // furled Choice so we
0089: // not need to furl the
0090: // Choice when MOUSE_RELEASED occured
0091:
0092: // 6425067. Mouse was pressed on furled choice and dropdown list appeared over Choice itself
0093: // and then there were no mouse movements until MOUSE_RELEASE.
0094: // This scenario leads to ItemStateChanged as the choice logic uses
0095: // MouseReleased event to send ItemStateChanged. To prevent it we should
0096: // use a combination of firstPress and wasDragged variables.
0097: // The only difference in dragging and wasDragged is: last one will not
0098: // set to false on mouse ungrab. It become false after MouseRelased() finishes.
0099: private boolean wasDragged = false;
0100: private ListHelper helper;
0101: private UnfurledChoice unfurledChoice;
0102:
0103: // TODO: Choice remembers where it was scrolled to when unfurled - it's not
0104: // always to the currently selected item.
0105:
0106: // Indicates whether or not to paint selected item in the choice.
0107: // Default is to paint
0108: private boolean drawSelectedItem = true;
0109:
0110: // If set, indicates components under which choice popup should be showed.
0111: // The choice's popup width and location should be adjust to appear
0112: // under both choice and alignUnder component.
0113: private Component alignUnder;
0114:
0115: // If cursor is outside of an unfurled Choice when the mouse is
0116: // released, Choice item should NOT be updated. Remember the proper index.
0117: private int dragStartIdx = -1;
0118:
0119: // Holds the listener (XFileDialogPeer) which the processing events from the choice
0120: // See 6240074 for more information
0121: private XChoicePeerListener choiceListener;
0122:
0123: XChoicePeer(Choice target) {
0124: super (target);
0125: }
0126:
0127: void preInit(XCreateWindowParams params) {
0128: super .preInit(params);
0129: Choice target = (Choice) this .target;
0130: int numItems = target.getItemCount();
0131: unfurledChoice = new UnfurledChoice(target);
0132: getToplevelXWindow().addToplevelStateListener(this );
0133: helper = new ListHelper(unfurledChoice, getGUIcolors(),
0134: numItems, false, true, false, target.getFont(),
0135: MAX_UNFURLED_ITEMS, TEXT_SPACE, ITEM_MARGIN,
0136: BORDER_WIDTH, SCROLLBAR_WIDTH);
0137: }
0138:
0139: void postInit(XCreateWindowParams params) {
0140: super .postInit(params);
0141: Choice target = (Choice) this .target;
0142: int numItems = target.getItemCount();
0143:
0144: // Add all items
0145: for (int i = 0; i < numItems; i++) {
0146: helper.add(target.getItem(i));
0147: }
0148: if (!helper.isEmpty()) {
0149: helper.select(target.getSelectedIndex());
0150: helper.setFocusedIndex(target.getSelectedIndex());
0151: }
0152: helper.updateColors(getGUIcolors());
0153: updateMotifColors(getPeerBackground());
0154: }
0155:
0156: public boolean isFocusable() {
0157: return true;
0158: }
0159:
0160: // 6399679. check if super.setBounds() actually changes the size of the
0161: // component and then compare current Choice size with a new one. If
0162: // they differs then hide dropdown menu
0163: public void setBounds(int x, int y, int width, int height, int op) {
0164: int oldX = this .x;
0165: int oldY = this .y;
0166: int oldWidth = this .width;
0167: int oldHeight = this .height;
0168: super .setBounds(x, y, width, height, op);
0169: if (unfurled
0170: && (oldX != this .x || oldY != this .y
0171: || oldWidth != this .width || oldHeight != this .height)) {
0172: hidePopdownMenu();
0173: }
0174: }
0175:
0176: public void focusGained(FocusEvent e) {
0177: // TODO: only need to paint the focus bit
0178: super .focusGained(e);
0179: repaint();
0180: }
0181:
0182: /*
0183: * Fix for 6246503 : Disabling a choice after selection locks keyboard, mouse and makes the system unusable, Xtoolkit
0184: * if setEnabled(false) invoked we should close opened choice in
0185: * order to prevent keyboard/mouse lock.
0186: */
0187: public void setEnabled(boolean value) {
0188: super .setEnabled(value);
0189: helper.updateColors(getGUIcolors());
0190: if (!value && unfurled) {
0191: hidePopdownMenu();
0192: }
0193: }
0194:
0195: public void focusLost(FocusEvent e) {
0196: // TODO: only need to paint the focus bit?
0197: super .focusLost(e);
0198: repaint();
0199: }
0200:
0201: void ungrabInputImpl() {
0202: if (unfurled) {
0203: unfurled = false;
0204: dragging = false;
0205: mouseInSB = false;
0206: unfurledChoice.setVisible(false);
0207: }
0208:
0209: super .ungrabInputImpl();
0210: }
0211:
0212: void handleJavaKeyEvent(KeyEvent e) {
0213: if (e.getID() == KeyEvent.KEY_PRESSED) {
0214: keyPressed(e);
0215: }
0216: }
0217:
0218: public void keyPressed(KeyEvent e) {
0219: switch (e.getKeyCode()) {
0220: // UP & DOWN are same if furled or unfurled
0221: case KeyEvent.VK_DOWN:
0222: case KeyEvent.VK_KP_DOWN: {
0223: if (helper.getItemCount() > 1) {
0224: helper.down();
0225: int newIdx = helper.getSelectedIndex();
0226:
0227: ((Choice) target).select(newIdx);
0228: postEvent(new ItemEvent((Choice) target,
0229: ItemEvent.ITEM_STATE_CHANGED, ((Choice) target)
0230: .getItem(newIdx), ItemEvent.SELECTED));
0231: repaint();
0232: }
0233: break;
0234: }
0235: case KeyEvent.VK_UP:
0236: case KeyEvent.VK_KP_UP: {
0237: if (helper.getItemCount() > 1) {
0238: helper.up();
0239: int newIdx = helper.getSelectedIndex();
0240:
0241: ((Choice) target).select(newIdx);
0242: postEvent(new ItemEvent((Choice) target,
0243: ItemEvent.ITEM_STATE_CHANGED, ((Choice) target)
0244: .getItem(newIdx), ItemEvent.SELECTED));
0245: repaint();
0246: }
0247: break;
0248: }
0249: case KeyEvent.VK_PAGE_DOWN:
0250: if (unfurled && !dragging) {
0251: int oldIdx = helper.getSelectedIndex();
0252: helper.pageDown();
0253: int newIdx = helper.getSelectedIndex();
0254: if (oldIdx != newIdx) {
0255: ((Choice) target).select(newIdx);
0256: postEvent(new ItemEvent((Choice) target,
0257: ItemEvent.ITEM_STATE_CHANGED,
0258: ((Choice) target).getItem(newIdx),
0259: ItemEvent.SELECTED));
0260: repaint();
0261: }
0262: }
0263: break;
0264: case KeyEvent.VK_PAGE_UP:
0265: if (unfurled && !dragging) {
0266: int oldIdx = helper.getSelectedIndex();
0267: helper.pageUp();
0268: int newIdx = helper.getSelectedIndex();
0269: if (oldIdx != newIdx) {
0270: ((Choice) target).select(newIdx);
0271: postEvent(new ItemEvent((Choice) target,
0272: ItemEvent.ITEM_STATE_CHANGED,
0273: ((Choice) target).getItem(newIdx),
0274: ItemEvent.SELECTED));
0275: repaint();
0276: }
0277: }
0278: break;
0279: case KeyEvent.VK_ESCAPE:
0280: case KeyEvent.VK_ENTER:
0281: if (unfurled) {
0282: if (dragging) {
0283: if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
0284: //This also happens on
0285: // - MouseButton2,3, etc. press
0286: // - ENTER press
0287: helper.select(dragStartIdx);
0288: } else { //KeyEvent.VK_ENTER:
0289: int newIdx = helper.getSelectedIndex();
0290: ((Choice) target).select(newIdx);
0291: postEvent(new ItemEvent((Choice) target,
0292: ItemEvent.ITEM_STATE_CHANGED,
0293: ((Choice) target).getItem(newIdx),
0294: ItemEvent.SELECTED));
0295: }
0296: }
0297: hidePopdownMenu();
0298: dragging = false;
0299: wasDragged = false;
0300: mouseInSB = false;
0301:
0302: // See 6240074 for more information
0303: if (choiceListener != null) {
0304: choiceListener.unfurledChoiceClosing();
0305: }
0306: }
0307: break;
0308: default:
0309: if (unfurled) {
0310: Toolkit.getDefaultToolkit().beep();
0311: }
0312: break;
0313: }
0314: }
0315:
0316: public boolean handlesWheelScrolling() {
0317: return true;
0318: }
0319:
0320: void handleJavaMouseWheelEvent(MouseWheelEvent e) {
0321: if (unfurled && helper.isVSBVisible()) {
0322: if (ListHelper.doWheelScroll(helper.getVSB(), null, e)) {
0323: repaint();
0324: }
0325: }
0326: }
0327:
0328: void handleJavaMouseEvent(MouseEvent e) {
0329: super .handleJavaMouseEvent(e);
0330: int i = e.getID();
0331: switch (i) {
0332: case MouseEvent.MOUSE_PRESSED:
0333: mousePressed(e);
0334: break;
0335: case MouseEvent.MOUSE_RELEASED:
0336: mouseReleased(e);
0337: break;
0338: case MouseEvent.MOUSE_DRAGGED:
0339: mouseDragged(e);
0340: break;
0341: }
0342: }
0343:
0344: public void mousePressed(MouseEvent e) {
0345: /*
0346: * fix for 5003166: a Choice on XAWT shouldn't react to any
0347: * mouse button presses except left. This involves presses on
0348: * Choice but not on opened part of choice.
0349: */
0350: if (e.getButton() == MouseEvent.BUTTON1) {
0351: dragStartIdx = helper.getSelectedIndex();
0352: if (unfurled) {
0353: //fix 6259328: PIT: Choice scrolls when dragging the parent frame while drop-down is active, XToolkit
0354: if (!(isMouseEventInChoice(e) || unfurledChoice
0355: .isMouseEventInside(e))) {
0356: hidePopdownMenu();
0357: }
0358: // Press on unfurled Choice. Highlight the item under the cursor,
0359: // but don't send item event or set the text on the button yet
0360: unfurledChoice.trackMouse(e);
0361: } else {
0362: // Choice is up - unfurl it
0363: grabInput();
0364: unfurledChoice.toFront();
0365: firstPress = true;
0366: wasDragged = false;
0367: unfurled = true;
0368: }
0369: }
0370: }
0371:
0372: /*
0373: * helper method for mouseReleased routine
0374: */
0375: void hidePopdownMenu() {
0376: ungrabInput();
0377: unfurledChoice.setVisible(false);
0378: unfurled = false;
0379: }
0380:
0381: public void mouseReleased(MouseEvent e) {
0382: if (unfurled) {
0383: if (mouseInSB) {
0384: unfurledChoice.trackMouse(e);
0385: } else {
0386: // We pressed and dragged onto the Choice, or, this is the
0387: // second release after clicking to make the Choice "stick"
0388: // unfurled.
0389: // This release should ungrab/furl, and set the new item if
0390: // release was over the unfurled Choice.
0391:
0392: // Fix for 6239944 : Choice shouldn't close its
0393: // pop-down menu if user presses Mouse on Choice's Scrollbar
0394: // some additional cases like releasing mouse outside
0395: // of Choice are considered too
0396: boolean isMouseEventInside = unfurledChoice
0397: .isMouseEventInside(e);
0398: boolean isMouseInListArea = unfurledChoice
0399: .isMouseInListArea(e);
0400:
0401: // Fixed 6318746: REG: File Selection is failing
0402: // We shouldn't restore the selected item
0403: // if the mouse was dragged outside the drop-down choice area
0404: if (!helper.isEmpty() && !isMouseInListArea && dragging) {
0405: // Set the selected item back how it was.
0406: ((Choice) target).select(dragStartIdx);
0407: }
0408:
0409: // Choice must be closed if user releases mouse on
0410: // pop-down menu on the second click
0411: if (!firstPress && isMouseInListArea) {
0412: hidePopdownMenu();
0413: }
0414: // Choice must be closed if user releases mouse
0415: // outside of Choice's pop-down menu on the second click
0416: if (!firstPress && !isMouseEventInside) {
0417: hidePopdownMenu();
0418: }
0419: //if user drags Mouse on pop-down menu, Scrollbar or
0420: // outside the Choice
0421: if (firstPress && dragging) {
0422: hidePopdownMenu();
0423: }
0424: /* this could happen when user has opened a Choice and
0425: * released mouse button. Then he drags mouse on the
0426: * Scrollbar and releases mouse again.
0427: */
0428: if (!firstPress && !isMouseInListArea
0429: && isMouseEventInside && dragging) {
0430: hidePopdownMenu();
0431: }
0432:
0433: if (!helper.isEmpty()) {
0434: // Only update the Choice if the mouse button is released
0435: // over the list of items.
0436: if (unfurledChoice.isMouseInListArea(e)) {
0437: int newIdx = helper.getSelectedIndex();
0438: if (newIdx >= 0) {
0439: // Update the selected item in the target now that
0440: // the mouse selection is complete.
0441: if (newIdx != dragStartIdx) {
0442: ((Choice) target).select(newIdx);
0443: // NOTE: We get a repaint when Choice.select()
0444: // calls our peer.select().
0445: }
0446: if (wasDragged
0447: && e.getButton() != MouseEvent.BUTTON1) {
0448: ((Choice) target).select(dragStartIdx);
0449: }
0450:
0451: /*fix for 6239941 : Choice triggers ItemEvent when selecting an item with right mouse button, Xtoolkit
0452: * We should generate ItemEvent if only
0453: * LeftMouseButton used */
0454: if (e.getButton() == MouseEvent.BUTTON1
0455: && (!firstPress || wasDragged)) {
0456: postEvent(new ItemEvent(
0457: (Choice) target,
0458: ItemEvent.ITEM_STATE_CHANGED,
0459: ((Choice) target)
0460: .getItem(newIdx),
0461: ItemEvent.SELECTED));
0462: }
0463:
0464: // see 6240074 for more information
0465: if (choiceListener != null) {
0466: choiceListener.unfurledChoiceClosing();
0467: }
0468: }
0469: }
0470: }
0471: // See 6243382 for more information
0472: unfurledChoice.trackMouse(e);
0473: }
0474: }
0475:
0476: dragging = false;
0477: wasDragged = false;
0478: firstPress = false;
0479: dragStartIdx = -1;
0480: }
0481:
0482: public void mouseDragged(MouseEvent e) {
0483: /*
0484: * fix for 5003166. On Motif user are unable to drag
0485: * mouse inside opened Choice if he drags the mouse with
0486: * different from LEFT mouse button ( e.g. RIGHT or MIDDLE).
0487: * This fix make impossible to drag mouse inside opened choice
0488: * with other mouse buttons rather then LEFT one.
0489: */
0490: if (e.getModifiers() == MouseEvent.BUTTON1_MASK) {
0491: dragging = true;
0492: wasDragged = true;
0493: unfurledChoice.trackMouse(e);
0494: }
0495: }
0496:
0497: // Stolen from TinyChoicePeer
0498: public Dimension getMinimumSize() {
0499: // TODO: move this impl into ListHelper?
0500: FontMetrics fm = getFontMetrics(target.getFont());
0501: Choice c = (Choice) target;
0502: int w = 0;
0503: for (int i = c.countItems(); i-- > 0;) {
0504: w = Math.max(fm.stringWidth(c.getItem(i)), w);
0505: }
0506: return new Dimension(w + TEXT_XPAD + WIDGET_OFFSET, fm
0507: .getMaxAscent()
0508: + fm.getMaxDescent() + TEXT_YPAD);
0509: }
0510:
0511: /*
0512: * Layout the...
0513: */
0514: public void layout() {
0515: /*
0516: Dimension size = target.getSize();
0517: Font f = target.getFont();
0518: FontMetrics fm = target.getFontMetrics(f);
0519: String text = ((Choice)target).getLabel();
0520:
0521: textRect.height = fm.getHeight();
0522:
0523: checkBoxSize = getChoiceSize(fm);
0524:
0525: // Note - Motif appears to use an left inset that is slightly
0526: // scaled to the checkbox/font size.
0527: cbX = borderInsets.left + checkBoxInsetFromText;
0528: cbY = size.height / 2 - checkBoxSize / 2;
0529: int minTextX = borderInsets.left + 2 * checkBoxInsetFromText + checkBoxSize;
0530: // FIXME: will need to account for alignment?
0531: // FIXME: call layout() on alignment changes
0532: //textRect.width = fm.stringWidth(text);
0533: textRect.width = fm.stringWidth(text == null ? "" : text);
0534: textRect.x = Math.max(minTextX, size.width / 2 - textRect.width / 2);
0535: textRect.y = size.height / 2 - textRect.height / 2 + borderInsets.top;
0536:
0537: focusRect.x = focusInsets.left;
0538: focusRect.y = focusInsets.top;
0539: focusRect.width = size.width-(focusInsets.left+focusInsets.right)-1;
0540: focusRect.height = size.height-(focusInsets.top+focusInsets.bottom)-1;
0541:
0542: myCheckMark = AffineTransform.getScaleInstance((double)target.getFont().getSize() / MASTER_SIZE, (double)target.getFont().getSize() / MASTER_SIZE).createTransformedShape(MASTER_CHECKMARK);
0543: */
0544:
0545: }
0546:
0547: /**
0548: * Paint the choice
0549: */
0550: public void paint(Graphics g) {
0551: flush();
0552: Dimension size = getPeerSize();
0553:
0554: // TODO: when mouse is down over button, widget should be drawn depressed
0555: g.setColor(getPeerBackground());
0556: g.fillRect(0, 0, width, height);
0557:
0558: drawMotif3DRect(g, 1, 1, width - 2, height - 2, false);
0559: drawMotif3DRect(g, width - WIDGET_OFFSET, (height / 2) - 3, 12,
0560: 6, false);
0561:
0562: if (!helper.isEmpty() && helper.getSelectedIndex() != -1) {
0563: g.setFont(getPeerFont());
0564: FontMetrics fm = g.getFontMetrics();
0565: String lbl = helper.getItem(helper.getSelectedIndex());
0566: if (lbl != null && drawSelectedItem) {
0567: g.setClip(1, 1, width - WIDGET_OFFSET - 2, height);
0568: if (isEnabled()) {
0569: g.setColor(getPeerForeground());
0570: g.drawString(lbl, 5,
0571: (height + fm.getMaxAscent() - fm
0572: .getMaxDescent()) / 2);
0573: } else {
0574: g.setColor(getPeerBackground().brighter());
0575: g.drawString(lbl, 5,
0576: (height + fm.getMaxAscent() - fm
0577: .getMaxDescent()) / 2);
0578: g.setColor(getPeerBackground().darker());
0579: g.drawString(lbl, 4,
0580: ((height + fm.getMaxAscent() - fm
0581: .getMaxDescent()) / 2) - 1);
0582: }
0583: g.setClip(0, 0, width, height);
0584: }
0585: }
0586: if (hasFocus()) {
0587: paintFocus(g, focusInsets.left, focusInsets.top, size.width
0588: - (focusInsets.left + focusInsets.right) - 1,
0589: size.height
0590: - (focusInsets.top + focusInsets.bottom)
0591: - 1);
0592: }
0593: if (unfurled) {
0594: unfurledChoice.repaint();
0595: }
0596: flush();
0597: }
0598:
0599: protected void paintFocus(Graphics g, int x, int y, int w, int h) {
0600: g.setColor(focusColor);
0601: g.drawRect(x, y, w, h);
0602: }
0603:
0604: /*
0605: * ChoicePeer methods stolen from TinyChoicePeer
0606: */
0607:
0608: public void select(int index) {
0609: helper.select(index);
0610: helper.setFocusedIndex(index);
0611: repaint();
0612: }
0613:
0614: public void add(String item, int index) {
0615: helper.add(item, index);
0616: repaint();
0617: }
0618:
0619: public void remove(int index) {
0620: boolean selected = (index == helper.getSelectedIndex());
0621: boolean visibled = (index >= helper.firstDisplayedIndex() && index <= helper
0622: .lastDisplayedIndex());
0623: helper.remove(index);
0624: if (selected) {
0625: if (helper.isEmpty()) {
0626: helper.select(-1);
0627: } else {
0628: helper.select(0);
0629: }
0630: }
0631: /*
0632: * Fix for 6248016
0633: * After removing the item of the choice we need to reshape unfurled choice
0634: * in order to keep actual bounds of the choice
0635: */
0636:
0637: /*
0638: * condition added only for performance
0639: */
0640: if (!unfurled) {
0641: // Fix 6292186: PIT: Choice is not refreshed properly when the last item gets removed, XToolkit
0642: // We should take into account that there is no 'select' invoking (hence 'repaint')
0643: // if the choice is empty (see Choice.java method removeNoInvalidate())
0644: // The condition isn't 'visibled' since it would be cause of the twice repainting
0645: if (helper.isEmpty()) {
0646: repaint();
0647: }
0648: return;
0649: }
0650:
0651: /*
0652: * condition added only for performance
0653: * the count of the visible items changed
0654: */
0655: if (visibled) {
0656: Rectangle r = unfurledChoice.placeOnScreen();
0657: unfurledChoice.reshape(r.x, r.y, r.width, r.height);
0658: return;
0659: }
0660:
0661: /*
0662: * condition added only for performance
0663: * the structure of visible items changed
0664: * if removable item is non visible and non selected then there is no repaint
0665: */
0666: if (visibled || selected) {
0667: repaint();
0668: }
0669: }
0670:
0671: public void removeAll() {
0672: helper.removeAll();
0673: helper.select(-1);
0674: /*
0675: * Fix for 6248016
0676: * After removing the item of the choice we need to reshape unfurled choice
0677: * in order to keep actual bounds of the choice
0678: */
0679: Rectangle r = unfurledChoice.placeOnScreen();
0680: unfurledChoice.reshape(r.x, r.y, r.width, r.height);
0681: repaint();
0682: }
0683:
0684: /**
0685: * DEPRECATED: Replaced by add(String, int).
0686: */
0687: public void addItem(String item, int index) {
0688: add(item, index);
0689: }
0690:
0691: public void setFont(Font font) {
0692: super .setFont(font);
0693: helper.setFont(this .font);
0694: }
0695:
0696: public void setForeground(Color c) {
0697: super .setForeground(c);
0698: helper.updateColors(getGUIcolors());
0699: }
0700:
0701: public void setBackground(Color c) {
0702: super .setBackground(c);
0703: unfurledChoice.setBackground(c);
0704: helper.updateColors(getGUIcolors());
0705: updateMotifColors(c);
0706: }
0707:
0708: public void setDrawSelectedItem(boolean value) {
0709: drawSelectedItem = value;
0710: }
0711:
0712: public void setAlignUnder(Component comp) {
0713: alignUnder = comp;
0714: }
0715:
0716: // see 6240074 for more information
0717: public void addXChoicePeerListener(XChoicePeerListener l) {
0718: choiceListener = l;
0719: }
0720:
0721: // see 6240074 for more information
0722: public void removeXChoicePeerListener() {
0723: choiceListener = null;
0724: }
0725:
0726: public boolean isUnfurled() {
0727: return unfurled;
0728: }
0729:
0730: /* fix for 6261352. We should detect if current parent Window (containing a Choice) become iconified and hide pop-down menu with grab release.
0731: * In this case we should hide pop-down menu.
0732: */
0733: //calls from XWindowPeer. Could accept X-styled state events
0734: public void stateChangedICCCM(int oldState, int newState) {
0735: if (unfurled && oldState != newState) {
0736: hidePopdownMenu();
0737: }
0738: }
0739:
0740: //calls from XFramePeer. Could accept Frame's states.
0741: public void stateChangedJava(int oldState, int newState) {
0742: if (unfurled && oldState != newState) {
0743: hidePopdownMenu();
0744: }
0745: }
0746:
0747: /**************************************************************************/
0748: /* Common functionality between List & Choice
0749: /**************************************************************************/
0750:
0751: /**
0752: * Inner class for the unfurled Choice list
0753: * Much, much more docs
0754: */
0755: class UnfurledChoice extends XWindow /*implements XScrollbarClient*/{
0756:
0757: // First try - use Choice as the target
0758:
0759: public UnfurledChoice(Component target) {
0760: super (target);
0761: }
0762:
0763: // Override so we can do our own create()
0764: public void preInit(XCreateWindowParams params) {
0765: // A parent of this window is the target, at this point: wrong.
0766: // Remove parent window; in the following preInit() call we'll calculate as a default
0767: // a correct root window which is the proper parent for override redirect.
0768: params.delete(PARENT_WINDOW);
0769: super .preInit(params);
0770: // Reset bounds(we'll set them later), set overrideRedirect
0771: params.remove(BOUNDS);
0772: params.add(OVERRIDE_REDIRECT, Boolean.TRUE);
0773: }
0774:
0775: // Generally, bounds should be:
0776: // x = target.x
0777: // y = target.y + target.height
0778: // w = Max(target.width, getLongestItemWidth) + possible vertScrollbar
0779: // h = Min(MAX_UNFURLED_ITEMS, target.getItemCount()) * itemHeight
0780: Rectangle placeOnScreen() {
0781: int numItemsDisplayed;
0782: // Motif paints an empty Choice the same size as a single item
0783: if (helper.isEmpty()) {
0784: numItemsDisplayed = 1;
0785: } else {
0786: int numItems = helper.getItemCount();
0787: numItemsDisplayed = Math.min(MAX_UNFURLED_ITEMS,
0788: numItems);
0789: }
0790: Point global = XChoicePeer.this .toGlobal(0, 0);
0791: Dimension screen = Toolkit.getDefaultToolkit()
0792: .getScreenSize();
0793:
0794: if (alignUnder != null) {
0795: Rectangle choiceRec = XChoicePeer.this .getBounds();
0796: choiceRec.setLocation(0, 0);
0797: choiceRec = XChoicePeer.this .toGlobal(choiceRec);
0798: Rectangle alignUnderRec = new Rectangle(alignUnder
0799: .getLocationOnScreen(), alignUnder.getSize()); // TODO: Security?
0800: Rectangle result = choiceRec.union(alignUnderRec);
0801: // we've got the left and width, calculate top and height
0802: width = result.width;
0803: x = result.x;
0804: y = result.y + result.height;
0805: height = 2 * BORDER_WIDTH + numItemsDisplayed
0806: * (helper.getItemHeight() + 2 * ITEM_MARGIN);
0807: } else {
0808: x = global.x;
0809: y = global.y + XChoicePeer.this .height;
0810: width = Math
0811: .max(
0812: XChoicePeer.this .width,
0813: helper.getMaxItemWidth()
0814: + 2
0815: * (BORDER_WIDTH + ITEM_MARGIN + TEXT_SPACE)
0816: + (helper.isVSBVisible() ? SCROLLBAR_WIDTH
0817: : 0));
0818: height = 2 * BORDER_WIDTH + numItemsDisplayed
0819: * (helper.getItemHeight() + 2 * ITEM_MARGIN);
0820: }
0821: // Don't run off the edge of the screen
0822: if (x < 0) {
0823: x = 0;
0824: } else if (x + width > screen.width) {
0825: x = screen.width - width;
0826: }
0827:
0828: if (y < 0) {
0829: y = 0;
0830: } else if (y + height > screen.height) {
0831: y = screen.height - height;
0832: }
0833: return new Rectangle(x, y, width, height);
0834: }
0835:
0836: public void toFront() {
0837: // see 6240074 for more information
0838: if (choiceListener != null)
0839: choiceListener.unfurledChoiceOpening(helper);
0840:
0841: Rectangle r = placeOnScreen();
0842: reshape(r.x, r.y, r.width, r.height);
0843: super .toFront();
0844: setVisible(true);
0845: }
0846:
0847: /*
0848: * Track a MouseEvent (either a drag or a press) and paint a new
0849: * selected item, if necessary.
0850: */
0851: // FIXME: first unfurl after move is not at edge of the screen onto second monitor doesn't
0852: // track mouse correctly. Problem is w/ UnfurledChoice coords
0853: public void trackMouse(MouseEvent e) {
0854: // Event coords are relative to the button, so translate a bit
0855: Point local = toLocalCoords(e);
0856:
0857: // If x,y is over unfurled Choice,
0858: // highlight item under cursor
0859:
0860: switch (e.getID()) {
0861: case MouseEvent.MOUSE_PRESSED:
0862: // FIXME: If the Choice is unfurled and the mouse is pressed
0863: // outside of the Choice, the mouse should ungrab on the
0864: // the press, not the release
0865: if (helper.isInVertSB(getBounds(), local.x, local.y)) {
0866: mouseInSB = true;
0867: helper.handleVSBEvent(e, getBounds(), local.x,
0868: local.y);
0869: } else {
0870: trackSelection(local.x, local.y);
0871: }
0872: break;
0873: case MouseEvent.MOUSE_RELEASED:
0874: if (mouseInSB) {
0875: mouseInSB = false;
0876: helper.handleVSBEvent(e, getBounds(), local.x,
0877: local.y);
0878: } else {
0879: // See 6243382 for more information
0880: helper.trackMouseReleasedScroll();
0881: }
0882: /*
0883: else {
0884: trackSelection(local.x, local.y);
0885: }
0886: */
0887: break;
0888: case MouseEvent.MOUSE_DRAGGED:
0889: if (mouseInSB) {
0890: helper.handleVSBEvent(e, getBounds(), local.x,
0891: local.y);
0892: } else {
0893: // See 6243382 for more information
0894: helper.trackMouseDraggedScroll(local.x, local.y,
0895: width, height);
0896: trackSelection(local.x, local.y);
0897: }
0898: break;
0899: }
0900: }
0901:
0902: private void trackSelection(int transX, int transY) {
0903: if (!helper.isEmpty()) {
0904: if (transX > 0 && transX < width && transY > 0
0905: && transY < height) {
0906: int newIdx = helper.y2index(transY);
0907: if (log.isLoggable(Level.FINE)) {
0908: log.fine("transX=" + transX + ", transY="
0909: + transY + ",width=" + width
0910: + ", height=" + height + ", newIdx="
0911: + newIdx + " on " + target);
0912: }
0913: if ((newIdx >= 0)
0914: && (newIdx < helper.getItemCount())
0915: && (newIdx != helper.getSelectedIndex())) {
0916: helper.select(newIdx);
0917: unfurledChoice.repaint();
0918: }
0919: }
0920: }
0921: // FIXME: If dragged off top or bottom, scroll if there's a vsb
0922: // (ICK - we'll need a timer or our own event or something)
0923: }
0924:
0925: /*
0926: * fillRect with current Background color on the whole dropdown list.
0927: */
0928: public void paintBackground() {
0929: Graphics g = getGraphics();
0930: g.setColor(getPeerBackground());
0931: g.fillRect(0, 0, width, height);
0932: }
0933:
0934: /*
0935: * 6405689. In some cases we should erase background to eliminate painting
0936: * artefacts.
0937: */
0938: public void repaint() {
0939: if (!isVisible()) {
0940: return;
0941: }
0942: if (helper.checkVsbVisibilityChangedAndReset()) {
0943: paintBackground();
0944: }
0945: super .repaint();
0946: }
0947:
0948: public void paint(Graphics g) {
0949: //System.out.println("UC.paint()");
0950: Choice choice = (Choice) target;
0951: Color colors[] = XChoicePeer.this .getGUIcolors();
0952: draw3DRect(g, getSystemColors(), 0, 0, width - 1,
0953: height - 1, true);
0954: draw3DRect(g, getSystemColors(), 1, 1, width - 3,
0955: height - 3, true);
0956:
0957: helper.paintAllItems(g, colors, getBounds());
0958: }
0959:
0960: public void setVisible(boolean vis) {
0961: xSetVisible(vis);
0962:
0963: if (!vis && alignUnder != null) {
0964: alignUnder.requestFocusInWindow();
0965: }
0966: }
0967:
0968: /**
0969: * Return a MouseEvent's Point in coordinates relative to the
0970: * UnfurledChoice.
0971: */
0972: private Point toLocalCoords(MouseEvent e) {
0973: // Event coords are relative to the button, so translate a bit
0974: Point global = XChoicePeer.this
0975: .toGlobal(e.getX(), e.getY());
0976:
0977: global.x -= x;
0978: global.y -= y;
0979: return global;
0980: }
0981:
0982: /* Returns true if the MouseEvent coords (which are based on the Choice)
0983: * are inside of the UnfurledChoice.
0984: */
0985: private boolean isMouseEventInside(MouseEvent e) {
0986: Point local = toLocalCoords(e);
0987: if (local.x > 0 && local.x < width && local.y > 0
0988: && local.y < height) {
0989: return true;
0990: }
0991: return false;
0992: }
0993:
0994: /**
0995: * Tests if the mouse cursor is in the Unfurled Choice, yet not
0996: * in the vertical scrollbar
0997: */
0998: private boolean isMouseInListArea(MouseEvent e) {
0999: if (isMouseEventInside(e)) {
1000: Point local = toLocalCoords(e);
1001: Rectangle bounds = getBounds();
1002: if (!helper.isInVertSB(bounds, local.x, local.y)) {
1003: return true;
1004: }
1005: }
1006: return false;
1007: }
1008:
1009: /*
1010: * Overridden from XWindow() because we don't want to send
1011: * ComponentEvents
1012: */
1013: public void handleConfigureNotifyEvent(XEvent xev) {
1014: }
1015:
1016: public void handleMapNotifyEvent(XEvent xev) {
1017: }
1018:
1019: public void handleUnmapNotifyEvent(XEvent xev) {
1020: }
1021: } //UnfurledChoice
1022:
1023: public void dispose() {
1024: if (unfurledChoice != null) {
1025: unfurledChoice.destroy();
1026: }
1027: super .dispose();
1028: }
1029:
1030: /*
1031: * fix for 6239938 : Choice drop-down does not disappear when it loses
1032: * focus, on XToolkit
1033: * We are able to handle all _Key_ events received by Choice when
1034: * it is in opened state without sending it to EventQueue.
1035: * If Choice is in closed state we should behave like before: send
1036: * all events to EventQueue.
1037: * To be compatible with Motif we should handle all KeyEvents in
1038: * Choice if it is opened. KeyEvents should be sent into Java if Choice is not opened.
1039: */
1040: boolean prePostEvent(final AWTEvent e) {
1041: if (unfurled) {
1042: // fix for 6253211: PIT: MouseWheel events not triggered for Choice drop down in XAWT
1043: if (e instanceof MouseWheelEvent) {
1044: return super .prePostEvent(e);
1045: }
1046: //fix 6252982: PIT: Keyboard FocusTraversal not working when choice's drop-down is visible, on XToolkit
1047: if (e instanceof KeyEvent) {
1048: // notify XWindow that this event had been already handled and no need to post it again
1049: EventQueue.invokeLater(new Runnable() {
1050: public void run() {
1051: if (target.isFocusable()
1052: && getParentTopLevel()
1053: .isFocusableWindow()) {
1054: handleJavaKeyEvent((KeyEvent) e);
1055: }
1056: }
1057: });
1058: return true;
1059: } else {
1060: if (e instanceof MouseEvent) {
1061: // Fix for 6240046 : REG:Choice's Drop-down does not disappear when clicking somewhere, after popup menu is disposed
1062: // if user presses Right Mouse Button on opened (unfurled)
1063: // Choice then we mustn't open a popup menu. We could filter
1064: // Mouse Events and handle them in XChoicePeer if Choice
1065: // currently in opened state.
1066: MouseEvent me = (MouseEvent) e;
1067: int eventId = e.getID();
1068: // fix 6251983: PIT: MouseDragged events not triggered
1069: // fix 6251988: PIT: Choice consumes MouseReleased, MouseClicked events when clicking it with left button,
1070: if ((unfurledChoice.isMouseEventInside(me) || (!firstPress && eventId == MouseEvent.MOUSE_DRAGGED))) {
1071: return handleMouseEventByChoice(me);
1072: }
1073: // MouseMoved events should be fired in Choice's comp if it's not opened
1074: // Shouldn't generate Moved Events. CR : 6251995
1075: if (eventId == MouseEvent.MOUSE_MOVED) {
1076: return handleMouseEventByChoice(me);
1077: }
1078: //fix for 6272965: PIT: Choice triggers MousePressed when pressing mouse outside comp while drop-down is active, XTkt
1079: if (!firstPress
1080: && !(isMouseEventInChoice(me) || unfurledChoice
1081: .isMouseEventInside(me))
1082: && (eventId == MouseEvent.MOUSE_PRESSED
1083: || eventId == MouseEvent.MOUSE_RELEASED || eventId == MouseEvent.MOUSE_CLICKED)) {
1084: return handleMouseEventByChoice(me);
1085: }
1086: }
1087: }//else KeyEvent
1088: }//if unfurled
1089: return super .prePostEvent(e);
1090: }
1091:
1092: //convenient method
1093: //do not generate this kind of Events
1094: public boolean handleMouseEventByChoice(final MouseEvent me) {
1095: EventQueue.invokeLater(new Runnable() {
1096: public void run() {
1097: handleJavaMouseEvent(me);
1098: }
1099: });
1100: return true;
1101: }
1102:
1103: /* Returns true if the MouseEvent coords
1104: * are inside of the Choice itself (it doesnt's depends on
1105: * if this choice opened or not).
1106: */
1107: private boolean isMouseEventInChoice(MouseEvent e) {
1108: int x = e.getX();
1109: int y = e.getY();
1110: Rectangle choiceRect = getBounds();
1111:
1112: if (x < 0 || x > choiceRect.width || y < 0
1113: || y > choiceRect.height) {
1114: return false;
1115: }
1116: return true;
1117: }
1118: }
1119:
1120: /*
1121: * The listener interface for receiving "interesting" for XFileDialogPeer
1122: * choice events (opening, closing).
1123: * The listener added by means of the method addXChoicePeerListener
1124: * A opening choice event is generated when the invoking unfurledChoice.toFront()
1125: * A closing choice event is generated at the time of the processing the mouse releasing
1126: * and the Enter pressing.
1127: * see 6240074 for more information
1128: */
1129: interface XChoicePeerListener {
1130: public void unfurledChoiceOpening(ListHelper choiceHelper);
1131:
1132: public void unfurledChoiceClosing();
1133: }
|