/*
* Copyright 2010-2015 Institut Pasteur.
*
* This file is part of Icy.
*
* Icy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Icy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Icy. If not, see <http://www.gnu.org/licenses/>.
*/
package icy.gui.util;
import icy.gui.frame.IcyFrame;
import icy.network.NetworkUtil;
import icy.system.SystemUtil;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GraphicsDevice;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Window;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.swing.JInternalFrame;
import javax.swing.JMenu;
import javax.swing.JSlider;
import javax.swing.JTextPane;
import javax.swing.JTree;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
/**
* General component utilities class.
*
* @author Stephane
*/
public class ComponentUtil
{
public static void setPreferredWidth(Component c, int w)
{
c.setPreferredSize(new Dimension(w, c.getPreferredSize().height));
}
public static void setPreferredHeight(Component c, int h)
{
c.setPreferredSize(new Dimension(c.getPreferredSize().width, h));
}
public static void setFixedSize(Component c, Dimension d)
{
c.setMinimumSize(d);
c.setMaximumSize(d);
c.setPreferredSize(d);
}
public static void setFixedWidth(Component c, int w)
{
c.setMinimumSize(new Dimension(w, 0));
c.setMaximumSize(new Dimension(w, 65535));
c.setPreferredSize(new Dimension(w, c.getPreferredSize().height));
}
public static void setFixedHeight(Component c, int h)
{
c.setMinimumSize(new Dimension(0, h));
c.setMaximumSize(new Dimension(65535, h));
c.setPreferredSize(new Dimension(c.getPreferredSize().width, h));
}
public static void setPreferredWidth(IcyFrame frm, int w)
{
frm.setPreferredSize(new Dimension(w, frm.getPreferredSize().height));
}
public static void setPreferredHeight(IcyFrame frm, int h)
{
frm.setPreferredSize(new Dimension(frm.getPreferredSize().width, h));
}
public static void setFixedSize(IcyFrame frm, Dimension d)
{
frm.setMinimumSize(d);
frm.setMaximumSize(d);
frm.setPreferredSize(d);
}
public static void setFixedWidth(IcyFrame frm, int w)
{
frm.setMinimumSize(new Dimension(w, 0));
frm.setMaximumSize(new Dimension(w, 65535));
frm.setPreferredSize(new Dimension(w, frm.getPreferredSize().height));
}
public static void setFixedHeight(IcyFrame frm, int h)
{
frm.setMinimumSize(new Dimension(0, h));
frm.setMaximumSize(new Dimension(65535, h));
frm.setPreferredSize(new Dimension(frm.getPreferredSize().width, h));
}
public static void removeFixedSize(Component c)
{
c.setMinimumSize(new Dimension(0, 0));
c.setMaximumSize(new Dimension(65535, 65535));
}
/**
* Center specified component relative to its parent
*/
public static void center(Component comp)
{
final Container parent = comp.getParent();
if (parent != null)
{
final int x = (parent.getWidth() - comp.getWidth()) / 2;
final int y = (parent.getHeight() - comp.getHeight()) / 2;
// avoid negative coordinates when centering
comp.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y);
}
}
/**
* Center specified windows relative to its parent
*/
public static void center(Window window)
{
window.setLocationRelativeTo(window.getParent());
}
/**
* Center specified JInternalFrame
*/
public static void center(JInternalFrame frame)
{
center((Component) frame);
}
/**
* Center the Window on specified point
*/
public static void centerOn(Window window, Point position)
{
final int x = position.x - (window.getWidth() / 2);
final int y = position.y - (window.getHeight() / 2);
// avoid negative coordinates when centering
window.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y);
}
/**
* Center the JInternalFrame on specified point
*/
public static void centerOn(JInternalFrame f, Point position)
{
centerOn((Component) f, position);
}
/**
* Center specified component relative to its parent
*/
public static void centerOn(Component comp, Point position)
{
final int x = position.x - (comp.getWidth() / 2);
final int y = position.y - (comp.getHeight() / 2);
// avoid negative coordinates when centering
comp.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y);
}
/**
* Use f.center() instead
*
* @deprecated
*/
@Deprecated
public static void center(IcyFrame f)
{
f.center();
}
public static void center(Component dst, Component src)
{
dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2),
src.getY() + ((src.getHeight() - dst.getHeight()) / 2));
}
public static void center(IcyFrame dst, Component src)
{
dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2),
src.getY() + ((src.getHeight() - dst.getHeight()) / 2));
}
public static void center(Component dst, IcyFrame src)
{
dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2),
src.getY() + ((src.getHeight() - dst.getHeight()) / 2));
}
public static void center(IcyFrame dst, IcyFrame src)
{
dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2),
src.getY() + ((src.getHeight() - dst.getHeight()) / 2));
}
/**
* Returns the center position of the specified component.
*/
public static Point2D.Double getCenter(Component c)
{
if (c != null)
{
final Rectangle r = c.getBounds();
return new Point2D.Double(r.getX() + (r.getWidth() / 2d), r.getY() + (r.getHeight() / 2d));
}
return new Point2D.Double(0d, 0d);
}
/**
* Returns all screen device where the specified component is currently displayed.<br>
* Can return an empty list if given region do not intersect any screen device.
*
* @see #getScreen(Component)
* @see SystemUtil#getScreenDevices(Rectangle)
*/
public static List<GraphicsDevice> getScreens(Component c)
{
return SystemUtil.getScreenDevices(c.getBounds());
}
/**
* Returns the main screen device where the specified component is currently displayed.<br>
* Can return <code>null</code> if component is not located on any screen device.
*
* @see #getScreens(Component)
* @see SystemUtil#getScreenDevice(Rectangle)
* @see SystemUtil#getScreenDevice(Point)
*/
public static GraphicsDevice getScreen(Component c)
{
final Point2D.Double pos2d = getCenter(c);
final Point pos = new Point((int) pos2d.getX(), (int) pos2d.getY());
// get screen on Component center first (better for multi screen)
GraphicsDevice result = SystemUtil.getScreenDevice(pos);
// cannot retrieve screen on center, just use component bounds then
if (result == null)
result = SystemUtil.getScreenDevice(c.getBounds());
return result;
}
/**
* Returns the new location of wanted bounds so it does not go outside the specified screen bounds.<br>
* Returns <code>null</code> if the wanted bounds doesn't need position adjustment.
*/
public static Point fixPosition(Rectangle wantedBounds, Rectangle screenBounds)
{
if (screenBounds.isEmpty())
return null;
final int margeX = 80;
final int margeY = 40;
int x = wantedBounds.x;
int y = wantedBounds.y;
int sx = screenBounds.x;
int sy = screenBounds.y;
int minX = (sx - wantedBounds.width) + margeX;
int maxX = (sx + screenBounds.width) - margeX;
int minY = sy;
// int minY = (sy - wantedBounds.height) + margeY;
int maxY = (sy + screenBounds.height) - margeY;
if (y < minY)
y = minY;
else if (y > maxY)
y = maxY;
if (x < minX)
x = minX;
else if (x > maxX)
x = maxX;
final Point pos = wantedBounds.getLocation();
// position changed ?
if ((pos.x != x) || (pos.y != y))
return new Point(x, y);
return null;
}
/**
* Fix the given bounds of specified component so it does not go completely off screen.<br>
* Returns <code>true</code> if the bounds position has be adjusted.
*/
public static boolean fixPosition(Component component, Rectangle wantedBounds)
{
final List<GraphicsDevice> screens = SystemUtil.getScreenDevices();
// headless mode probably
if (screens.isEmpty())
return false;
Point newPos = null;
boolean useMainScreen = false;
for (GraphicsDevice screen : screens)
{
final Point pt = fixPosition(wantedBounds, SystemUtil.getScreenBounds(screen, true));
// this screen accept current position --> no need to adjust position
if (pt == null)
return false;
// we already have an adjusted position ?
if (newPos != null)
useMainScreen = true;
else
newPos = pt;
}
// multiple possible position adjustment ? --> use main screen
if (useMainScreen)
newPos = fixPosition(wantedBounds, SystemUtil.getScreenBounds(getScreen(component), true));
// got a new position ? --> set it
if (newPos != null)
{
wantedBounds.setLocation(newPos);
return true;
}
return false;
// final List<GraphicsDevice> screens = getScreens(component);
// Point newPos = null;
// boolean useMainScreen = false;
//
// for (GraphicsDevice screen : screens)
// {
// final Point pt = fixPosition(wantedBounds, SystemUtil.getScreenBounds(screen, true));
//
// // this screen accept current position --> no need to adjust position
// if (pt == null)
// return false;
//
// // we already have an adjusted position ?
// if (newPos != null)
// useMainScreen = true;
// else
// newPos = pt;
// }
//
// // use main screen
// if (screens.isEmpty())
// useMainScreen = true;
//
// // multiple possible position adjustment ? --> use main screen
// if (useMainScreen)
// newPos = fixPosition(wantedBounds, SystemUtil.getScreenBounds(getScreen(component), true));
//
// // got a new position ? --> set it
// if (newPos != null)
// wantedBounds.setLocation(newPos);
//
// return true;
}
/**
* Fix the given bounds of specified component so it does not go completely off screen.<br>
* Returns <code>true</code> if component position has be adjusted.
*/
public static boolean fixPosition(Component component)
{
final Rectangle bounds = component.getBounds();
if (fixPosition(component, bounds))
{
component.setBounds(bounds);
return true;
}
return false;
}
public static int getComponentIndex(Component c)
{
if (c != null)
{
final Container container = c.getParent();
if (container != null)
for (int i = 0; i < container.getComponentCount(); i++)
if (container.getComponent(i) == c)
return i;
}
return -1;
}
public static Point convertPoint(Component src, Point p, Component dst)
{
return SwingUtilities.convertPoint(src, p, dst);
}
public static Point convertPointFromScreen(Point p, Component c)
{
final Point result = new Point(p);
SwingUtilities.convertPointFromScreen(result, c);
return result;
}
public static Point convertPointToScreen(Point p, Component c)
{
final Point result = new Point(p);
SwingUtilities.convertPointToScreen(result, c);
return result;
}
public static boolean isOutside(Component c, Rectangle r)
{
return !r.intersects(c.getBounds());
}
public static boolean isInside(Component c, Rectangle r)
{
return r.contains(c.getBounds());
}
public static void increaseFontSize(Component c, int value)
{
setFontSize(c, c.getFont().getSize() + value);
}
public static void decreaseFontSize(Component c, int value)
{
setFontSize(c, c.getFont().getSize() - value);
}
public static void setFontSize(Component c, int fontSize)
{
c.setFont(FontUtil.setSize(c.getFont(), fontSize));
}
public static void setFontStyle(Component c, int fontStyle)
{
c.setFont(FontUtil.setStyle(c.getFont(), fontStyle));
}
public static void setFontBold(Component c)
{
setFontStyle(c, c.getFont().getStyle() | Font.BOLD);
}
public static void setJTextPaneFont(JTextPane tp, Font font, Color c)
{
final MutableAttributeSet attrs = tp.getInputAttributes();
// Set the font family, size, and style, based on properties of
// the Font object. Note that JTextPane supports a number of
// character attributes beyond those supported by the Font class.
// For example, underline, strike-through, super- and sub-script.
StyleConstants.setFontFamily(attrs, font.getFamily());
StyleConstants.setFontSize(attrs, font.getSize());
StyleConstants.setItalic(attrs, (font.getStyle() & Font.ITALIC) != 0);
StyleConstants.setBold(attrs, (font.getStyle() & Font.BOLD) != 0);
// Set the font color
StyleConstants.setForeground(attrs, c);
// Retrieve the pane's document object
StyledDocument doc = tp.getStyledDocument();
// Replace the style for the entire document. We exceed the length
// of the document by 1 so that text entered at the end of the
// document uses the attributes.
doc.setCharacterAttributes(0, doc.getLength() + 1, attrs, false);
}
public static void setTickMarkers(JSlider slider)
{
final int min = slider.getMinimum();
final int max = slider.getMaximum();
final int delta = max - min;
if (delta > 0)
{
final int sliderSize;
if (slider.getOrientation() == SwingConstants.HORIZONTAL)
sliderSize = slider.getPreferredSize().width;
else
sliderSize = slider.getPreferredSize().height;
// adjust ticks space on slider
final int majTick = findBestMajTickSpace(sliderSize, delta);
slider.setMinorTickSpacing(Math.max(1, majTick / 5));
slider.setMajorTickSpacing(majTick);
slider.setLabelTable(slider.createStandardLabels(slider.getMajorTickSpacing(), majTick));
}
}
private static int findBestMajTickSpace(int sliderSize, int delta)
{
final int values[] = {1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000};
// wanted a major tick each ~40 pixels
final int wantedMajTickSpace = delta / (sliderSize / 40);
int min = Integer.MAX_VALUE;
int bestValue = 1;
// try with our predefined values
for (int value : values)
{
final int dx = Math.abs(value - wantedMajTickSpace);
if (dx < min)
{
min = dx;
bestValue = value;
}
}
return bestValue;
}
/**
* Breaks the list of items in the specified menu, by creating sub-menus containing the
* specified number of items, and a "More..." menu to access subsequent items.
*
* @param menu
* the menu to break into smaller sub-menus
* @param maxItemsPerMenu
* the maximum number of items to display in each sub-menu
*/
public static void split(JMenu menu, int maxItemsPerMenu)
{
ArrayList<Component> components = new ArrayList<Component>(Arrays.asList(menu.getPopupMenu().getComponents()));
if (components.size() > maxItemsPerMenu)
{
menu.removeAll();
JMenu currentMenu = menu;
while (components.size() > 0)
{
int n = Math.min(components.size(), maxItemsPerMenu - 1);
for (int i = 0; i < n; i++)
currentMenu.add(components.remove(0));
if (components.size() > 0)
currentMenu = (JMenu) currentMenu.add(new JMenu("More..."));
}
if (components.size() > 0)
System.err.println(components.size() + " are remaining !!");
}
// do this recursively for sub-menus
for (Component component : menu.getPopupMenu().getComponents())
{
if (component instanceof JMenu)
split((JMenu) component, maxItemsPerMenu);
}
}
public static TreePath buildTreePath(TreeNode node)
{
final ArrayList<TreeNode> nodes = new ArrayList<TreeNode>();
nodes.add(node);
TreeNode n = node;
while (n.getParent() != null)
{
n = n.getParent();
nodes.add(n);
}
Collections.reverse(nodes);
return new TreePath(nodes.toArray());
}
public static void expandAllTree(JTree tree)
{
for (int i = 0; i < tree.getRowCount(); i++)
tree.expandRow(i);
}
public static HyperlinkListener getDefaultHyperlinkListener()
{
return new HyperlinkListener()
{
@Override
public void hyperlinkUpdate(HyperlinkEvent e)
{
if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED)
NetworkUtil.openURL(e.getURL());
}
};
}
public static boolean isMaximized(Frame f)
{
return (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH;
}
public static void setMaximized(Frame f, boolean b)
{
// only relevant if state changed
if (isMaximized(f) ^ b)
{
if (b)
f.setExtendedState(Frame.MAXIMIZED_BOTH);
else
f.setExtendedState(Frame.NORMAL);
}
}
public static boolean isMinimized(Frame f)
{
return (f.getExtendedState() & Frame.ICONIFIED) == Frame.ICONIFIED;
}
public static void setMinimized(Frame f, boolean b)
{
// only relevant if state changed
if (isMinimized(f) ^ b)
{
if (b)
f.setExtendedState(Frame.ICONIFIED);
else
f.setExtendedState(Frame.NORMAL);
}
}
}