001/* 002 * Copyright 2010-2015 Institut Pasteur. 003 * 004 * This file is part of Icy. 005 * 006 * Icy is free software: you can redistribute it and/or modify 007 * it under the terms of the GNU General Public License as published by 008 * the Free Software Foundation, either version 3 of the License, or 009 * (at your option) any later version. 010 * 011 * Icy is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 014 * GNU General Public License for more details. 015 * 016 * You should have received a copy of the GNU General Public License 017 * along with Icy. If not, see <http://www.gnu.org/licenses/>. 018 */ 019package icy.gui.util; 020 021import icy.gui.frame.IcyFrame; 022import icy.network.NetworkUtil; 023import icy.system.SystemUtil; 024 025import java.awt.Color; 026import java.awt.Component; 027import java.awt.Container; 028import java.awt.Dimension; 029import java.awt.Font; 030import java.awt.Frame; 031import java.awt.GraphicsDevice; 032import java.awt.Point; 033import java.awt.Rectangle; 034import java.awt.Window; 035import java.awt.geom.Point2D; 036import java.util.ArrayList; 037import java.util.Arrays; 038import java.util.Collections; 039import java.util.List; 040 041import javax.swing.JInternalFrame; 042import javax.swing.JMenu; 043import javax.swing.JSlider; 044import javax.swing.JTextPane; 045import javax.swing.JTree; 046import javax.swing.SwingConstants; 047import javax.swing.SwingUtilities; 048import javax.swing.event.HyperlinkEvent; 049import javax.swing.event.HyperlinkListener; 050import javax.swing.text.MutableAttributeSet; 051import javax.swing.text.StyleConstants; 052import javax.swing.text.StyledDocument; 053import javax.swing.tree.TreeNode; 054import javax.swing.tree.TreePath; 055 056/** 057 * General component utilities class. 058 * 059 * @author Stephane 060 */ 061public class ComponentUtil 062{ 063 public static void setPreferredWidth(Component c, int w) 064 { 065 c.setPreferredSize(new Dimension(w, c.getPreferredSize().height)); 066 } 067 068 public static void setPreferredHeight(Component c, int h) 069 { 070 c.setPreferredSize(new Dimension(c.getPreferredSize().width, h)); 071 } 072 073 public static void setFixedSize(Component c, Dimension d) 074 { 075 c.setMinimumSize(d); 076 c.setMaximumSize(d); 077 c.setPreferredSize(d); 078 } 079 080 public static void setFixedWidth(Component c, int w) 081 { 082 c.setMinimumSize(new Dimension(w, 0)); 083 c.setMaximumSize(new Dimension(w, 65535)); 084 c.setPreferredSize(new Dimension(w, c.getPreferredSize().height)); 085 } 086 087 public static void setFixedHeight(Component c, int h) 088 { 089 c.setMinimumSize(new Dimension(0, h)); 090 c.setMaximumSize(new Dimension(65535, h)); 091 c.setPreferredSize(new Dimension(c.getPreferredSize().width, h)); 092 } 093 094 public static void setPreferredWidth(IcyFrame frm, int w) 095 { 096 frm.setPreferredSize(new Dimension(w, frm.getPreferredSize().height)); 097 } 098 099 public static void setPreferredHeight(IcyFrame frm, int h) 100 { 101 frm.setPreferredSize(new Dimension(frm.getPreferredSize().width, h)); 102 } 103 104 public static void setFixedSize(IcyFrame frm, Dimension d) 105 { 106 frm.setMinimumSize(d); 107 frm.setMaximumSize(d); 108 frm.setPreferredSize(d); 109 } 110 111 public static void setFixedWidth(IcyFrame frm, int w) 112 { 113 frm.setMinimumSize(new Dimension(w, 0)); 114 frm.setMaximumSize(new Dimension(w, 65535)); 115 frm.setPreferredSize(new Dimension(w, frm.getPreferredSize().height)); 116 } 117 118 public static void setFixedHeight(IcyFrame frm, int h) 119 { 120 frm.setMinimumSize(new Dimension(0, h)); 121 frm.setMaximumSize(new Dimension(65535, h)); 122 frm.setPreferredSize(new Dimension(frm.getPreferredSize().width, h)); 123 } 124 125 public static void removeFixedSize(Component c) 126 { 127 c.setMinimumSize(new Dimension(0, 0)); 128 c.setMaximumSize(new Dimension(65535, 65535)); 129 } 130 131 /** 132 * Center specified component relative to its parent 133 */ 134 public static void center(Component comp) 135 { 136 final Container parent = comp.getParent(); 137 138 if (parent != null) 139 { 140 final int x = (parent.getWidth() - comp.getWidth()) / 2; 141 final int y = (parent.getHeight() - comp.getHeight()) / 2; 142 143 // avoid negative coordinates when centering 144 comp.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y); 145 } 146 } 147 148 /** 149 * Center specified windows relative to its parent 150 */ 151 public static void center(Window window) 152 { 153 window.setLocationRelativeTo(window.getParent()); 154 } 155 156 /** 157 * Center specified JInternalFrame 158 */ 159 public static void center(JInternalFrame frame) 160 { 161 center((Component) frame); 162 } 163 164 /** 165 * Center the Window on specified point 166 */ 167 public static void centerOn(Window window, Point position) 168 { 169 final int x = position.x - (window.getWidth() / 2); 170 final int y = position.y - (window.getHeight() / 2); 171 172 // avoid negative coordinates when centering 173 window.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y); 174 } 175 176 /** 177 * Center the JInternalFrame on specified point 178 */ 179 public static void centerOn(JInternalFrame f, Point position) 180 { 181 centerOn((Component) f, position); 182 } 183 184 /** 185 * Center specified component relative to its parent 186 */ 187 public static void centerOn(Component comp, Point position) 188 { 189 final int x = position.x - (comp.getWidth() / 2); 190 final int y = position.y - (comp.getHeight() / 2); 191 192 // avoid negative coordinates when centering 193 comp.setLocation((x < 0) ? 0 : x, (y < 0) ? 0 : y); 194 } 195 196 /** 197 * Use f.center() instead 198 * 199 * @deprecated 200 */ 201 @Deprecated 202 public static void center(IcyFrame f) 203 { 204 f.center(); 205 } 206 207 public static void center(Component dst, Component src) 208 { 209 dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2), 210 src.getY() + ((src.getHeight() - dst.getHeight()) / 2)); 211 } 212 213 public static void center(IcyFrame dst, Component src) 214 { 215 dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2), 216 src.getY() + ((src.getHeight() - dst.getHeight()) / 2)); 217 } 218 219 public static void center(Component dst, IcyFrame src) 220 { 221 dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2), 222 src.getY() + ((src.getHeight() - dst.getHeight()) / 2)); 223 } 224 225 public static void center(IcyFrame dst, IcyFrame src) 226 { 227 dst.setLocation(src.getX() + ((src.getWidth() - dst.getWidth()) / 2), 228 src.getY() + ((src.getHeight() - dst.getHeight()) / 2)); 229 } 230 231 /** 232 * Returns the center position of the specified component. 233 */ 234 public static Point2D.Double getCenter(Component c) 235 { 236 if (c != null) 237 { 238 final Rectangle r = c.getBounds(); 239 return new Point2D.Double(r.getX() + (r.getWidth() / 2d), r.getY() + (r.getHeight() / 2d)); 240 } 241 242 return new Point2D.Double(0d, 0d); 243 } 244 245 /** 246 * Returns all screen device where the specified component is currently displayed.<br> 247 * Can return an empty list if given region do not intersect any screen device. 248 * 249 * @see #getScreen(Component) 250 * @see SystemUtil#getScreenDevices(Rectangle) 251 */ 252 public static List<GraphicsDevice> getScreens(Component c) 253 { 254 return SystemUtil.getScreenDevices(c.getBounds()); 255 } 256 257 /** 258 * Returns the main screen device where the specified component is currently displayed.<br> 259 * Can return <code>null</code> if component is not located on any screen device. 260 * 261 * @see #getScreens(Component) 262 * @see SystemUtil#getScreenDevice(Rectangle) 263 * @see SystemUtil#getScreenDevice(Point) 264 */ 265 public static GraphicsDevice getScreen(Component c) 266 { 267 final Point2D.Double pos2d = getCenter(c); 268 final Point pos = new Point((int) pos2d.getX(), (int) pos2d.getY()); 269 270 // get screen on Component center first (better for multi screen) 271 GraphicsDevice result = SystemUtil.getScreenDevice(pos); 272 273 // cannot retrieve screen on center, just use component bounds then 274 if (result == null) 275 result = SystemUtil.getScreenDevice(c.getBounds()); 276 277 return result; 278 } 279 280 /** 281 * Returns the new location of wanted bounds so it does not go outside the specified screen bounds.<br> 282 * Returns <code>null</code> if the wanted bounds doesn't need position adjustment. 283 */ 284 public static Point fixPosition(Rectangle wantedBounds, Rectangle screenBounds) 285 { 286 if (screenBounds.isEmpty()) 287 return null; 288 289 final int margeX = 80; 290 final int margeY = 40; 291 292 int x = wantedBounds.x; 293 int y = wantedBounds.y; 294 int sx = screenBounds.x; 295 int sy = screenBounds.y; 296 int minX = (sx - wantedBounds.width) + margeX; 297 int maxX = (sx + screenBounds.width) - margeX; 298 int minY = sy; 299// int minY = (sy - wantedBounds.height) + margeY; 300 int maxY = (sy + screenBounds.height) - margeY; 301 302 if (y < minY) 303 y = minY; 304 else if (y > maxY) 305 y = maxY; 306 if (x < minX) 307 x = minX; 308 else if (x > maxX) 309 x = maxX; 310 311 final Point pos = wantedBounds.getLocation(); 312 313 // position changed ? 314 if ((pos.x != x) || (pos.y != y)) 315 return new Point(x, y); 316 317 return null; 318 } 319 320 /** 321 * Fix the given bounds of specified component so it does not go completely off screen.<br> 322 * Returns <code>true</code> if the bounds position has be adjusted. 323 */ 324 public static boolean fixPosition(Component component, Rectangle wantedBounds) 325 { 326 final List<GraphicsDevice> screens = SystemUtil.getScreenDevices(); 327 328 // headless mode probably 329 if (screens.isEmpty()) 330 return false; 331 332 Point newPos = null; 333 boolean useMainScreen = false; 334 335 for (GraphicsDevice screen : screens) 336 { 337 final Point pt = fixPosition(wantedBounds, SystemUtil.getScreenBounds(screen, true)); 338 339 // this screen accept current position --> no need to adjust position 340 if (pt == null) 341 return false; 342 343 // we already have an adjusted position ? 344 if (newPos != null) 345 useMainScreen = true; 346 else 347 newPos = pt; 348 } 349 350 // multiple possible position adjustment ? --> use main screen 351 if (useMainScreen) 352 newPos = fixPosition(wantedBounds, SystemUtil.getScreenBounds(getScreen(component), true)); 353 354 // got a new position ? --> set it 355 if (newPos != null) 356 { 357 wantedBounds.setLocation(newPos); 358 return true; 359 } 360 361 return false; 362 363// final List<GraphicsDevice> screens = getScreens(component); 364// Point newPos = null; 365// boolean useMainScreen = false; 366// 367// for (GraphicsDevice screen : screens) 368// { 369// final Point pt = fixPosition(wantedBounds, SystemUtil.getScreenBounds(screen, true)); 370// 371// // this screen accept current position --> no need to adjust position 372// if (pt == null) 373// return false; 374// 375// // we already have an adjusted position ? 376// if (newPos != null) 377// useMainScreen = true; 378// else 379// newPos = pt; 380// } 381// 382// // use main screen 383// if (screens.isEmpty()) 384// useMainScreen = true; 385// 386// // multiple possible position adjustment ? --> use main screen 387// if (useMainScreen) 388// newPos = fixPosition(wantedBounds, SystemUtil.getScreenBounds(getScreen(component), true)); 389// 390// // got a new position ? --> set it 391// if (newPos != null) 392// wantedBounds.setLocation(newPos); 393// 394// return true; 395 } 396 397 /** 398 * Fix the given bounds of specified component so it does not go completely off screen.<br> 399 * Returns <code>true</code> if component position has be adjusted. 400 */ 401 public static boolean fixPosition(Component component) 402 { 403 final Rectangle bounds = component.getBounds(); 404 405 if (fixPosition(component, bounds)) 406 { 407 component.setBounds(bounds); 408 return true; 409 } 410 411 return false; 412 } 413 414 public static int getComponentIndex(Component c) 415 { 416 if (c != null) 417 { 418 final Container container = c.getParent(); 419 420 if (container != null) 421 for (int i = 0; i < container.getComponentCount(); i++) 422 if (container.getComponent(i) == c) 423 return i; 424 } 425 426 return -1; 427 } 428 429 public static Point convertPoint(Component src, Point p, Component dst) 430 { 431 return SwingUtilities.convertPoint(src, p, dst); 432 } 433 434 public static Point convertPointFromScreen(Point p, Component c) 435 { 436 final Point result = new Point(p); 437 438 SwingUtilities.convertPointFromScreen(result, c); 439 440 return result; 441 } 442 443 public static Point convertPointToScreen(Point p, Component c) 444 { 445 final Point result = new Point(p); 446 447 SwingUtilities.convertPointToScreen(result, c); 448 449 return result; 450 } 451 452 public static boolean isOutside(Component c, Rectangle r) 453 { 454 return !r.intersects(c.getBounds()); 455 } 456 457 public static boolean isInside(Component c, Rectangle r) 458 { 459 return r.contains(c.getBounds()); 460 } 461 462 public static void increaseFontSize(Component c, int value) 463 { 464 setFontSize(c, c.getFont().getSize() + value); 465 } 466 467 public static void decreaseFontSize(Component c, int value) 468 { 469 setFontSize(c, c.getFont().getSize() - value); 470 } 471 472 public static void setFontSize(Component c, int fontSize) 473 { 474 c.setFont(FontUtil.setSize(c.getFont(), fontSize)); 475 } 476 477 public static void setFontStyle(Component c, int fontStyle) 478 { 479 c.setFont(FontUtil.setStyle(c.getFont(), fontStyle)); 480 } 481 482 public static void setFontBold(Component c) 483 { 484 setFontStyle(c, c.getFont().getStyle() | Font.BOLD); 485 } 486 487 public static void setJTextPaneFont(JTextPane tp, Font font, Color c) 488 { 489 final MutableAttributeSet attrs = tp.getInputAttributes(); 490 491 // Set the font family, size, and style, based on properties of 492 // the Font object. Note that JTextPane supports a number of 493 // character attributes beyond those supported by the Font class. 494 // For example, underline, strike-through, super- and sub-script. 495 StyleConstants.setFontFamily(attrs, font.getFamily()); 496 StyleConstants.setFontSize(attrs, font.getSize()); 497 StyleConstants.setItalic(attrs, (font.getStyle() & Font.ITALIC) != 0); 498 StyleConstants.setBold(attrs, (font.getStyle() & Font.BOLD) != 0); 499 500 // Set the font color 501 StyleConstants.setForeground(attrs, c); 502 503 // Retrieve the pane's document object 504 StyledDocument doc = tp.getStyledDocument(); 505 506 // Replace the style for the entire document. We exceed the length 507 // of the document by 1 so that text entered at the end of the 508 // document uses the attributes. 509 doc.setCharacterAttributes(0, doc.getLength() + 1, attrs, false); 510 } 511 512 public static void setTickMarkers(JSlider slider) 513 { 514 final int min = slider.getMinimum(); 515 final int max = slider.getMaximum(); 516 final int delta = max - min; 517 518 if (delta > 0) 519 { 520 final int sliderSize; 521 if (slider.getOrientation() == SwingConstants.HORIZONTAL) 522 sliderSize = slider.getPreferredSize().width; 523 else 524 sliderSize = slider.getPreferredSize().height; 525 526 // adjust ticks space on slider 527 final int majTick = findBestMajTickSpace(sliderSize, delta); 528 529 slider.setMinorTickSpacing(Math.max(1, majTick / 5)); 530 slider.setMajorTickSpacing(majTick); 531 slider.setLabelTable(slider.createStandardLabels(slider.getMajorTickSpacing(), majTick)); 532 } 533 } 534 535 private static int findBestMajTickSpace(int sliderSize, int delta) 536 { 537 final int values[] = {1, 2, 5, 10, 20, 25, 50, 100, 200, 250, 500, 1000, 2000, 2500, 5000}; 538 // wanted a major tick each ~40 pixels 539 final int wantedMajTickSpace = delta / (sliderSize / 40); 540 541 int min = Integer.MAX_VALUE; 542 int bestValue = 1; 543 544 // try with our predefined values 545 for (int value : values) 546 { 547 final int dx = Math.abs(value - wantedMajTickSpace); 548 549 if (dx < min) 550 { 551 min = dx; 552 bestValue = value; 553 } 554 } 555 556 return bestValue; 557 } 558 559 /** 560 * Breaks the list of items in the specified menu, by creating sub-menus containing the 561 * specified number of items, and a "More..." menu to access subsequent items. 562 * 563 * @param menu 564 * the menu to break into smaller sub-menus 565 * @param maxItemsPerMenu 566 * the maximum number of items to display in each sub-menu 567 */ 568 public static void split(JMenu menu, int maxItemsPerMenu) 569 { 570 ArrayList<Component> components = new ArrayList<Component>(Arrays.asList(menu.getPopupMenu().getComponents())); 571 572 if (components.size() > maxItemsPerMenu) 573 { 574 menu.removeAll(); 575 576 JMenu currentMenu = menu; 577 578 while (components.size() > 0) 579 { 580 int n = Math.min(components.size(), maxItemsPerMenu - 1); 581 582 for (int i = 0; i < n; i++) 583 currentMenu.add(components.remove(0)); 584 585 if (components.size() > 0) 586 currentMenu = (JMenu) currentMenu.add(new JMenu("More...")); 587 } 588 589 if (components.size() > 0) 590 System.err.println(components.size() + " are remaining !!"); 591 } 592 593 // do this recursively for sub-menus 594 for (Component component : menu.getPopupMenu().getComponents()) 595 { 596 if (component instanceof JMenu) 597 split((JMenu) component, maxItemsPerMenu); 598 } 599 } 600 601 public static TreePath buildTreePath(TreeNode node) 602 { 603 final ArrayList<TreeNode> nodes = new ArrayList<TreeNode>(); 604 605 nodes.add(node); 606 607 TreeNode n = node; 608 while (n.getParent() != null) 609 { 610 n = n.getParent(); 611 nodes.add(n); 612 } 613 614 Collections.reverse(nodes); 615 616 return new TreePath(nodes.toArray()); 617 } 618 619 public static void expandAllTree(JTree tree) 620 { 621 for (int i = 0; i < tree.getRowCount(); i++) 622 tree.expandRow(i); 623 } 624 625 public static HyperlinkListener getDefaultHyperlinkListener() 626 { 627 return new HyperlinkListener() 628 { 629 @Override 630 public void hyperlinkUpdate(HyperlinkEvent e) 631 { 632 if (e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) 633 NetworkUtil.openURL(e.getURL()); 634 } 635 }; 636 } 637 638 public static boolean isMaximized(Frame f) 639 { 640 return (f.getExtendedState() & Frame.MAXIMIZED_BOTH) == Frame.MAXIMIZED_BOTH; 641 } 642 643 public static void setMaximized(Frame f, boolean b) 644 { 645 // only relevant if state changed 646 if (isMaximized(f) ^ b) 647 { 648 if (b) 649 f.setExtendedState(Frame.MAXIMIZED_BOTH); 650 else 651 f.setExtendedState(Frame.NORMAL); 652 } 653 } 654 655 public static boolean isMinimized(Frame f) 656 { 657 return (f.getExtendedState() & Frame.ICONIFIED) == Frame.ICONIFIED; 658 } 659 660 public static void setMinimized(Frame f, boolean b) 661 { 662 // only relevant if state changed 663 if (isMinimized(f) ^ b) 664 { 665 if (b) 666 f.setExtendedState(Frame.ICONIFIED); 667 else 668 f.setExtendedState(Frame.NORMAL); 669 } 670 } 671}