/* * Copyright (c) 2009 Kathryn Huxtable and Kenneth Orr. * * This file is part of the SeaGlass Pluggable Look and Feel. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * $Id$ */ package com.seaglasslookandfeel.component; import java.awt.Color; import java.awt.Dimension; import java.awt.GraphicsConfiguration; import java.awt.Insets; import java.awt.Point; import java.awt.Rectangle; import java.awt.Toolkit; import javax.swing.BoxLayout; import javax.swing.JComboBox; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.border.Border; import javax.swing.border.LineBorder; import javax.swing.plaf.basic.BasicComboPopup; /** * SeaGlassComboPopup. The code to calculate the popup bounds was lifted from * Quaqua by Werner Randelshofer. * * @see javax.swing.plaf.synth.SynthComboPopup * @see ch.randelshofer.quaqua.QuaquaComboPopup */ public class SeaGlassComboPopup extends BasicComboPopup { private static final long serialVersionUID = -2424496840321170560L; private static final int LEFT_SHIFT = 5; private Color BORDER_COLOR = UIManager.getColor("seaGlassPopupBorder"); private Border LIST_BORDER = new LineBorder(BORDER_COLOR, 1); /** * Creates a new SeaGlassComboPopup object. * * @param combo the combo box. */ public SeaGlassComboPopup(JComboBox combo) { super(combo); } /** * {@inheritDoc} */ @Override public void updateUI() { // TODO Auto-generated method stub super.updateUI(); } /** * Is the combo box popup a "drop down" popup, or does it overlay the box? * * @return {@code true} if the popup should drop down from the combo box, * {@code false} if it should overlay the combo box. */ private boolean isDropDown() { return comboBox.isEditable() || hasScrollBars(); } /** * Does the combo box have scroll bars? * * @return {@code true} if the combo box popup has scroll bars, * {@code false} otherwise. */ private boolean hasScrollBars() { return comboBox.getModel().getSize() > getMaximumRowCount(); } /** * Is the combo box editable? * * @return {@code true} if the combo box popup is editable, {@code false} * otherwise. */ private boolean isEditable() { return comboBox.isEditable(); } /** * Configures the list which is used to hold the combo box items in the * popup. This method is called when the UI class is created. */ @Override protected void configureList() { list.setFont(comboBox.getFont()); list.setBorder(null); list.setCellRenderer(comboBox.getRenderer()); list.setFocusable(false); list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); int selectedIndex = comboBox.getSelectedIndex(); if (selectedIndex == -1) { list.clearSelection(); } else { list.setSelectedIndex(selectedIndex); list.ensureIndexIsVisible(selectedIndex); } installListListeners(); } /** * Configures the popup portion of the combo box. This method is called when * the UI class is created. */ protected void configurePopup() { setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setBorderPainted(true); setBorder(LIST_BORDER); setOpaque(false); add(scroller); setDoubleBuffered(true); setFocusable(false); } /** * Calculate the placement and size of the popup portion of the combo box * based on the combo box location and the enclosing screen bounds. If no * transformations are required, then the returned rectangle will have the * same values as the parameters. * * @param px starting x location * @param py starting y location * @param pw starting width * @param ph starting height * * @return a rectangle which represents the placement and size of the popup */ protected Rectangle computePopupBounds(int px, int py, int pw, int ph) { Toolkit toolkit = Toolkit.getDefaultToolkit(); Rectangle screenBounds; int listWidth = getList().getPreferredSize().width; Insets margin = comboBox.getInsets(); if (hasScrollBars()) { px += margin.left; pw = Math.max(pw - margin.left - margin.right, listWidth + 16); } else { px += margin.left; pw = Math.max(pw - LEFT_SHIFT - margin.left, listWidth); } // Calculate the desktop dimensions relative to the combo box. GraphicsConfiguration gc = comboBox.getGraphicsConfiguration(); Point p = new Point(); SwingUtilities.convertPointFromScreen(p, comboBox); if (gc == null) { screenBounds = new Rectangle(p, toolkit.getScreenSize()); } else { // Get the screen insets. Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc); screenBounds = new Rectangle(gc.getBounds()); screenBounds.width -= (screenInsets.left + screenInsets.right); screenBounds.height -= (screenInsets.top + screenInsets.bottom); screenBounds.x += screenInsets.left; screenBounds.y += screenInsets.top; } if (isDropDown()) { if (isEditable()) { py -= margin.bottom + 2; } else { py -= margin.bottom; } } else { int yOffset = -margin.top; int selectedIndex = comboBox.getSelectedIndex(); if (selectedIndex <= 0) { py = -yOffset; } else { py = -yOffset - list.getCellBounds(0, selectedIndex - 1).height; } } // Compute the rectangle for the popup menu Rectangle rect = new Rectangle(px, Math.max(py, p.y + screenBounds.y), Math.min(screenBounds.width, pw), Math.min(screenBounds.height - 40, ph)); // Add the preferred scroll bar width, if the popup does not fit // on the available rectangle. if (rect.height < ph) { rect.width += 16; } return rect; } /** * Sets the list selection index to the selectedIndex. This method is used * to synchronize the list selection with the combo box selection. * * @param selectedIndex the index to set the list */ private void setListSelection(int selectedIndex) { if (selectedIndex == -1) { list.clearSelection(); } else { list.setSelectedIndex(selectedIndex); list.ensureIndexIsVisible(selectedIndex); } } /** * Implementation of ComboPopup.show(). */ public void show() { setListSelection(comboBox.getSelectedIndex()); Point location = getPopupLocation(); show(comboBox, location.x, location.y); // For some reason, this is needed to clear the old selection being // displayed. Calling list.clearSelection() doesn't cut it. list.repaint(); } /** * Get the maximum row count to display. If none is set, return 100. * * @return the maximum row count to display. */ private int getMaximumRowCount() { // return isEditable() ? comboBox.getMaximumRowCount() : 100; return comboBox.getMaximumRowCount(); } /** * Calculates the upper left location of the Popup. * * @return the Point representing the upper-left coordinate of the Popup. */ private Point getPopupLocation() { Dimension popupSize = comboBox.getSize(); Insets insets = getInsets(); // reduce the width of the scrollpane by the insets so that the popup // is the same width as the combo box. popupSize.setSize(popupSize.width - (insets.right + insets.left), getPopupHeightForRowCount(getMaximumRowCount())); Rectangle popupBounds = computePopupBounds(0, comboBox.getBounds().height, popupSize.width, popupSize.height); Dimension scrollSize = popupBounds.getSize(); Point popupLocation = popupBounds.getLocation(); scroller.setMaximumSize(scrollSize); scroller.setPreferredSize(scrollSize); scroller.setMinimumSize(scrollSize); list.revalidate(); return popupLocation; } }