001/* 002 * $Id: ComponentPopup.java,v 1.9 2011/03/24 16:06:34 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2011 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031package edu.wisc.ssec.mcidasv.ui; 032 033import java.awt.BorderLayout; 034import java.awt.Component; 035import java.awt.Dimension; 036import java.awt.DisplayMode; 037import java.awt.FlowLayout; 038import java.awt.GraphicsDevice; 039import java.awt.GraphicsEnvironment; 040import java.awt.MouseInfo; 041import java.awt.Point; 042import java.awt.PointerInfo; 043import java.awt.event.ActionEvent; 044import java.awt.event.ActionListener; 045import java.awt.event.ComponentAdapter; 046import java.awt.event.ComponentEvent; 047import java.awt.event.MouseAdapter; 048import java.awt.event.MouseEvent; 049 050import javax.swing.BorderFactory; 051import javax.swing.JButton; 052import javax.swing.JFrame; 053import javax.swing.JTree; 054import javax.swing.JWindow; 055import javax.swing.SwingUtilities; 056import javax.swing.border.BevelBorder; 057import javax.swing.tree.DefaultMutableTreeNode; 058import javax.swing.tree.DefaultTreeModel; 059 060/** 061 * A popup window that attaches itself to a parent and can display an 062 * component without preventing user interaction like a <tt>JComboBox</tt>. 063 * 064 * @author <a href="https://www.ssec.wisc.edu/cgi-bin/email_form.cgi?name=Flynn,%20Bruce">Bruce Flynn, SSEC</a> 065 * 066 */ 067public class ComponentPopup extends JWindow { 068 069 private static final long serialVersionUID = 7394231585407030118L; 070 071 /** 072 * Number of pixels to use to compenstate for when the mouse is moved slowly 073 * thereby hiding this popup when between components. 074 */ 075 private static final int FLUFF = 3; 076 077 /** 078 * Get the calculated total screen size. 079 * @return The dimensions of the screen on the default screen device. 080 */ 081 protected static Dimension getScreenSize() { 082 GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment(); 083 GraphicsDevice gdev = genv.getDefaultScreenDevice(); 084 DisplayMode dmode = gdev.getDisplayMode(); 085 086 return new Dimension(dmode.getWidth(), dmode.getHeight()); 087 } 088 089 /** 090 * Does the component contain the screen relative point. 091 * @param comp The component to check. 092 * @param point Screen relative point. 093 * @param fluff Size in pixels of the area added to both sides of the component 094 * in the x and y directions and used for the contains calculation. 095 * @return True if the the point lies in the area plus or minus the fluff factor 096 * in either direction. 097 */ 098 public boolean containsPoint(Component comp, Point point, int fluff) { 099 if (!comp.isVisible()) { 100 return false; 101 } 102 Point my = comp.getLocationOnScreen(); 103 boolean containsX = point.x > my.x - FLUFF && point.x < my.x + getWidth() + FLUFF; 104 boolean containsY = point.y > my.y - FLUFF && point.y < my.y + getHeight() + FLUFF; 105 return containsX && containsY; 106 } 107 108 /** 109 * Does the component contain the screen relative point. 110 * @param comp The component to check. 111 * @param point Screen relative point. 112 * @return True if the the point lies in the same area occupied by the component. 113 */ 114 public boolean containsPoint(Component comp, Point point) { 115 return containsPoint(comp, point, 0); 116 } 117 118 /** 119 * Determines if the mouse is on me. 120 */ 121 private final MouseAdapter ourHideAdapter; 122 /** 123 * Determines if the mouse is on my dad. 124 */ 125 private final MouseAdapter parentsHideAdapter; 126 /** 127 * What to do if the parent compoentn state changes. 128 */ 129 private final ComponentAdapter parentsCompAdapter; 130 private Component parent; 131 132 /** 133 * Create an instance associated with the given parent. 134 * @param parent The component to attach this instance to. 135 */ 136 public ComponentPopup(Component parent) { 137 ourHideAdapter = new MouseAdapter() { 138 @Override 139 public void mouseExited(MouseEvent evt) { 140 PointerInfo info = MouseInfo.getPointerInfo(); 141 boolean onParent = containsPoint( 142 ComponentPopup.this.parent, 143 info.getLocation() 144 ); 145 146 if (isVisible() && !onParent) { 147 setVisible(false); 148 } 149 } 150 }; 151 parentsHideAdapter = new MouseAdapter() { 152 @Override 153 public void mouseExited(MouseEvent evt) { 154 PointerInfo info = MouseInfo.getPointerInfo(); 155 boolean onComponent = containsPoint( 156 ComponentPopup.this, 157 info.getLocation() 158 ); 159 if (isVisible() && !onComponent) { 160 setVisible(false); 161 } 162 } 163 }; 164 parentsCompAdapter = new ComponentAdapter() { 165 @Override 166 public void componentHidden(ComponentEvent evt) { 167 setVisible(false); 168 } 169 @Override 170 public void componentResized(ComponentEvent evt) { 171 showPopup(); 172 } 173 }; 174 setParent(parent); 175 } 176 177 /** 178 * Set our parent. If there is currently a parent remove the associated 179 * listeners and add them to the new parent. 180 * @param comp 181 */ 182 public void setParent(Component comp) { 183 if (parent != null) { 184 parent.removeMouseListener(parentsHideAdapter); 185 parent.removeComponentListener(parentsCompAdapter); 186 } 187 188 parent = comp; 189 parent.addComponentListener(parentsCompAdapter); 190 parent.addMouseListener(parentsHideAdapter); 191 } 192 193 /** 194 * Show this popup above the parent. It is not checked if 195 * the component will fit on the screen. 196 */ 197 public void showAbove() { 198 Point loc = parent.getLocationOnScreen(); 199 int x = loc.x; 200 int y = loc.y - getHeight(); 201 showPopupAt(x, y); 202 } 203 204 /** 205 * Show this popup below the parent. It is not checked if 206 * the component will fit on the screen. 207 */ 208 public void showBelow() { 209 Point loc = parent.getLocationOnScreen(); 210 int x = loc.x; 211 int y = loc.y + parent.getHeight(); 212 showPopupAt(x, y); 213 } 214 215 /** 216 * Do we fit between the top of the parent and the top edge 217 * of the screen. 218 * @return True if we fit between the upper edge of our parent and 219 * the top edge of the screen. 220 */ 221 protected boolean fitsAbove() { 222 Point loc = parent.getLocationOnScreen(); 223 int myH = getHeight(); 224 return loc.y - myH > 0; 225 } 226 227 /** 228 * Do we fit between the bottom of the parent and the edge 229 * of the screen. 230 * @return True if we fit between the bottom edge of our parent and 231 * the bottom edge of the screen. 232 */ 233 protected boolean fitsBelow() { 234 Point loc = parent.getLocationOnScreen(); 235 Dimension scr = getScreenSize(); 236 int myH = getHeight(); 237 return loc.y + parent.getHeight() + myH < scr.height; 238 } 239 240 /** 241 * Show at the specified X and Y. 242 * @param x 243 * @param y 244 */ 245 public void showPopupAt(int x, int y) { 246 setLocation(x, y); 247 setVisible(true); 248 } 249 250 /** 251 * Show this popup deciding whether to show it above 252 * or below the parent component. 253 */ 254 public void showPopup() { 255 if (fitsBelow()) { 256 showBelow(); 257 } else { 258 showAbove(); 259 } 260 } 261 262 /** 263 * Overridden to make sure our hide listeners are added to child components. 264 * @see javax.swing.JWindow#addImpl(java.awt.Component, java.lang.Object, int) 265 */ 266 protected void addImpl(Component comp, Object constraints, int index) { 267 super.addImpl(comp, constraints, index); 268 comp.addMouseListener(ourHideAdapter); 269 } 270 271 /** 272 * Test method. 273 */ 274 private static void createAndShowGui() { 275 276 DefaultMutableTreeNode root = new DefaultMutableTreeNode("ROOT"); 277 DefaultTreeModel model = new DefaultTreeModel(root); 278 JTree tree = new JTree(model); 279 tree.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 280 281 root.add(new DefaultMutableTreeNode("Child 1")); 282 root.add(new DefaultMutableTreeNode("Child 2")); 283 root.add(new DefaultMutableTreeNode("Child 3")); 284 285 for (int i = 0; i < tree.getRowCount(); i++) { 286 tree.expandPath(tree.getPathForRow(i)); 287 } 288 289 final JButton button = new JButton("Popup"); 290 final ComponentPopup cp = new ComponentPopup(button); 291 cp.add(tree, BorderLayout.CENTER); 292 cp.pack(); 293 294 button.addActionListener(new ActionListener() { 295 public void actionPerformed(ActionEvent evt) { 296 cp.showPopup(); 297 } 298 }); 299 300 JFrame frame = new JFrame("ComponentPopup"); 301 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 302 frame.setLayout(new FlowLayout()); 303 frame.add(button); 304 frame.pack(); 305 frame.setVisible(true); 306 } 307 308 /** 309 * Test method. 310 * @param args 311 */ 312 public static void main(String[] args) { 313 try { 314 javax.swing.UIManager.setLookAndFeel( 315 javax.swing.UIManager.getCrossPlatformLookAndFeelClassName()); 316 } catch (Exception e) { 317 e.printStackTrace(); 318 } 319 SwingUtilities.invokeLater(new Runnable() { 320 public void run() { 321 createAndShowGui(); 322 } 323 }); 324 } 325 326}