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