package com.opendoorlogistics.codefromweb; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Point; import javax.swing.JComboBox; import javax.swing.JList; import javax.swing.JScrollBar; import javax.swing.JScrollPane; import javax.swing.JViewport; import javax.swing.SwingUtilities; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.plaf.basic.BasicComboPopup; /** * From http://www.camick.com/java/source/BoundsPopupMenuListener.java * * This class will change the bounds of the JComboBox popup menu to support different functionality. It will support the following features: - a * horizontal scrollbar can be displayed when necessary - the popup can be wider than the combo box - the popup can be displayed above the combo box * * Class will only work for a JComboBox that uses a BasicComboPop. */ public class BoundsPopupMenuListener implements PopupMenuListener { private boolean scrollBarRequired = true; private boolean popupWider; private int maximumWidth = -1; private boolean popupAbove; private JScrollPane scrollPane; /** * Convenience constructore to allow the display of a horizontal scrollbar when required. */ public BoundsPopupMenuListener() { this(true, false, -1, false); } /** * Convenience constructor that allows you to display the popup wider and/or above the combo box. * * @param popupWider * when true, popup width is based on the popup preferred width * @param popupAbove * when true, popup is displayed above the combobox */ public BoundsPopupMenuListener(boolean popupWider, boolean popupAbove) { this(false, popupWider, -1, popupAbove); } /** * Convenience constructor that allows you to display the popup wider than the combo box and to specify the maximum width * * @param maximumWidth * the maximum width of the popup. The popupAbove value is set to "true". */ public BoundsPopupMenuListener(int maximumWidth) { this(true, true, maximumWidth, false); } /** * General purpose constructor to set all popup properties at once. * * @param scrollBarRequired * display a horizontal scrollbar when the preferred width of popup is greater than width of scrollPane. * @param popupWider * display the popup at its preferred with * @param maximumWidth * limit the popup width to the value specified (minimum size will be the width of the combo box) * @param popupAbove * display the popup above the combo box * */ public BoundsPopupMenuListener(boolean scrollBarRequired, boolean popupWider, int maximumWidth, boolean popupAbove) { setScrollBarRequired(scrollBarRequired); setPopupWider(popupWider); setMaximumWidth(maximumWidth); setPopupAbove(popupAbove); } /** * Return the maximum width of the popup. * * @return the maximumWidth value */ public int getMaximumWidth() { return maximumWidth; } /** * Set the maximum width for the popup. This value is only used when setPopupWider( true ) has been specified. A value of -1 indicates that there * is no maximum. * * @param maximumWidth * the maximum width of the popup */ public void setMaximumWidth(int maximumWidth) { this.maximumWidth = maximumWidth; } /** * Determine if the popup should be displayed above the combo box. * * @return the popupAbove value */ public boolean isPopupAbove() { return popupAbove; } /** * Change the location of the popup relative to the combo box. * * @param popupAbove * true display popup above the combo box, false display popup below the combo box. */ public void setPopupAbove(boolean popupAbove) { this.popupAbove = popupAbove; } /** * Determine if the popup might be displayed wider than the combo box * * @return the popupWider value */ public boolean isPopupWider() { return popupWider; } /** * Change the width of the popup to be the greater of the width of the combo box or the preferred width of the popup. Normally the popup width is * always the same size as the combo box width. * * @param popupWider * true adjust the width as required. */ public void setPopupWider(boolean popupWider) { this.popupWider = popupWider; } /** * Determine if the horizontal scroll bar might be required for the popup * * @return the scrollBarRequired value */ public boolean isScrollBarRequired() { return scrollBarRequired; } /** * For some reason the default implementation of the popup removes the horizontal scrollBar from the popup scroll pane which can result in the * truncation of the rendered items in the popop. Adding a scrollBar back to the scrollPane will allow horizontal scrolling if necessary. * * @param scrollBarRequired * true add horizontal scrollBar to scrollPane false remove the horizontal scrollBar */ public void setScrollBarRequired(boolean scrollBarRequired) { this.scrollBarRequired = scrollBarRequired; } /** * Alter the bounds of the popup just before it is made visible. */ @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { JComboBox comboBox = (JComboBox) e.getSource(); if (comboBox.getItemCount() == 0) return; final Object child = comboBox.getAccessibleContext().getAccessibleChild(0); if (child instanceof BasicComboPopup) { SwingUtilities.invokeLater(new Runnable() { public void run() { customizePopup((BasicComboPopup) child); } }); } } protected void customizePopup(BasicComboPopup popup) { scrollPane = getScrollPane(popup); if (popupWider) popupWider(popup); checkHorizontalScrollBar(popup); // For some reason in JDK7 the popup will not display at its preferred // width unless its location has been changed from its default // (ie. for normal "pop down" shift the popup and reset) Component comboBox = popup.getInvoker(); try { // getLocationOnScreen sometimes causes an exception... Point location = comboBox.getLocationOnScreen(); if (popupAbove) { int height = popup.getPreferredSize().height; popup.setLocation(location.x, location.y - height); } else { int height = comboBox.getPreferredSize().height; popup.setLocation(location.x, location.y + height - 1); popup.setLocation(location.x, location.y + height); } } catch (Throwable e) { // TODO: handle exception } } /* * Adjust the width of the scrollpane used by the popup */ protected void popupWider(BasicComboPopup popup) { JList list = popup.getList(); // Determine the maximimum width to use: // a) determine the popup preferred width // b) limit width to the maximum if specified // c) ensure width is not less than the scroll pane width int popupWidth = list.getPreferredSize().width + 5 // make sure horizontal scrollbar doesn't appear + getScrollBarWidth(popup, scrollPane); if (maximumWidth != -1) { popupWidth = Math.min(popupWidth, maximumWidth); } Dimension scrollPaneSize = scrollPane.getPreferredSize(); popupWidth = Math.max(popupWidth, scrollPaneSize.width); // Adjust the width scrollPaneSize.width = popupWidth; scrollPane.setPreferredSize(scrollPaneSize); scrollPane.setMaximumSize(scrollPaneSize); } /* * This method is called every time: - to make sure the viewport is returned to its default position - to remove the horizontal scrollbar when it * is not wanted */ private void checkHorizontalScrollBar(BasicComboPopup popup) { // Reset the viewport to the left JViewport viewport = scrollPane.getViewport(); Point p = viewport.getViewPosition(); p.x = 0; viewport.setViewPosition(p); // Remove the scrollbar so it is never painted if (!scrollBarRequired) { scrollPane.setHorizontalScrollBar(null); return; } // Make sure a horizontal scrollbar exists in the scrollpane JScrollBar horizontal = scrollPane.getHorizontalScrollBar(); if (horizontal == null) { horizontal = new JScrollBar(JScrollBar.HORIZONTAL); scrollPane.setHorizontalScrollBar(horizontal); scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); } // Potentially increase height of scroll pane to display the scrollbar if (horizontalScrollBarWillBeVisible(popup, scrollPane)) { Dimension scrollPaneSize = scrollPane.getPreferredSize(); scrollPaneSize.height += horizontal.getPreferredSize().height; scrollPane.setPreferredSize(scrollPaneSize); scrollPane.setMaximumSize(scrollPaneSize); scrollPane.revalidate(); } } /* * Get the scroll pane used by the popup so its bounds can be adjusted */ protected JScrollPane getScrollPane(BasicComboPopup popup) { JList list = popup.getList(); Container c = SwingUtilities.getAncestorOfClass(JScrollPane.class, list); return (JScrollPane) c; } /* * I can't find any property on the scrollBar to determine if it will be displayed or not so use brute force to determine this. */ protected int getScrollBarWidth(BasicComboPopup popup, JScrollPane scrollPane) { int scrollBarWidth = 0; JComboBox comboBox = (JComboBox) popup.getInvoker(); if (comboBox.getItemCount() > comboBox.getMaximumRowCount()) { JScrollBar vertical = scrollPane.getVerticalScrollBar(); scrollBarWidth = vertical.getPreferredSize().width; } return scrollBarWidth; } /* * I can't find any property on the scrollBar to determine if it will be displayed or not so use brute force to determine this. */ protected boolean horizontalScrollBarWillBeVisible(BasicComboPopup popup, JScrollPane scrollPane) { JList list = popup.getList(); int scrollBarWidth = getScrollBarWidth(popup, scrollPane); int popupWidth = list.getPreferredSize().width + scrollBarWidth; return popupWidth > scrollPane.getPreferredSize().width; } @Override public void popupMenuCanceled(PopupMenuEvent e) { } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { // In its normal state the scrollpane does not have a scrollbar if (scrollPane != null) { scrollPane.setHorizontalScrollBar(null); } } }