001 /* 002 * $Id: ComponentPopup.java,v 1.11 2012/02/19 17:35:50 davep Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 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 031 package edu.wisc.ssec.mcidasv.ui; 032 033 import java.awt.BorderLayout; 034 import java.awt.Component; 035 import java.awt.Dimension; 036 import java.awt.DisplayMode; 037 import java.awt.FlowLayout; 038 import java.awt.GraphicsDevice; 039 import java.awt.GraphicsEnvironment; 040 import java.awt.MouseInfo; 041 import java.awt.Point; 042 import java.awt.PointerInfo; 043 import java.awt.event.ActionEvent; 044 import java.awt.event.ActionListener; 045 import java.awt.event.ComponentAdapter; 046 import java.awt.event.ComponentEvent; 047 import java.awt.event.MouseAdapter; 048 import java.awt.event.MouseEvent; 049 050 import javax.swing.BorderFactory; 051 import javax.swing.JButton; 052 import javax.swing.JFrame; 053 import javax.swing.JTree; 054 import javax.swing.JWindow; 055 import javax.swing.SwingUtilities; 056 import javax.swing.border.BevelBorder; 057 import javax.swing.tree.DefaultMutableTreeNode; 058 import 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 */ 067 public class ComponentPopup extends JWindow { 068 069 private static final long serialVersionUID = 7394231585407030118L; 070 071 /** 072 * Number of pixels to use to compensate 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 * 080 * @return The dimensions of the screen on the default screen device. 081 */ 082 protected static Dimension getScreenSize() { 083 GraphicsEnvironment genv = GraphicsEnvironment 084 .getLocalGraphicsEnvironment(); 085 GraphicsDevice gdev = genv.getDefaultScreenDevice(); 086 DisplayMode dmode = gdev.getDisplayMode(); 087 088 return new Dimension(dmode.getWidth(), dmode.getHeight()); 089 } 090 091 /** 092 * Does the component contain the screen relative point. 093 * 094 * @param comp The component to check. 095 * @param point Screen relative point. 096 * @param fluff Size in pixels of the area added to both sides of the 097 * component in the x and y directions and used for the contains 098 * calculation. 099 * @return True if the the point lies in the area plus or minus the fluff 100 * factor in either direction. 101 */ 102 public boolean containsPoint(Component comp, Point point, int fluff) { 103 if (!comp.isVisible()) { 104 return false; 105 } 106 Point my = comp.getLocationOnScreen(); 107 boolean containsX = point.x > my.x - FLUFF && point.x < my.x + getWidth() + FLUFF; 108 boolean containsY = point.y > my.y - FLUFF && point.y < my.y + getHeight() + FLUFF; 109 return containsX && containsY; 110 } 111 112 /** 113 * Does the component contain the screen relative point. 114 * 115 * @param comp The component to check. 116 * @param point Screen relative point. 117 * @return True if the the point lies in the same area occupied by the 118 * component. 119 */ 120 public boolean containsPoint(Component comp, Point point) { 121 return containsPoint(comp, point, 0); 122 } 123 124 /** 125 * Determines if the mouse is on me. 126 */ 127 private final MouseAdapter ourHideAdapter; 128 129 /** 130 * Determines if the mouse is on my dad. 131 */ 132 private final MouseAdapter parentsHideAdapter; 133 134 /** 135 * What to do if the parent compoentn state changes. 136 */ 137 private final ComponentAdapter parentsCompAdapter; 138 139 private Component parent; 140 141 /** 142 * Create an instance associated with the given parent. 143 * 144 * @param parent The component to attach this instance to. 145 */ 146 public ComponentPopup(Component parent) { 147 ourHideAdapter = new MouseAdapter() { 148 149 @Override 150 public void mouseExited(MouseEvent evt) { 151 PointerInfo info = MouseInfo.getPointerInfo(); 152 boolean onParent = containsPoint(ComponentPopup.this.parent, 153 info.getLocation()); 154 155 if (isVisible() && !onParent) { 156 setVisible(false); 157 } 158 } 159 }; 160 parentsHideAdapter = new MouseAdapter() { 161 162 @Override 163 public void mouseExited(MouseEvent evt) { 164 PointerInfo info = MouseInfo.getPointerInfo(); 165 boolean onComponent = containsPoint(ComponentPopup.this, 166 info.getLocation()); 167 if (isVisible() && !onComponent) { 168 setVisible(false); 169 } 170 } 171 }; 172 parentsCompAdapter = new ComponentAdapter() { 173 174 @Override 175 public void componentHidden(ComponentEvent evt) { 176 setVisible(false); 177 } 178 179 @Override 180 public void componentResized(ComponentEvent evt) { 181 showPopup(); 182 } 183 }; 184 setParent(parent); 185 } 186 187 /** 188 * Set our parent. If there is currently a parent remove the associated 189 * listeners and add them to the new parent. 190 * 191 * @param comp 192 */ 193 public void setParent(Component comp) { 194 if (parent != null) { 195 parent.removeMouseListener(parentsHideAdapter); 196 parent.removeComponentListener(parentsCompAdapter); 197 } 198 199 parent = comp; 200 parent.addComponentListener(parentsCompAdapter); 201 parent.addMouseListener(parentsHideAdapter); 202 } 203 204 /** 205 * Show this popup above the parent. It is not checked if the component will 206 * fit on the screen. 207 */ 208 public void showAbove() { 209 Point loc = parent.getLocationOnScreen(); 210 int x = loc.x; 211 int y = loc.y - getHeight(); 212 showPopupAt(x, y); 213 } 214 215 /** 216 * Show this popup below the parent. It is not checked if the component will 217 * fit on the screen. 218 */ 219 public void showBelow() { 220 Point loc = parent.getLocationOnScreen(); 221 int x = loc.x; 222 int y = loc.y + parent.getHeight(); 223 showPopupAt(x, y); 224 } 225 226 /** 227 * Do we fit between the top of the parent and the top edge of the screen. 228 * 229 * @return True if we fit between the upper edge of our parent and the top 230 * edge of the screen. 231 */ 232 protected boolean fitsAbove() { 233 Point loc = parent.getLocationOnScreen(); 234 int myH = getHeight(); 235 return loc.y - myH > 0; 236 } 237 238 /** 239 * Do we fit between the bottom of the parent and the edge of the screen. 240 * 241 * @return True if we fit between the bottom edge of our parent and the 242 * bottom edge of the screen. 243 */ 244 protected boolean fitsBelow() { 245 Point loc = parent.getLocationOnScreen(); 246 Dimension scr = getScreenSize(); 247 int myH = getHeight(); 248 return loc.y + parent.getHeight() + myH < scr.height; 249 } 250 251 /** 252 * Show at the specified X and Y. 253 * 254 * @param x 255 * @param y 256 */ 257 public void showPopupAt(int x, int y) { 258 setLocation(x, y); 259 setVisible(true); 260 } 261 262 /** 263 * Show this popup deciding whether to show it above or below the parent 264 * component. 265 */ 266 public void showPopup() { 267 if (fitsBelow()) { 268 showBelow(); 269 } else { 270 showAbove(); 271 } 272 } 273 274 /** 275 * Overridden to make sure our hide listeners are added to child components. 276 * 277 * @see javax.swing.JWindow#addImpl(java.awt.Component, java.lang.Object, int) 278 */ 279 protected void addImpl(Component comp, Object constraints, int index) { 280 super.addImpl(comp, constraints, index); 281 comp.addMouseListener(ourHideAdapter); 282 } 283 284 /** 285 * Test method. 286 */ 287 private static void createAndShowGui() { 288 DefaultMutableTreeNode root = new DefaultMutableTreeNode("ROOT"); 289 DefaultTreeModel model = new DefaultTreeModel(root); 290 JTree tree = new JTree(model); 291 tree.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED)); 292 293 root.add(new DefaultMutableTreeNode("Child 1")); 294 root.add(new DefaultMutableTreeNode("Child 2")); 295 root.add(new DefaultMutableTreeNode("Child 3")); 296 297 for (int i = 0; i < tree.getRowCount(); i++) { 298 tree.expandPath(tree.getPathForRow(i)); 299 } 300 final JButton button = new JButton("Popup"); 301 final ComponentPopup cp = new ComponentPopup(button); 302 cp.add(tree, BorderLayout.CENTER); 303 cp.pack(); 304 button.addActionListener(new ActionListener() { 305 public void actionPerformed(ActionEvent evt) { 306 cp.showPopup(); 307 } 308 }); 309 310 JFrame frame = new JFrame("ComponentPopup"); 311 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 312 frame.setLayout(new FlowLayout()); 313 frame.add(button); 314 frame.pack(); 315 frame.setVisible(true); 316 } 317 318 /** 319 * Test method. 320 * 321 * @param args 322 */ 323 public static void main(String[] args) { 324 try { 325 javax.swing.UIManager.setLookAndFeel(javax.swing.UIManager 326 .getCrossPlatformLookAndFeelClassName()); 327 } catch (Exception e) { 328 e.printStackTrace(); 329 } 330 SwingUtilities.invokeLater(new Runnable() { 331 332 public void run() { 333 createAndShowGui(); 334 } 335 }); 336 } 337 338 }