Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaComboBoxPopup.java
38831 views
/*1* Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.awt.event.*;2930import javax.swing.*;31import javax.swing.plaf.basic.BasicComboPopup;3233import sun.lwawt.macosx.CPlatformWindow;3435class AquaComboBoxPopup extends BasicComboPopup {36static final int FOCUS_RING_PAD_LEFT = 6;37static final int FOCUS_RING_PAD_RIGHT = 6;38static final int FOCUS_RING_PAD_BOTTOM = 5;3940protected Component topStrut;41protected Component bottomStrut;42protected boolean isPopDown = false;4344public AquaComboBoxPopup(final JComboBox cBox) {45super(cBox);46}4748@Override49protected void configurePopup() {50super.configurePopup();5152setBorderPainted(false);53setBorder(null);54updateContents(false);5556// TODO: CPlatformWindow?57putClientProperty(CPlatformWindow.WINDOW_FADE_OUT, new Integer(150));58}5960public void updateContents(final boolean remove) {61// for more background on this issue, see AquaMenuBorder.getBorderInsets()6263isPopDown = isPopdown();64if (isPopDown) {65if (remove) {66if (topStrut != null) {67this.remove(topStrut);68}69if (bottomStrut != null) {70this.remove(bottomStrut);71}72} else {73add(scroller);74}75} else {76if (topStrut == null) {77topStrut = Box.createVerticalStrut(4);78bottomStrut = Box.createVerticalStrut(4);79}8081if (remove) remove(scroller);8283this.add(topStrut);84this.add(scroller);85this.add(bottomStrut);86}87}8889protected Dimension getBestPopupSizeForRowCount(final int maxRowCount) {90final int currentElementCount = comboBox.getModel().getSize();91final int rowCount = Math.min(maxRowCount, currentElementCount);9293final Dimension popupSize = new Dimension();94final ListCellRenderer renderer = list.getCellRenderer();9596for (int i = 0; i < rowCount; i++) {97final Object value = list.getModel().getElementAt(i);98final Component c = renderer.getListCellRendererComponent(list, value, i, false, false);99100final Dimension prefSize = c.getPreferredSize();101popupSize.height += prefSize.height;102popupSize.width = Math.max(prefSize.width, popupSize.width);103}104105popupSize.width += 10;106107return popupSize;108}109110protected boolean shouldScroll() {111return comboBox.getItemCount() > comboBox.getMaximumRowCount();112}113114protected boolean isPopdown() {115return shouldScroll() || AquaComboBoxUI.isPopdown(comboBox);116}117118@Override119public void show() {120final int startItemCount = comboBox.getItemCount();121122final Rectangle popupBounds = adjustPopupAndGetBounds();123if (popupBounds == null) return; // null means don't show124125comboBox.firePopupMenuWillBecomeVisible();126show(comboBox, popupBounds.x, popupBounds.y);127128// hack for <rdar://problem/4905531> JComboBox does not fire popupWillBecomeVisible if item count is 0129final int afterShowItemCount = comboBox.getItemCount();130if (afterShowItemCount == 0) {131hide();132return;133}134135if (startItemCount != afterShowItemCount) {136final Rectangle newBounds = adjustPopupAndGetBounds();137list.setSize(newBounds.width, newBounds.height);138pack();139140final Point newLoc = comboBox.getLocationOnScreen();141setLocation(newLoc.x + newBounds.x, newLoc.y + newBounds.y);142}143// end hack144145list.requestFocusInWindow();146}147148@Override149protected JList createList() {150return new JList(comboBox.getModel()) {151@Override152public void processMouseEvent(MouseEvent e) {153if (e.isMetaDown()) {154e = new MouseEvent((Component)e.getSource(), e.getID(), e.getWhen(), e.getModifiers() ^ InputEvent.META_MASK, e.getX(), e.getY(), e.getXOnScreen(), e.getYOnScreen(), e.getClickCount(), e.isPopupTrigger(), MouseEvent.NOBUTTON);155}156super.processMouseEvent(e);157}158};159}160161protected Rectangle adjustPopupAndGetBounds() {162if (isPopDown != isPopdown()) {163updateContents(true);164}165166final Dimension popupSize = getBestPopupSizeForRowCount(comboBox.getMaximumRowCount());167final Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height);168if (popupBounds == null) return null; // returning null means don't show anything169170final Dimension realPopupSize = popupBounds.getSize();171scroller.setMaximumSize(realPopupSize);172scroller.setPreferredSize(realPopupSize);173scroller.setMinimumSize(realPopupSize);174list.invalidate();175176final int selectedIndex = comboBox.getSelectedIndex();177if (selectedIndex == -1) {178list.clearSelection();179} else {180list.setSelectedIndex(selectedIndex);181}182list.ensureIndexIsVisible(list.getSelectedIndex());183184return popupBounds;185}186187// Get the bounds of the screen where the menu should appear188// p is the origin of the combo box in screen bounds189Rectangle getBestScreenBounds(final Point p) {190//System.err.println("GetBestScreenBounds p: "+ p.x + ", " + p.y);191final GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();192final GraphicsDevice[] gs = ge.getScreenDevices();193//System.err.println(" gs.length = " + gs.length);194final Rectangle comboBoxBounds = comboBox.getBounds();195if (gs.length == 1) {196final Dimension scrSize = Toolkit.getDefaultToolkit().getScreenSize();197198//System.err.println(" scrSize: "+ scrSize);199200// If the combo box is totally off screen, don't show a popup201if ((p.x + comboBoxBounds.width < 0) || (p.y + comboBoxBounds.height < 0) || (p.x > scrSize.width) || (p.y > scrSize.height)) {202return null;203}204return new Rectangle(0, 22, scrSize.width, scrSize.height - 22);205}206207for (final GraphicsDevice gd : gs) {208final GraphicsConfiguration[] gc = gd.getConfigurations();209for (final GraphicsConfiguration element0 : gc) {210final Rectangle gcBounds = element0.getBounds();211if (gcBounds.contains(p)) return gcBounds;212}213}214215// Hmm. Origin's off screen, but is any part on?216comboBoxBounds.setLocation(p);217for (final GraphicsDevice gd : gs) {218final GraphicsConfiguration[] gc = gd.getConfigurations();219for (final GraphicsConfiguration element0 : gc) {220final Rectangle gcBounds = element0.getBounds();221if (gcBounds.intersects(comboBoxBounds)) return gcBounds;222}223}224225return null;226}227228@Override229protected Rectangle computePopupBounds(int px, int py, int pw, int ph) {230final int itemCount = comboBox.getModel().getSize();231final boolean isPopdown = isPopdown();232final boolean isTableCellEditor = AquaComboBoxUI.isTableCellEditor(comboBox);233if (isPopdown && !isTableCellEditor) {234// place the popup just below the button, which is235// near the center of a large combo box236py = Math.min((py / 2) + 9, py); // if py is less than new y we have a clipped combo, so leave it alone.237}238239// px & py are relative to the combo box240241// **** Common calculation - applies to the scrolling and menu-style ****242final Point p = new Point(0, 0);243SwingUtilities.convertPointToScreen(p, comboBox);244//System.err.println("First Converting from point to screen: 0,0 is now " + p.x + ", " + p.y);245final Rectangle scrBounds = getBestScreenBounds(p);246//System.err.println("BestScreenBounds is " + scrBounds);247248// If the combo box is totally off screen, do whatever super does249if (scrBounds == null) return super.computePopupBounds(px, py, pw, ph);250251// line up with the bottom of the text field/button (or top, if we have to go above it)252// and left edge if left-to-right, right edge if right-to-left253final Insets comboBoxInsets = comboBox.getInsets();254final Rectangle comboBoxBounds = comboBox.getBounds();255256if (shouldScroll()) {257pw += 15;258}259260if (isPopdown) {261pw += 4;262}263264// the popup should be wide enough for the items but not wider than the screen it's on265final int minWidth = comboBoxBounds.width - (comboBoxInsets.left + comboBoxInsets.right);266pw = Math.max(minWidth, pw);267268final boolean leftToRight = AquaUtils.isLeftToRight(comboBox);269if (leftToRight) {270px += comboBoxInsets.left;271if (!isPopDown) px -= FOCUS_RING_PAD_LEFT;272} else {273px = comboBoxBounds.width - pw - comboBoxInsets.right;274if (!isPopDown) px += FOCUS_RING_PAD_RIGHT;275}276py -= (comboBoxInsets.bottom); //sja fix was +kInset277278// Make sure it's all on the screen - shift it by the amount it's off279p.x += px;280p.y += py; // Screen location of px & py281if (p.x < scrBounds.x) px -= (p.x + scrBounds.x);282if (p.y < scrBounds.y) py -= (p.y + scrBounds.y);283284final Point top = new Point(0, 0);285SwingUtilities.convertPointFromScreen(top, comboBox);286//System.err.println("Converting from point to screen: 0,0 is now " + top.x + ", " + top.y);287288// Since the popup is at zero in this coord space, the maxWidth == the X coord of the screen right edge289// (it might be wider than the screen, if the combo is off the left edge)290final int maxWidth = Math.min(scrBounds.width, top.x + scrBounds.x + scrBounds.width) - 2; // subtract some buffer space291292pw = Math.min(maxWidth, pw);293if (pw < minWidth) {294px -= (minWidth - pw);295pw = minWidth;296}297298// this is a popup window, and will continue calculations below299if (!isPopdown) {300// popup windows are slightly inset from the combo end-cap301pw -= 6;302return computePopupBoundsForMenu(px, py, pw, ph, itemCount, scrBounds);303}304305// don't attempt to inset table cell editors306if (!isTableCellEditor) {307pw -= (FOCUS_RING_PAD_LEFT + FOCUS_RING_PAD_RIGHT);308if (leftToRight) {309px += FOCUS_RING_PAD_LEFT;310}311}312313final Rectangle r = new Rectangle(px, py, pw, ph);314// Check whether it goes below the bottom of the screen, if so flip it315if (r.y + r.height < top.y + scrBounds.y + scrBounds.height) return r;316317return new Rectangle(px, -r.height + comboBoxInsets.top, r.width, r.height);318}319320// The one to use when itemCount <= maxRowCount. Size never adjusts for arrows321// We want it positioned so the selected item is right above the combo box322protected Rectangle computePopupBoundsForMenu(final int px, final int py, final int pw, final int ph, final int itemCount, final Rectangle scrBounds) {323//System.err.println("computePopupBoundsForMenu: " + px + "," + py + " " + pw + "," + ph);324//System.err.println("itemCount: " +itemCount +" src: "+ scrBounds);325int elementSize = 0; //kDefaultItemSize;326if (list != null && itemCount > 0) {327final Rectangle cellBounds = list.getCellBounds(0, 0);328if (cellBounds != null) elementSize = cellBounds.height;329}330331int offsetIndex = comboBox.getSelectedIndex();332if (offsetIndex < 0) offsetIndex = 0;333list.setSelectedIndex(offsetIndex);334335final int selectedLocation = elementSize * offsetIndex;336337final Point top = new Point(0, scrBounds.y);338final Point bottom = new Point(0, scrBounds.y + scrBounds.height - 20); // Allow some slack339SwingUtilities.convertPointFromScreen(top, comboBox);340SwingUtilities.convertPointFromScreen(bottom, comboBox);341342final Rectangle popupBounds = new Rectangle(px, py, pw, ph);// Relative to comboBox343344final int theRest = ph - selectedLocation;345346// If the popup fits on the screen and the selection appears under the mouse w/o scrolling, cool!347// If the popup won't fit on the screen, adjust its position but not its size348// and rewrite this to support arrows - JLists always move the contents so they all show349350// Test to see if it extends off the screen351final boolean extendsOffscreenAtTop = selectedLocation > -top.y;352final boolean extendsOffscreenAtBottom = theRest > bottom.y;353354if (extendsOffscreenAtTop) {355popupBounds.y = top.y + 1;356// Round it so the selection lines up with the combobox357popupBounds.y = (popupBounds.y / elementSize) * elementSize;358} else if (extendsOffscreenAtBottom) {359// Provide blank space at top for off-screen stuff to scroll into360popupBounds.y = bottom.y - popupBounds.height; // popupBounds.height has already been adjusted to fit361} else { // fits - position it so the selectedLocation is under the mouse362popupBounds.y = -selectedLocation;363}364365// Center the selected item on the combobox366final int height = comboBox.getHeight();367final Insets insets = comboBox.getInsets();368final int buttonSize = height - (insets.top + insets.bottom);369final int diff = (buttonSize - elementSize) / 2 + insets.top;370popupBounds.y += diff - FOCUS_RING_PAD_BOTTOM;371372return popupBounds;373}374}375376377