001/* 002 * $Id: SwingXUtilities.java 3886 2010-11-16 16:28:58Z kschaefe $ 003 * 004 * Copyright 2008 Sun Microsystems, Inc., 4150 Network Circle, 005 * Santa Clara, California 95054, U.S.A. All rights reserved. 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 020 */ 021package org.jdesktop.swingx; 022 023import java.awt.Color; 024import java.awt.Component; 025import java.awt.Container; 026import java.awt.Dimension; 027import java.awt.Font; 028import java.awt.Graphics2D; 029import java.awt.Insets; 030import java.awt.Point; 031import java.awt.Rectangle; 032import java.awt.Window; 033import java.awt.event.InputEvent; 034import java.awt.event.MouseEvent; 035import java.io.IOException; 036import java.io.StringReader; 037import java.lang.reflect.InvocationTargetException; 038import java.lang.reflect.Method; 039import java.util.Locale; 040import java.util.concurrent.Callable; 041import java.util.concurrent.ExecutionException; 042import java.util.concurrent.FutureTask; 043 044import javax.swing.InputMap; 045import javax.swing.JComponent; 046import javax.swing.JList; 047import javax.swing.JPopupMenu; 048import javax.swing.KeyStroke; 049import javax.swing.ListCellRenderer; 050import javax.swing.ListModel; 051import javax.swing.ListSelectionModel; 052import javax.swing.MenuElement; 053import javax.swing.RepaintManager; 054import javax.swing.SwingUtilities; 055import javax.swing.plaf.ComponentInputMapUIResource; 056import javax.swing.plaf.UIResource; 057import javax.swing.text.html.HTMLDocument; 058 059import org.jdesktop.swingx.painter.Painter; 060import org.jdesktop.swingx.plaf.PainterUIResource; 061 062/** 063 * A collection of utility methods for Swing(X) classes. 064 * 065 * <ul> 066 * PENDING JW: think about location of this class and/or its methods, Options: 067 * 068 * <li> move this class to the swingx utils package which already has a bunch of xxUtils 069 * <li> move methods between xxUtils classes as appropriate (one window/comp related util) 070 * <li> keep here in swingx (consistent with swingutilities in core) 071 * </ul> 072 * @author Karl George Schaefer 073 */ 074public final class SwingXUtilities { 075 private SwingXUtilities() { 076 //does nothing 077 } 078 079 080 /** 081 * A helper for creating and updating key bindings for components with 082 * mnemonics. The {@code pressed} action will be invoked when the mnemonic 083 * is activated. 084 * <p> 085 * TODO establish an interface for the mnemonic properties, such as {@code 086 * MnemonicEnabled} and change signature to {@code public static <T extends 087 * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String 088 * pressed)} 089 * 090 * @param c 091 * the component bindings to update 092 * @param pressed 093 * the name of the action in the action map to invoke when the 094 * mnemonic is pressed 095 * @throws NullPointerException 096 * if the component is {@code null} 097 */ 098 public static void updateMnemonicBinding(JComponent c, String pressed) { 099 updateMnemonicBinding(c, pressed, null); 100 } 101 102 /** 103 * A helper for creating and updating key bindings for components with 104 * mnemonics. The {@code pressed} action will be invoked when the mnemonic 105 * is activated and the {@code released} action will be invoked when the 106 * mnemonic is deactivated. 107 * <p> 108 * TODO establish an interface for the mnemonic properties, such as {@code 109 * MnemonicEnabled} and change signature to {@code public static <T extends 110 * JComponent & MnemonicEnabled> void updateMnemonicBinding(T c, String 111 * pressed, String released)} 112 * 113 * @param c 114 * the component bindings to update 115 * @param pressed 116 * the name of the action in the action map to invoke when the 117 * mnemonic is pressed 118 * @param released 119 * the name of the action in the action map to invoke when the 120 * mnemonic is released (if the action is a toggle style, then 121 * this parameter should be {@code null}) 122 * @throws NullPointerException 123 * if the component is {@code null} 124 */ 125 public static void updateMnemonicBinding(JComponent c, String pressed, String released) { 126 Class<?> clazz = c.getClass(); 127 int m = -1; 128 129 try { 130 Method mtd = clazz.getMethod("getMnemonic"); 131 m = (Integer) mtd.invoke(c); 132 } catch (RuntimeException e) { 133 throw e; 134 } catch (Exception e) { 135 throw new IllegalArgumentException("unable to access mnemonic", e); 136 } 137 138 InputMap map = SwingUtilities.getUIInputMap(c, 139 JComponent.WHEN_IN_FOCUSED_WINDOW); 140 141 if (m != 0) { 142 if (map == null) { 143 map = new ComponentInputMapUIResource(c); 144 SwingUtilities.replaceUIInputMap(c, 145 JComponent.WHEN_IN_FOCUSED_WINDOW, map); 146 } 147 148 map.clear(); 149 150 //TODO is ALT_MASK right for all platforms? 151 map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, false), 152 pressed); 153 map.put(KeyStroke.getKeyStroke(m, InputEvent.ALT_MASK, true), 154 released); 155 map.put(KeyStroke.getKeyStroke(m, 0, true), released); 156 } else { 157 if (map != null) { 158 map.clear(); 159 } 160 } 161 } 162 163 static <C extends JComponent & BackgroundPaintable> void installBackground(C comp, Color color) { 164 if (isUIInstallable(color)) { 165 //only handle UIResource, if null then painter isn't painted; this allows optimized code paths 166 if (comp.getBackgroundPainter() instanceof UIResource) { 167 comp.setBackgroundPainter(new PainterUIResource<JComponent>(new BackgroundPainter(color))); 168 } 169 //does nothing otherwise; do not install UIResource Color over a non-UIResource Painter 170 } else { 171 comp.setBackgroundPainter(new BackgroundPainter(color)); 172 } 173 } 174 175 @SuppressWarnings("unchecked") 176 static <C extends JComponent & BackgroundPaintable> void paintBackground(C comp, Graphics2D g) { 177 Painter<? super C> painter = comp.getBackgroundPainter(); 178 179 if (painter instanceof BackgroundPainter) { 180 //ignore paintBorderInsets for BackgroundPainter 181 painter.paint(g, comp, comp.getWidth(), comp.getHeight()); 182 } else if (painter != null) { 183 if (comp.isPaintBorderInsets()) { 184 painter.paint(g, comp, comp.getWidth(), comp.getHeight()); 185 } else { 186 Insets insets = comp.getInsets(); 187 g.translate(insets.left, insets.top); 188 painter.paint(g, comp, comp.getWidth() - insets.left - insets.right, 189 comp.getHeight() - insets.top - insets.bottom); 190 g.translate(-insets.left, -insets.top); 191 } 192 } 193 } 194 195 private static Component[] getChildren(Component c) { 196 Component[] children = null; 197 198 if (c instanceof MenuElement) { 199 MenuElement[] elements = ((MenuElement) c).getSubElements(); 200 children = new Component[elements.length]; 201 202 for (int i = 0; i < elements.length; i++) { 203 children[i] = elements[i].getComponent(); 204 } 205 } else if (c instanceof Container) { 206 children = ((Container) c).getComponents(); 207 } 208 209 return children; 210 } 211 212 /** 213 * Enables or disables of the components in the tree starting with {@code c}. 214 * 215 * @param c 216 * the starting component 217 * @param enabled 218 * {@code true} if the component is to enabled; {@code false} otherwise 219 */ 220 public static void setComponentTreeEnabled(Component c, boolean enabled) { 221 c.setEnabled(enabled); 222 223 Component[] children = getChildren(c); 224 225 if (children != null) { 226 for(int i = 0; i < children.length; i++) { 227 setComponentTreeEnabled(children[i], enabled); 228 } 229 } 230 } 231 232 /** 233 * Sets the locale for an entire component hierarchy to the specified 234 * locale. 235 * 236 * @param c 237 * the starting component 238 * @param locale 239 * the locale to set 240 */ 241 public static void setComponentTreeLocale(Component c, Locale locale) { 242 c.setLocale(locale); 243 244 Component[] children = getChildren(c); 245 246 if (children != null) { 247 for(int i = 0; i < children.length; i++) { 248 setComponentTreeLocale(children[i], locale); 249 } 250 } 251 } 252 253 /** 254 * Sets the background for an entire component hierarchy to the specified 255 * color. 256 * 257 * @param c 258 * the starting component 259 * @param color 260 * the color to set 261 */ 262 public static void setComponentTreeBackground(Component c, Color color) { 263 c.setBackground(color); 264 265 Component[] children = getChildren(c); 266 267 if (children != null) { 268 for(int i = 0; i < children.length; i++) { 269 setComponentTreeBackground(children[i], color); 270 } 271 } 272 } 273 274 /** 275 * Sets the foreground for an entire component hierarchy to the specified 276 * color. 277 * 278 * @param c 279 * the starting component 280 * @param color 281 * the color to set 282 */ 283 public static void setComponentTreeForeground(Component c, Color color) { 284 c.setForeground(color); 285 286 Component[] children = getChildren(c); 287 288 if (children != null) { 289 for(int i = 0; i < children.length; i++) { 290 setComponentTreeForeground(children[i], color); 291 } 292 } 293 } 294 295 /** 296 * Sets the font for an entire component hierarchy to the specified font. 297 * 298 * @param c 299 * the starting component 300 * @param font 301 * the font to set 302 */ 303 public static void setComponentTreeFont(Component c, Font font) { 304 c.setFont(font); 305 306 Component[] children = getChildren(c); 307 308 if (children != null) { 309 for(int i = 0; i < children.length; i++) { 310 setComponentTreeFont(children[i], font); 311 } 312 } 313 } 314 315 private static String STYLESHEET = 316 "body { margin-top: 0; margin-bottom: 0; margin-left: 0; margin-right: 0;" 317 + " font-family: %s; font-size: %dpt; }" 318 + "a, p, li { margin-top: 0; margin-bottom: 0; margin-left: 0;" 319 + " margin-right: 0; font-family: %s; font-size: %dpt; }"; 320 321 /** 322 * Sets the font used for HTML displays to the specified font. Components 323 * that display HTML do not necessarily honor font properties, since the 324 * HTML document can override these values. Calling {@code setHtmlFont} 325 * after the data is set will force the HTML display to use the font 326 * specified to this method. 327 * 328 * @param doc 329 * the HTML document to update 330 * @param font 331 * the font to use 332 * @throws NullPointerException 333 * if any parameter is {@code null} 334 */ 335 public static void setHtmlFont(HTMLDocument doc, Font font) { 336 String stylesheet = String.format(STYLESHEET, font.getName(), 337 font.getSize(), font.getName(), font.getSize()); 338 339 try { 340 doc.getStyleSheet().loadRules(new StringReader(stylesheet), null); 341 } catch (IOException e) { 342 //this should never happen with our sheet 343 throw new IllegalStateException(e); 344 } 345 } 346 347 /** 348 * Updates the componentTreeUI of all top-level windows of the 349 * current application. 350 * 351 */ 352 public static void updateAllComponentTreeUIs() { 353// for (Frame frame : Frame.getFrames()) { 354// updateAllComponentTreeUIs(frame); 355// } 356 // JW: updated to new 1.6 api - returns all windows, owned and ownerless 357 for (Window window: Window.getWindows()) { 358 SwingUtilities.updateComponentTreeUI(window); 359 } 360 } 361 362 363 364 /** 365 * Updates the componentTreeUI of the given window and all its 366 * owned windows, recursively. 367 * 368 * 369 * @param window the window to update 370 */ 371 public static void updateAllComponentTreeUIs(Window window) { 372 SwingUtilities.updateComponentTreeUI(window); 373 for (Window owned : window.getOwnedWindows()) { 374 updateAllComponentTreeUIs(owned); 375 } 376 } 377 378 /** 379 * A version of {@link SwingUtilities#invokeLater(Runnable)} that supports return values. 380 * 381 * @param <T> 382 * the return type of the callable 383 * @param callable 384 * the callable to execute 385 * @return a future task for accessing the return value 386 * @see Callable 387 */ 388 public static <T> FutureTask<T> invokeLater(Callable<T> callable) { 389 FutureTask<T> task = new FutureTask<T>(callable); 390 391 SwingUtilities.invokeLater(task); 392 393 return task; 394 } 395 396 /** 397 * A version of {@link SwingUtilities#invokeAndWait(Runnable)} that supports return values. 398 * 399 * @param <T> 400 * the return type of the callable 401 * @param callable 402 * the callable to execute 403 * @return the value returned by the callable 404 * @throws InterruptedException 405 * if we're interrupted while waiting for the event dispatching thread to finish 406 * executing {@code callable.call()} 407 * @throws InvocationTargetException 408 * if an exception is thrown while running {@code callable} 409 * @see Callable 410 */ 411 public static <T> T invokeAndWait(Callable<T> callable) throws InterruptedException, 412 InvocationTargetException { 413 try { 414 //blocks until future returns 415 return invokeLater(callable).get(); 416 } catch (ExecutionException e) { 417 Throwable t = e.getCause(); 418 419 if (t instanceof RuntimeException) { 420 throw (RuntimeException) t; 421 } else if (t instanceof InvocationTargetException) { 422 throw (InvocationTargetException) t; 423 } else { 424 throw new InvocationTargetException(t); 425 } 426 } 427 } 428 429 /** 430 * An improved version of 431 * {@link SwingUtilities#getAncestorOfClass(Class, Component)}. This method 432 * traverses {@code JPopupMenu} invoker and uses generics to return an 433 * appropriately typed object. 434 * 435 * @param <T> 436 * the type of ancestor to find 437 * @param clazz 438 * the class instance of the ancestor to find 439 * @param c 440 * the component to start the search from 441 * @return an ancestor of the correct type or {@code null} if no such 442 * ancestor exists. This method also returns {@code null} if any 443 * parameter is {@code null}. 444 */ 445 @SuppressWarnings("unchecked") 446 public static <T> T getAncestor(Class<T> clazz, Component c) { 447 if (clazz == null || c == null) { 448 return null; 449 } 450 451 Component parent = c.getParent(); 452 453 while (parent != null && !(clazz.isInstance(parent))) { 454 parent = parent instanceof JPopupMenu 455 ? ((JPopupMenu) parent).getInvoker() : parent.getParent(); 456 } 457 458 return (T) parent; 459 } 460 461 /** 462 * Returns whether the component is part of the parent's 463 * container hierarchy. If a parent in the chain is of type 464 * JPopupMenu, the parent chain of its invoker is walked. 465 * 466 * @param focusOwner 467 * @param parent 468 * @return true if the component is contained under the parent's 469 * hierarchy, coping with JPopupMenus. 470 */ 471 public static boolean isDescendingFrom(Component focusOwner, Component parent) { 472 while (focusOwner != null) { 473 if (focusOwner instanceof JPopupMenu) { 474 focusOwner = ((JPopupMenu) focusOwner).getInvoker(); 475 if (focusOwner == null) { 476 return false; 477 } 478 } 479 if (focusOwner == parent) { 480 return true; 481 } 482 focusOwner = focusOwner.getParent(); 483 } 484 return false; 485 } 486 487 /** 488 * Obtains a {@code TranslucentRepaintManager} from the specified manager. 489 * If the current manager is a {@code TranslucentRepaintManager} or a 490 * {@code ForwardingRepaintManager} that contains a {@code 491 * TranslucentRepaintManager}, then the passed in manager is returned. 492 * Otherwise a new repaint manager is created and returned. 493 * 494 * @param delegate 495 * the current repaint manager 496 * @return a non-{@code null} {@code TranslucentRepaintManager} 497 * @throws NullPointerException if {@code delegate} is {@code null} 498 */ 499 static RepaintManager getTranslucentRepaintManager(RepaintManager delegate) { 500 RepaintManager manager = delegate; 501 502 while (manager != null && !manager.getClass().isAnnotationPresent(TranslucentRepaintManager.class)) { 503 if (manager instanceof ForwardingRepaintManager) { 504 manager = ((ForwardingRepaintManager) manager).getDelegateManager(); 505 } else { 506 manager = null; 507 } 508 } 509 510 return manager == null ? new RepaintManagerX(delegate) : delegate; 511 } 512 513 /** 514 * Checks and returns whether the given property should be replaced 515 * by the UI's default value. 516 * 517 * @param property the property to check. 518 * @return true if the given property should be replaced by the UI's 519 * default value, false otherwise. 520 */ 521 public static boolean isUIInstallable(Object property) { 522 return (property == null) || (property instanceof UIResource); 523 } 524 525//---- methods c&p'ed from SwingUtilities2 to reduce dependencies on sun packages 526 527 /** 528 * Updates lead and anchor selection index without changing the selection. 529 * 530 * Note: this is c&p'ed from SwingUtilities2 to not have any direct 531 * dependency. 532 * 533 * @param selectionModel the selection model to change lead/anchor 534 * @param lead the lead selection index 535 * @param anchor the anchor selection index 536 */ 537 public static void setLeadAnchorWithoutSelection( 538 ListSelectionModel selectionModel, int lead, int anchor) { 539 if (anchor == -1) { 540 anchor = lead; 541 } 542 if (lead == -1) { 543 selectionModel.setAnchorSelectionIndex(-1); 544 selectionModel.setLeadSelectionIndex(-1); 545 } else { 546 if (selectionModel.isSelectedIndex(lead)) 547 selectionModel.addSelectionInterval(lead, lead); 548 else { 549 selectionModel.removeSelectionInterval(lead, lead); 550 } 551 selectionModel.setAnchorSelectionIndex(anchor); 552 } 553 } 554 555 public static boolean shouldIgnore(MouseEvent mouseEvent, 556 JComponent component) { 557 return ((component == null) || (!(component.isEnabled())) 558 || (!(SwingUtilities.isLeftMouseButton(mouseEvent))) 559 || (mouseEvent.isConsumed())); 560 } 561 562 563 public static int loc2IndexFileList(JList list, Point point) { 564 int i = list.locationToIndex(point); 565 if (i != -1) { 566 Object localObject = list 567 .getClientProperty("List.isFileList"); 568 if ((localObject instanceof Boolean) 569 && (((Boolean) localObject).booleanValue()) 570 // PENDING JW: this isn't aware of sorting/filtering - fix! 571 && (!(pointIsInActualBounds(list, i, point)))) { 572 i = -1; 573 } 574 } 575 return i; 576 } 577 578 // PENDING JW: this isn't aware of sorting/filtering - fix! 579 private static boolean pointIsInActualBounds(JList list, int index, 580 Point point) { 581 ListCellRenderer renderer = list.getCellRenderer(); 582 ListModel model = list.getModel(); 583 Object element = model.getElementAt(index); 584 Component comp = renderer.getListCellRendererComponent(list, element, 585 index, false, false); 586 587 Dimension prefSize = comp.getPreferredSize(); 588 Rectangle cellBounds = list.getCellBounds(index, index); 589 if (!(comp.getComponentOrientation().isLeftToRight())) { 590 cellBounds.x += cellBounds.width - prefSize.width; 591 } 592 cellBounds.width = prefSize.width; 593 594 return cellBounds.contains(point); 595 } 596 597 public static void adjustFocus(JComponent component) { 598 if ((!(component.hasFocus())) && (component.isRequestFocusEnabled())) 599 component.requestFocus(); 600 } 601 602 public static int convertModifiersToDropAction(int modifiers, 603 int sourcActions) { 604 // PENDING JW: c'p from a decompiled SunDragSourceContextPeer 605 // PENDING JW: haha ... completely readable, right ;-) 606 int i = 0; 607 608 switch (modifiers & 0xC0) { 609 case 192: 610 i = 1073741824; 611 break; 612 case 128: 613 i = 1; 614 break; 615 case 64: 616 i = 2; 617 break; 618 default: 619 if ((sourcActions & 0x2) != 0) { 620 i = 2; 621 break; 622 } 623 if ((sourcActions & 0x1) != 0) { 624 i = 1; 625 break; 626 } 627 if ((sourcActions & 0x40000000) == 0) 628 break; 629 i = 1073741824; 630 } 631 632 // label88: 633 return (i & sourcActions); 634 } 635 636}