1 /* 2 * Copyright (c) 2002, 2018, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.swing; 27 28 import java.awt.AWTEvent; 29 import java.awt.Color; 30 import java.awt.Component; 31 import java.awt.Container; 32 import java.awt.Dimension; 33 import java.awt.EventQueue; 34 import java.awt.FocusTraversalPolicy; 35 import java.awt.Font; 36 import java.awt.FontMetrics; 37 import java.awt.Graphics; 38 import java.awt.Graphics2D; 39 import java.awt.GraphicsConfiguration; 40 import java.awt.GraphicsEnvironment; 41 import java.awt.Point; 42 import java.awt.PrintGraphics; 43 import java.awt.Rectangle; 44 import java.awt.RenderingHints; 45 import java.awt.Shape; 46 import java.awt.Toolkit; 47 import java.awt.event.InputEvent; 48 import java.awt.event.KeyEvent; 49 import java.awt.event.MouseEvent; 50 import java.awt.font.FontRenderContext; 51 import java.awt.font.GlyphVector; 52 import java.awt.font.LineBreakMeasurer; 53 import java.awt.font.TextAttribute; 54 import java.awt.font.TextHitInfo; 55 import java.awt.font.TextLayout; 56 import java.awt.geom.AffineTransform; 57 import java.awt.geom.Rectangle2D; 58 import java.awt.print.PrinterGraphics; 59 import java.beans.PropertyChangeEvent; 60 import java.io.BufferedInputStream; 61 import java.io.ByteArrayOutputStream; 62 import java.io.IOException; 63 import java.io.InputStream; 64 import java.lang.reflect.Modifier; 65 import java.security.AccessController; 66 import java.security.PrivilegedAction; 67 import java.text.AttributedCharacterIterator; 68 import java.text.AttributedString; 69 import java.text.BreakIterator; 70 import java.text.CharacterIterator; 71 import java.util.HashMap; 72 import java.util.Locale; 73 import java.util.Map; 74 import java.util.Objects; 75 import java.util.concurrent.Callable; 76 import java.util.concurrent.Future; 77 import java.util.concurrent.FutureTask; 78 79 import javax.swing.JComponent; 80 import javax.swing.JList; 81 import javax.swing.JTable; 82 import javax.swing.ListCellRenderer; 83 import javax.swing.ListSelectionModel; 84 import javax.swing.SwingUtilities; 85 import javax.swing.UIDefaults; 86 import javax.swing.UIManager; 87 import javax.swing.event.TreeModelEvent; 88 import javax.swing.table.TableCellRenderer; 89 import javax.swing.table.TableColumnModel; 90 import javax.swing.text.DefaultCaret; 91 import javax.swing.text.DefaultHighlighter; 92 import javax.swing.text.Highlighter; 93 import javax.swing.text.JTextComponent; 94 import javax.swing.tree.TreeModel; 95 import javax.swing.tree.TreePath; 96 97 import sun.awt.AWTAccessor; 98 import sun.awt.AWTPermissions; 99 import sun.awt.AppContext; 100 import sun.awt.SunToolkit; 101 import sun.font.FontDesignMetrics; 102 import sun.font.FontUtilities; 103 import sun.java2d.SunGraphicsEnvironment; 104 import sun.print.ProxyPrintGraphics; 105 106 import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING; 107 import static java.awt.RenderingHints.KEY_TEXT_LCD_CONTRAST; 108 import static java.awt.RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT; 109 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT; 110 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HBGR; 111 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB; 112 import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_OFF; 113 import static java.awt.geom.AffineTransform.TYPE_FLIP; 114 import static java.awt.geom.AffineTransform.TYPE_MASK_SCALE; 115 import static java.awt.geom.AffineTransform.TYPE_TRANSLATION; 116 117 /** 118 * A collection of utility methods for Swing. 119 * <p> 120 * <b>WARNING:</b> While this class is public, it should not be treated as 121 * public API and its API may change in incompatable ways between dot dot 122 * releases and even patch releases. You should not rely on this class even 123 * existing. 124 * 125 */ 126 public class SwingUtilities2 { 127 /** 128 * The {@code AppContext} key for our one {@code LAFState} 129 * instance. 130 */ 131 public static final Object LAF_STATE_KEY = 132 new StringBuffer("LookAndFeel State"); 133 134 public static final Object MENU_SELECTION_MANAGER_LISTENER_KEY = 135 new StringBuffer("MenuSelectionManager listener key"); 136 137 // Maintain a cache of CACHE_SIZE fonts and the left side bearing 138 // of the characters falling into the range MIN_CHAR_INDEX to 139 // MAX_CHAR_INDEX. The values in fontCache are created as needed. 140 private static LSBCacheEntry[] fontCache; 141 // Windows defines 6 font desktop properties, we will therefore only 142 // cache the metrics for 6 fonts. 143 private static final int CACHE_SIZE = 6; 144 // nextIndex in fontCache to insert a font into. 145 private static int nextIndex; 146 // LSBCacheEntry used to search in fontCache to see if we already 147 // have an entry for a particular font 148 private static LSBCacheEntry searchKey; 149 150 // getLeftSideBearing will consult all characters that fall in the 151 // range MIN_CHAR_INDEX to MAX_CHAR_INDEX. 152 private static final int MIN_CHAR_INDEX = (int)'W'; 153 private static final int MAX_CHAR_INDEX = (int)'W' + 1; 154 155 public static final FontRenderContext DEFAULT_FRC = 156 new FontRenderContext(null, false, false); 157 158 /** 159 * Attribute key for the content elements. If it is set on an element, the 160 * element is considered to be a line break. 161 */ 162 public static final String IMPLIED_CR = "CR"; 163 164 /** 165 * Used to tell a text component, being used as an editor for table 166 * or tree, how many clicks it took to start editing. 167 */ 168 private static final StringBuilder SKIP_CLICK_COUNT = 169 new StringBuilder("skipClickCount"); 170 171 @SuppressWarnings("unchecked") 172 public static void putAATextInfo(boolean lafCondition, 173 Map<Object, Object> map) { 174 SunToolkit.setAAFontSettingsCondition(lafCondition); 175 Toolkit tk = Toolkit.getDefaultToolkit(); 176 Object desktopHints = tk.getDesktopProperty(SunToolkit.DESKTOPFONTHINTS); 177 178 if (desktopHints instanceof Map) { 179 Map<Object, Object> hints = (Map<Object, Object>) desktopHints; 180 Object aaHint = hints.get(KEY_TEXT_ANTIALIASING); 181 if (aaHint == null 182 || aaHint == VALUE_TEXT_ANTIALIAS_OFF 183 || aaHint == VALUE_TEXT_ANTIALIAS_DEFAULT) { 184 return; 185 } 186 map.put(KEY_TEXT_ANTIALIASING, aaHint); 187 map.put(KEY_TEXT_LCD_CONTRAST, hints.get(KEY_TEXT_LCD_CONTRAST)); 188 } 189 } 190 191 /** Client Property key for the text maximal offsets for BasicMenuItemUI */ 192 public static final StringUIClientPropertyKey BASICMENUITEMUI_MAX_TEXT_OFFSET = 193 new StringUIClientPropertyKey ("maxTextOffset"); 194 195 // security stuff 196 private static final String UntrustedClipboardAccess = 197 "UNTRUSTED_CLIPBOARD_ACCESS_KEY"; 198 199 //all access to charsBuffer is to be synchronized on charsBufferLock 200 private static final int CHAR_BUFFER_SIZE = 100; 201 private static final Object charsBufferLock = new Object(); 202 private static char[] charsBuffer = new char[CHAR_BUFFER_SIZE]; 203 204 static { 205 fontCache = new LSBCacheEntry[CACHE_SIZE]; 206 } 207 208 /** 209 * Fill the character buffer cache. Return the buffer length. 210 */ 211 private static int syncCharsBuffer(String s) { 212 int length = s.length(); 213 if ((charsBuffer == null) || (charsBuffer.length < length)) { 214 charsBuffer = s.toCharArray(); 215 } else { 216 s.getChars(0, length, charsBuffer, 0); 217 } 218 return length; 219 } 220 221 /** 222 * checks whether TextLayout is required to handle characters. 223 * 224 * @param text characters to be tested 225 * @param start start 226 * @param limit limit 227 * @return {@code true} if TextLayout is required 228 * {@code false} if TextLayout is not required 229 */ 230 public static final boolean isComplexLayout(char[] text, int start, int limit) { 231 return FontUtilities.isComplexText(text, start, limit); 232 } 233 234 // 235 // WARNING WARNING WARNING WARNING WARNING WARNING 236 // Many of the following methods are invoked from older API. 237 // As this older API was not passed a Component, a null Component may 238 // now be passsed in. For example, SwingUtilities.computeStringWidth 239 // is implemented to call SwingUtilities2.stringWidth, the 240 // SwingUtilities variant does not take a JComponent, as such 241 // SwingUtilities2.stringWidth can be passed a null Component. 242 // In other words, if you add new functionality to these methods you 243 // need to gracefully handle null. 244 // 245 246 /** 247 * Returns the left side bearing of the first character of string. The 248 * left side bearing is calculated from the passed in 249 * FontMetrics. If the passed in String is less than one 250 * character {@code 0} is returned. 251 * 252 * @param c JComponent that will display the string 253 * @param fm FontMetrics used to measure the String width 254 * @param string String to get the left side bearing for. 255 * @throws NullPointerException if {@code string} is {@code null} 256 * 257 * @return the left side bearing of the first character of string 258 * or {@code 0} if the string is empty 259 */ 260 public static int getLeftSideBearing(JComponent c, FontMetrics fm, 261 String string) { 262 if ((string == null) || (string.length() == 0)) { 263 return 0; 264 } 265 return getLeftSideBearing(c, fm, string.charAt(0)); 266 } 267 268 /** 269 * Returns the left side bearing of the first character of string. The 270 * left side bearing is calculated from the passed in FontMetrics. 271 * 272 * @param c JComponent that will display the string 273 * @param fm FontMetrics used to measure the String width 274 * @param firstChar Character to get the left side bearing for. 275 */ 276 public static int getLeftSideBearing(JComponent c, FontMetrics fm, 277 char firstChar) { 278 int charIndex = (int) firstChar; 279 if (charIndex < MAX_CHAR_INDEX && charIndex >= MIN_CHAR_INDEX) { 280 byte[] lsbs = null; 281 282 FontRenderContext frc = getFontRenderContext(c, fm); 283 Font font = fm.getFont(); 284 synchronized (SwingUtilities2.class) { 285 LSBCacheEntry entry = null; 286 if (searchKey == null) { 287 searchKey = new LSBCacheEntry(frc, font); 288 } else { 289 searchKey.reset(frc, font); 290 } 291 // See if we already have an entry for this pair 292 for (LSBCacheEntry cacheEntry : fontCache) { 293 if (searchKey.equals(cacheEntry)) { 294 entry = cacheEntry; 295 break; 296 } 297 } 298 if (entry == null) { 299 // No entry for this pair, add it. 300 entry = searchKey; 301 fontCache[nextIndex] = searchKey; 302 searchKey = null; 303 nextIndex = (nextIndex + 1) % CACHE_SIZE; 304 } 305 return entry.getLeftSideBearing(firstChar); 306 } 307 } 308 return 0; 309 } 310 311 /** 312 * Returns the FontMetrics for the current Font of the passed 313 * in Graphics. This method is used when a Graphics 314 * is available, typically when painting. If a Graphics is not 315 * available the JComponent method of the same name should be used. 316 * <p> 317 * Callers should pass in a non-null JComponent, the exception 318 * to this is if a JComponent is not readily available at the time of 319 * painting. 320 * <p> 321 * This does not necessarily return the FontMetrics from the 322 * Graphics. 323 * 324 * @param c JComponent requesting FontMetrics, may be null 325 * @param g Graphics Graphics 326 */ 327 public static FontMetrics getFontMetrics(JComponent c, Graphics g) { 328 return getFontMetrics(c, g, g.getFont()); 329 } 330 331 332 /** 333 * Returns the FontMetrics for the specified Font. 334 * This method is used when a Graphics is available, typically when 335 * painting. If a Graphics is not available the JComponent method of 336 * the same name should be used. 337 * <p> 338 * Callers should pass in a non-null JComonent, the exception 339 * to this is if a JComponent is not readily available at the time of 340 * painting. 341 * <p> 342 * This does not necessarily return the FontMetrics from the 343 * Graphics. 344 * 345 * @param c JComponent requesting FontMetrics, may be null 346 * @param c Graphics Graphics 347 * @param font Font to get FontMetrics for 348 */ 349 @SuppressWarnings("deprecation") 350 public static FontMetrics getFontMetrics(JComponent c, Graphics g, 351 Font font) { 352 if (c != null) { 353 // Note: We assume that we're using the FontMetrics 354 // from the widget to layout out text, otherwise we can get 355 // mismatches when printing. 356 return c.getFontMetrics(font); 357 } 358 return Toolkit.getDefaultToolkit().getFontMetrics(font); 359 } 360 361 362 /** 363 * Returns the width of the passed in String. 364 * If the passed String is {@code null}, returns zero. 365 * 366 * @param c JComponent that will display the string, may be null 367 * @param fm FontMetrics used to measure the String width 368 * @param string String to get the width of 369 */ 370 public static int stringWidth(JComponent c, FontMetrics fm, String string) { 371 return (int) stringWidth(c, fm, string, false); 372 } 373 374 /** 375 * Returns the width of the passed in String. 376 * If the passed String is {@code null}, returns zero. 377 * 378 * @param c JComponent that will display the string, may be null 379 * @param fm FontMetrics used to measure the String width 380 * @param string String to get the width of 381 * @param useFPAPI use floating point API 382 */ 383 public static float stringWidth(JComponent c, FontMetrics fm, String string, 384 boolean useFPAPI){ 385 if (string == null || string.equals("")) { 386 return 0; 387 } 388 boolean needsTextLayout = ((c != null) && 389 (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); 390 if (needsTextLayout) { 391 synchronized(charsBufferLock) { 392 int length = syncCharsBuffer(string); 393 needsTextLayout = isComplexLayout(charsBuffer, 0, length); 394 } 395 } 396 if (needsTextLayout) { 397 TextLayout layout = createTextLayout(c, string, 398 fm.getFont(), fm.getFontRenderContext()); 399 return layout.getAdvance(); 400 } else { 401 return getFontStringWidth(string, fm, useFPAPI); 402 } 403 } 404 405 406 /** 407 * Clips the passed in String to the space provided. 408 * 409 * @param c JComponent that will display the string, may be null 410 * @param fm FontMetrics used to measure the String width 411 * @param string String to display 412 * @param availTextWidth Amount of space that the string can be drawn in 413 * @return Clipped string that can fit in the provided space. 414 */ 415 public static String clipStringIfNecessary(JComponent c, FontMetrics fm, 416 String string, 417 int availTextWidth) { 418 if ((string == null) || (string.equals(""))) { 419 return ""; 420 } 421 int textWidth = SwingUtilities2.stringWidth(c, fm, string); 422 if (textWidth > availTextWidth) { 423 return SwingUtilities2.clipString(c, fm, string, availTextWidth); 424 } 425 return string; 426 } 427 428 429 /** 430 * Clips the passed in String to the space provided. NOTE: this assumes 431 * the string does not fit in the available space. 432 * 433 * @param c JComponent that will display the string, may be null 434 * @param fm FontMetrics used to measure the String width 435 * @param string String to display 436 * @param availTextWidth Amount of space that the string can be drawn in 437 * @return Clipped string that can fit in the provided space. 438 */ 439 public static String clipString(JComponent c, FontMetrics fm, 440 String string, int availTextWidth) { 441 // c may be null here. 442 String clipString = "..."; 443 availTextWidth -= SwingUtilities2.stringWidth(c, fm, clipString); 444 if (availTextWidth <= 0) { 445 //can not fit any characters 446 return clipString; 447 } 448 449 boolean needsTextLayout; 450 synchronized (charsBufferLock) { 451 int stringLength = syncCharsBuffer(string); 452 needsTextLayout = 453 isComplexLayout(charsBuffer, 0, stringLength); 454 if (!needsTextLayout) { 455 int width = 0; 456 for (int nChars = 0; nChars < stringLength; nChars++) { 457 width += fm.charWidth(charsBuffer[nChars]); 458 if (width > availTextWidth) { 459 string = string.substring(0, nChars); 460 break; 461 } 462 } 463 } 464 } 465 if (needsTextLayout) { 466 AttributedString aString = new AttributedString(string); 467 if (c != null) { 468 aString.addAttribute(TextAttribute.NUMERIC_SHAPING, 469 c.getClientProperty(TextAttribute.NUMERIC_SHAPING)); 470 } 471 LineBreakMeasurer measurer = new LineBreakMeasurer( 472 aString.getIterator(), BreakIterator.getCharacterInstance(), 473 getFontRenderContext(c, fm)); 474 string = string.substring(0, measurer.nextOffset(availTextWidth)); 475 476 } 477 return string + clipString; 478 } 479 480 481 /** 482 * Draws the string at the specified location. 483 * 484 * @param c JComponent that will display the string, may be null 485 * @param g Graphics to draw the text to 486 * @param text String to display 487 * @param x X coordinate to draw the text at 488 * @param y Y coordinate to draw the text at 489 */ 490 public static void drawString(JComponent c, Graphics g, String text, 491 int x, int y) { 492 drawString(c, g, text, x, y, false); 493 } 494 495 /** 496 * Draws the string at the specified location. 497 * 498 * @param c JComponent that will display the string, may be null 499 * @param g Graphics to draw the text to 500 * @param text String to display 501 * @param x X coordinate to draw the text at 502 * @param y Y coordinate to draw the text at 503 * @param useFPAPI use floating point API 504 */ 505 public static void drawString(JComponent c, Graphics g, String text, 506 float x, float y, boolean useFPAPI) { 507 // c may be null 508 509 // All non-editable widgets that draw strings call into this 510 // methods. By non-editable that means widgets like JLabel, JButton 511 // but NOT JTextComponents. 512 if ( text == null || text.length() <= 0 ) { //no need to paint empty strings 513 return; 514 } 515 if (isPrinting(g)) { 516 Graphics2D g2d = getGraphics2D(g); 517 if (g2d != null) { 518 /* The printed text must scale linearly with the UI. 519 * Calculate the width on screen, obtain a TextLayout with 520 * advances for the printer graphics FRC, and then justify 521 * it to fit in the screen width. This distributes the spacing 522 * more evenly than directly laying out to the screen advances. 523 */ 524 String trimmedText = trimTrailingSpaces(text); 525 if (!trimmedText.isEmpty()) { 526 float screenWidth = (float) g2d.getFont().getStringBounds 527 (trimmedText, DEFAULT_FRC).getWidth(); 528 TextLayout layout = createTextLayout(c, text, g2d.getFont(), 529 g2d.getFontRenderContext()); 530 531 layout = layout.getJustifiedLayout(screenWidth); 532 /* Use alternate print color if specified */ 533 Color col = g2d.getColor(); 534 if (col instanceof PrintColorUIResource) { 535 g2d.setColor(((PrintColorUIResource)col).getPrintColor()); 536 } 537 538 layout.draw(g2d, x, y); 539 540 g2d.setColor(col); 541 } 542 543 return; 544 } 545 } 546 547 // If we get here we're not printing 548 if (g instanceof Graphics2D) { 549 Graphics2D g2 = (Graphics2D)g; 550 551 boolean needsTextLayout = ((c != null) && 552 (c.getClientProperty(TextAttribute.NUMERIC_SHAPING) != null)); 553 554 if (needsTextLayout) { 555 synchronized(charsBufferLock) { 556 int length = syncCharsBuffer(text); 557 needsTextLayout = isComplexLayout(charsBuffer, 0, length); 558 } 559 } 560 561 Object aaHint = (c == null) 562 ? null 563 : c.getClientProperty(KEY_TEXT_ANTIALIASING); 564 if (aaHint != null) { 565 Object oldContrast = null; 566 Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); 567 if (aaHint != oldAAValue) { 568 g2.setRenderingHint(KEY_TEXT_ANTIALIASING, aaHint); 569 } else { 570 oldAAValue = null; 571 } 572 573 Object lcdContrastHint = c.getClientProperty( 574 KEY_TEXT_LCD_CONTRAST); 575 if (lcdContrastHint != null) { 576 oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); 577 if (lcdContrastHint.equals(oldContrast)) { 578 oldContrast = null; 579 } else { 580 g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 581 lcdContrastHint); 582 } 583 } 584 585 if (needsTextLayout) { 586 TextLayout layout = createTextLayout(c, text, g2.getFont(), 587 g2.getFontRenderContext()); 588 layout.draw(g2, x, y); 589 } else { 590 g2.drawString(text, x, y); 591 } 592 593 if (oldAAValue != null) { 594 g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue); 595 } 596 if (oldContrast != null) { 597 g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast); 598 } 599 600 return; 601 } 602 603 if (needsTextLayout){ 604 TextLayout layout = createTextLayout(c, text, g2.getFont(), 605 g2.getFontRenderContext()); 606 layout.draw(g2, x, y); 607 return; 608 } 609 } 610 611 g.drawString(text, (int) x, (int) y); 612 } 613 614 /** 615 * Draws the string at the specified location underlining the specified 616 * character. 617 * 618 * @param c JComponent that will display the string, may be null 619 * @param g Graphics to draw the text to 620 * @param text String to display 621 * @param underlinedIndex Index of a character in the string to underline 622 * @param x X coordinate to draw the text at 623 * @param y Y coordinate to draw the text at 624 */ 625 626 public static void drawStringUnderlineCharAt(JComponent c,Graphics g, 627 String text, int underlinedIndex, int x, int y) { 628 drawStringUnderlineCharAt(c, g, text, underlinedIndex, x, y, false); 629 } 630 /** 631 * Draws the string at the specified location underlining the specified 632 * character. 633 * 634 * @param c JComponent that will display the string, may be null 635 * @param g Graphics to draw the text to 636 * @param text String to display 637 * @param underlinedIndex Index of a character in the string to underline 638 * @param x X coordinate to draw the text at 639 * @param y Y coordinate to draw the text at 640 * @param useFPAPI use floating point API 641 */ 642 public static void drawStringUnderlineCharAt(JComponent c, Graphics g, 643 String text, int underlinedIndex, 644 float x, float y, 645 boolean useFPAPI) { 646 if (text == null || text.length() <= 0) { 647 return; 648 } 649 SwingUtilities2.drawString(c, g, text, x, y, useFPAPI); 650 int textLength = text.length(); 651 if (underlinedIndex >= 0 && underlinedIndex < textLength ) { 652 float underlineRectY = y; 653 int underlineRectHeight = 1; 654 float underlineRectX = 0; 655 int underlineRectWidth = 0; 656 boolean isPrinting = isPrinting(g); 657 boolean needsTextLayout = isPrinting; 658 if (!needsTextLayout) { 659 synchronized (charsBufferLock) { 660 syncCharsBuffer(text); 661 needsTextLayout = 662 isComplexLayout(charsBuffer, 0, textLength); 663 } 664 } 665 if (!needsTextLayout) { 666 FontMetrics fm = g.getFontMetrics(); 667 underlineRectX = x + 668 SwingUtilities2.stringWidth(c,fm, 669 text.substring(0,underlinedIndex)); 670 underlineRectWidth = fm.charWidth(text. 671 charAt(underlinedIndex)); 672 } else { 673 Graphics2D g2d = getGraphics2D(g); 674 if (g2d != null) { 675 TextLayout layout = 676 createTextLayout(c, text, g2d.getFont(), 677 g2d.getFontRenderContext()); 678 if (isPrinting) { 679 float screenWidth = (float)g2d.getFont(). 680 getStringBounds(text, DEFAULT_FRC).getWidth(); 681 layout = layout.getJustifiedLayout(screenWidth); 682 } 683 TextHitInfo leading = 684 TextHitInfo.leading(underlinedIndex); 685 TextHitInfo trailing = 686 TextHitInfo.trailing(underlinedIndex); 687 Shape shape = 688 layout.getVisualHighlightShape(leading, trailing); 689 Rectangle rect = shape.getBounds(); 690 underlineRectX = x + rect.x; 691 underlineRectWidth = rect.width; 692 } 693 } 694 g.fillRect((int) underlineRectX, (int) underlineRectY + 1, 695 underlineRectWidth, underlineRectHeight); 696 } 697 } 698 699 700 /** 701 * A variation of locationToIndex() which only returns an index if the 702 * Point is within the actual bounds of a list item (not just in the cell) 703 * and if the JList has the "List.isFileList" client property set. 704 * Otherwise, this method returns -1. 705 * This is used to make Windows {@literal L&F} JFileChooser act 706 * like native dialogs. 707 */ 708 public static int loc2IndexFileList(JList<?> list, Point point) { 709 int index = list.locationToIndex(point); 710 if (index != -1) { 711 Object bySize = list.getClientProperty("List.isFileList"); 712 if (bySize instanceof Boolean && ((Boolean)bySize).booleanValue() && 713 !pointIsInActualBounds(list, index, point)) { 714 index = -1; 715 } 716 } 717 return index; 718 } 719 720 721 /** 722 * Returns true if the given point is within the actual bounds of the 723 * JList item at index (not just inside the cell). 724 */ 725 private static <T> boolean pointIsInActualBounds(JList<T> list, int index, 726 Point point) { 727 ListCellRenderer<? super T> renderer = list.getCellRenderer(); 728 T value = list.getModel().getElementAt(index); 729 Component item = renderer.getListCellRendererComponent(list, 730 value, index, false, false); 731 Dimension itemSize = item.getPreferredSize(); 732 Rectangle cellBounds = list.getCellBounds(index, index); 733 if (!item.getComponentOrientation().isLeftToRight()) { 734 cellBounds.x += (cellBounds.width - itemSize.width); 735 } 736 cellBounds.width = itemSize.width; 737 738 return cellBounds.contains(point); 739 } 740 741 742 /** 743 * Returns true if the given point is outside the preferredSize of the 744 * item at the given row of the table. (Column must be 0). 745 * Does not check the "Table.isFileList" property. That should be checked 746 * before calling this method. 747 * This is used to make Windows {@literal L&F} JFileChooser act 748 * like native dialogs. 749 */ 750 public static boolean pointOutsidePrefSize(JTable table, int row, int column, Point p) { 751 if (table.convertColumnIndexToModel(column) != 0 || row == -1) { 752 return true; 753 } 754 TableCellRenderer tcr = table.getCellRenderer(row, column); 755 Object value = table.getValueAt(row, column); 756 Component cell = tcr.getTableCellRendererComponent(table, value, false, 757 false, row, column); 758 Dimension itemSize = cell.getPreferredSize(); 759 Rectangle cellBounds = table.getCellRect(row, column, false); 760 cellBounds.width = itemSize.width; 761 cellBounds.height = itemSize.height; 762 763 // See if coords are inside 764 // ASSUME: mouse x,y will never be < cell's x,y 765 assert (p.x >= cellBounds.x && p.y >= cellBounds.y); 766 return p.x > cellBounds.x + cellBounds.width || 767 p.y > cellBounds.y + cellBounds.height; 768 } 769 770 /** 771 * Set the lead and anchor without affecting selection. 772 */ 773 public static void setLeadAnchorWithoutSelection(ListSelectionModel model, 774 int lead, int anchor) { 775 if (anchor == -1) { 776 anchor = lead; 777 } 778 if (lead == -1) { 779 model.setAnchorSelectionIndex(-1); 780 model.setLeadSelectionIndex(-1); 781 } else { 782 if (model.isSelectedIndex(lead)) { 783 model.addSelectionInterval(lead, lead); 784 } else { 785 model.removeSelectionInterval(lead, lead); 786 } 787 model.setAnchorSelectionIndex(anchor); 788 } 789 } 790 791 /** 792 * Ignore mouse events if the component is null, not enabled, the event 793 * is not associated with the left mouse button, or the event has been 794 * consumed. 795 */ 796 public static boolean shouldIgnore(MouseEvent me, JComponent c) { 797 return c == null || !c.isEnabled() 798 || !SwingUtilities.isLeftMouseButton(me) 799 || me.isConsumed(); 800 } 801 802 /** 803 * Request focus on the given component if it doesn't already have it 804 * and {@code isRequestFocusEnabled()} returns true. 805 */ 806 public static void adjustFocus(JComponent c) { 807 if (!c.hasFocus() && c.isRequestFocusEnabled()) { 808 c.requestFocus(); 809 } 810 } 811 812 /** 813 * The following draw functions have the same semantic as the 814 * Graphics methods with the same names. 815 * 816 * this is used for printing 817 */ 818 public static int drawChars(JComponent c, Graphics g, 819 char[] data, 820 int offset, 821 int length, 822 int x, 823 int y) { 824 return (int) drawChars(c, g, data, offset, length, x, y, false); 825 } 826 827 public static float drawChars(JComponent c, Graphics g, 828 char[] data, 829 int offset, 830 int length, 831 float x, 832 float y) { 833 return drawChars(c, g, data, offset, length, x, y, true); 834 } 835 836 public static float drawChars(JComponent c, Graphics g, 837 char[] data, 838 int offset, 839 int length, 840 float x, 841 float y, 842 boolean useFPAPI) { 843 if ( length <= 0 ) { //no need to paint empty strings 844 return x; 845 } 846 float nextX = x + getFontCharsWidth(data, offset, length, 847 getFontMetrics(c, g), 848 useFPAPI); 849 if (isPrinting(g)) { 850 Graphics2D g2d = getGraphics2D(g); 851 if (g2d != null) { 852 FontRenderContext deviceFontRenderContext = g2d. 853 getFontRenderContext(); 854 FontRenderContext frc = getFontRenderContext(c); 855 if (frc != null && 856 !isFontRenderContextPrintCompatible 857 (deviceFontRenderContext, frc)) { 858 859 String text = new String(data, offset, length); 860 TextLayout layout = new TextLayout(text, g2d.getFont(), 861 deviceFontRenderContext); 862 String trimmedText = trimTrailingSpaces(text); 863 if (!trimmedText.isEmpty()) { 864 float screenWidth = (float)g2d.getFont(). 865 getStringBounds(trimmedText, frc).getWidth(); 866 layout = layout.getJustifiedLayout(screenWidth); 867 868 /* Use alternate print color if specified */ 869 Color col = g2d.getColor(); 870 if (col instanceof PrintColorUIResource) { 871 g2d.setColor(((PrintColorUIResource)col).getPrintColor()); 872 } 873 874 layout.draw(g2d,x,y); 875 876 g2d.setColor(col); 877 } 878 879 return nextX; 880 } 881 } 882 } 883 // Assume we're not printing if we get here, or that we are invoked 884 // via Swing text printing which is laid out for the printer. 885 Object aaHint = (c == null) 886 ? null 887 : c.getClientProperty(KEY_TEXT_ANTIALIASING); 888 889 if (!(g instanceof Graphics2D)) { 890 g.drawChars(data, offset, length, (int) x, (int) y); 891 return nextX; 892 } 893 894 Graphics2D g2 = (Graphics2D) g; 895 if (aaHint != null) { 896 897 Object oldContrast = null; 898 Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); 899 if (aaHint != null && aaHint != oldAAValue) { 900 g2.setRenderingHint(KEY_TEXT_ANTIALIASING, aaHint); 901 } else { 902 oldAAValue = null; 903 } 904 905 Object lcdContrastHint = c.getClientProperty(KEY_TEXT_LCD_CONTRAST); 906 if (lcdContrastHint != null) { 907 oldContrast = g2.getRenderingHint(KEY_TEXT_LCD_CONTRAST); 908 if (lcdContrastHint.equals(oldContrast)) { 909 oldContrast = null; 910 } else { 911 g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, 912 lcdContrastHint); 913 } 914 } 915 916 g2.drawString(new String(data, offset, length), x, y); 917 918 if (oldAAValue != null) { 919 g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue); 920 } 921 if (oldContrast != null) { 922 g2.setRenderingHint(KEY_TEXT_LCD_CONTRAST, oldContrast); 923 } 924 } 925 else { 926 g2.drawString(new String(data, offset, length), x, y); 927 } 928 return nextX; 929 } 930 931 public static float getFontCharWidth(char c, FontMetrics fm, 932 boolean useFPAPI) 933 { 934 return getFontCharsWidth(new char[]{c}, 0, 1, fm, useFPAPI); 935 } 936 937 public static float getFontCharsWidth(char[] data, int offset, int len, 938 FontMetrics fm, 939 boolean useFPAPI) 940 { 941 if (len == 0) { 942 return 0; 943 } 944 if (useFPAPI) { 945 Rectangle2D bounds = fm.getFont(). 946 getStringBounds(data, offset, offset + len, 947 fm.getFontRenderContext()); 948 return (float) bounds.getWidth(); 949 } else { 950 return fm.charsWidth(data, offset, len); 951 } 952 } 953 954 public static float getFontStringWidth(String data, FontMetrics fm, 955 boolean useFPAPI) 956 { 957 if (useFPAPI) { 958 Rectangle2D bounds = fm.getFont() 959 .getStringBounds(data, fm.getFontRenderContext()); 960 return (float) bounds.getWidth(); 961 } else { 962 return fm.stringWidth(data); 963 } 964 } 965 966 /* 967 * see documentation for drawChars 968 * returns the advance 969 */ 970 public static float drawString(JComponent c, Graphics g, 971 AttributedCharacterIterator iterator, 972 int x, int y) 973 { 974 return drawStringImpl(c, g, iterator, x, y); 975 } 976 977 public static float drawString(JComponent c, Graphics g, 978 AttributedCharacterIterator iterator, 979 float x, float y) 980 { 981 return drawStringImpl(c, g, iterator, x, y); 982 } 983 984 private static float drawStringImpl(JComponent c, Graphics g, 985 AttributedCharacterIterator iterator, 986 float x, float y) 987 { 988 989 float retVal; 990 boolean isPrinting = isPrinting(g); 991 Color col = g.getColor(); 992 993 if (isPrinting) { 994 /* Use alternate print color if specified */ 995 if (col instanceof PrintColorUIResource) { 996 g.setColor(((PrintColorUIResource)col).getPrintColor()); 997 } 998 } 999 1000 Graphics2D g2d = getGraphics2D(g); 1001 if (g2d == null) { 1002 g.drawString(iterator, (int)x, (int)y); //for the cases where advance 1003 //matters it should not happen 1004 retVal = x; 1005 1006 } else { 1007 FontRenderContext frc; 1008 if (isPrinting) { 1009 frc = getFontRenderContext(c); 1010 if (frc.isAntiAliased() || frc.usesFractionalMetrics()) { 1011 frc = new FontRenderContext(frc.getTransform(), false, false); 1012 } 1013 } else if ((frc = getFRCProperty(c)) != null) { 1014 /* frc = frc; ! */ 1015 } else { 1016 frc = g2d.getFontRenderContext(); 1017 } 1018 TextLayout layout; 1019 if (isPrinting) { 1020 FontRenderContext deviceFRC = g2d.getFontRenderContext(); 1021 if (!isFontRenderContextPrintCompatible(frc, deviceFRC)) { 1022 layout = new TextLayout(iterator, deviceFRC); 1023 AttributedCharacterIterator trimmedIt = 1024 getTrimmedTrailingSpacesIterator(iterator); 1025 if (trimmedIt != null) { 1026 float screenWidth = new TextLayout(trimmedIt, frc). 1027 getAdvance(); 1028 layout = layout.getJustifiedLayout(screenWidth); 1029 } 1030 } else { 1031 layout = new TextLayout(iterator, frc); 1032 } 1033 } else { 1034 layout = new TextLayout(iterator, frc); 1035 } 1036 layout.draw(g2d, x, y); 1037 retVal = layout.getAdvance(); 1038 } 1039 1040 if (isPrinting) { 1041 g.setColor(col); 1042 } 1043 1044 return retVal; 1045 } 1046 1047 /** 1048 * This method should be used for drawing a borders over a filled rectangle. 1049 * Draws vertical line, using the current color, between the points {@code 1050 * (x, y1)} and {@code (x, y2)} in graphics context's coordinate system. 1051 * Note: it use {@code Graphics.fillRect()} internally. 1052 * 1053 * @param g Graphics to draw the line to. 1054 * @param x the <i>x</i> coordinate. 1055 * @param y1 the first point's <i>y</i> coordinate. 1056 * @param y2 the second point's <i>y</i> coordinate. 1057 */ 1058 public static void drawVLine(Graphics g, int x, int y1, int y2) { 1059 if (y2 < y1) { 1060 final int temp = y2; 1061 y2 = y1; 1062 y1 = temp; 1063 } 1064 g.fillRect(x, y1, 1, y2 - y1 + 1); 1065 } 1066 1067 /** 1068 * This method should be used for drawing a borders over a filled rectangle. 1069 * Draws horizontal line, using the current color, between the points {@code 1070 * (x1, y)} and {@code (x2, y)} in graphics context's coordinate system. 1071 * Note: it use {@code Graphics.fillRect()} internally. 1072 * 1073 * @param g Graphics to draw the line to. 1074 * @param x1 the first point's <i>x</i> coordinate. 1075 * @param x2 the second point's <i>x</i> coordinate. 1076 * @param y the <i>y</i> coordinate. 1077 */ 1078 public static void drawHLine(Graphics g, int x1, int x2, int y) { 1079 if (x2 < x1) { 1080 final int temp = x2; 1081 x2 = x1; 1082 x1 = temp; 1083 } 1084 g.fillRect(x1, y, x2 - x1 + 1, 1); 1085 } 1086 1087 /** 1088 * This method should be used for drawing a borders over a filled rectangle. 1089 * Draws the outline of the specified rectangle. The left and right edges of 1090 * the rectangle are at {@code x} and {@code x + w}. The top and bottom 1091 * edges are at {@code y} and {@code y + h}. The rectangle is drawn using 1092 * the graphics context's current color. Note: it use {@code 1093 * Graphics.fillRect()} internally. 1094 * 1095 * @param g Graphics to draw the rectangle to. 1096 * @param x the <i>x</i> coordinate of the rectangle to be drawn. 1097 * @param y the <i>y</i> coordinate of the rectangle to be drawn. 1098 * @param w the w of the rectangle to be drawn. 1099 * @param h the h of the rectangle to be drawn. 1100 * @see SwingUtilities2#drawVLine(java.awt.Graphics, int, int, int) 1101 * @see SwingUtilities2#drawHLine(java.awt.Graphics, int, int, int) 1102 */ 1103 public static void drawRect(Graphics g, int x, int y, int w, int h) { 1104 if (w < 0 || h < 0) { 1105 return; 1106 } 1107 1108 if (h == 0 || w == 0) { 1109 g.fillRect(x, y, w + 1, h + 1); 1110 } else { 1111 g.fillRect(x, y, w, 1); 1112 g.fillRect(x + w, y, 1, h); 1113 g.fillRect(x + 1, y + h, w, 1); 1114 g.fillRect(x, y + 1, 1, h); 1115 } 1116 } 1117 1118 private static TextLayout createTextLayout(JComponent c, String s, 1119 Font f, FontRenderContext frc) { 1120 Object shaper = (c == null ? 1121 null : c.getClientProperty(TextAttribute.NUMERIC_SHAPING)); 1122 if (shaper == null) { 1123 return new TextLayout(s, f, frc); 1124 } else { 1125 Map<TextAttribute, Object> a = new HashMap<TextAttribute, Object>(); 1126 a.put(TextAttribute.FONT, f); 1127 a.put(TextAttribute.NUMERIC_SHAPING, shaper); 1128 return new TextLayout(s, a, frc); 1129 } 1130 } 1131 1132 /* 1133 * Checks if two given FontRenderContexts are compatible for printing. 1134 * We can't just use equals as we want to exclude from the comparison : 1135 * + whether AA is set as irrelevant for printing and shouldn't affect 1136 * printed metrics anyway 1137 * + any translation component in the transform of either FRC, as it 1138 * does not affect metrics. 1139 * Compatible means no special handling needed for text painting 1140 */ 1141 private static boolean 1142 isFontRenderContextPrintCompatible(FontRenderContext frc1, 1143 FontRenderContext frc2) { 1144 1145 if (frc1 == frc2) { 1146 return true; 1147 } 1148 1149 if (frc1 == null || frc2 == null) { // not supposed to happen 1150 return false; 1151 } 1152 1153 if (frc1.getFractionalMetricsHint() != 1154 frc2.getFractionalMetricsHint()) { 1155 return false; 1156 } 1157 1158 /* If both are identity, return true */ 1159 if (!frc1.isTransformed() && !frc2.isTransformed()) { 1160 return true; 1161 } 1162 1163 /* That's the end of the cheap tests, need to get and compare 1164 * the transform matrices. We don't care about the translation 1165 * components, so return true if they are otherwise identical. 1166 */ 1167 double[] mat1 = new double[4]; 1168 double[] mat2 = new double[4]; 1169 frc1.getTransform().getMatrix(mat1); 1170 frc2.getTransform().getMatrix(mat2); 1171 return 1172 mat1[0] == mat2[0] && 1173 mat1[1] == mat2[1] && 1174 mat1[2] == mat2[2] && 1175 mat1[3] == mat2[3]; 1176 } 1177 1178 /* 1179 * Tries it best to get Graphics2D out of the given Graphics 1180 * returns null if can not derive it. 1181 */ 1182 public static Graphics2D getGraphics2D(Graphics g) { 1183 if (g instanceof Graphics2D) { 1184 return (Graphics2D) g; 1185 } else if (g instanceof ProxyPrintGraphics) { 1186 return (Graphics2D)(((ProxyPrintGraphics)g).getGraphics()); 1187 } else { 1188 return null; 1189 } 1190 } 1191 1192 /* 1193 * Returns FontRenderContext associated with Component. 1194 * FontRenderContext from Component.getFontMetrics is associated 1195 * with the component. 1196 * 1197 * Uses Component.getFontMetrics to get the FontRenderContext from. 1198 * see JComponent.getFontMetrics and TextLayoutStrategy.java 1199 */ 1200 public static FontRenderContext getFontRenderContext(Component c) { 1201 assert c != null; 1202 if (c == null) { 1203 return DEFAULT_FRC; 1204 } else { 1205 return c.getFontMetrics(c.getFont()).getFontRenderContext(); 1206 } 1207 } 1208 1209 /** 1210 * A convenience method to get FontRenderContext. 1211 * Returns the FontRenderContext for the passed in FontMetrics or 1212 * for the passed in Component if FontMetrics is null 1213 */ 1214 private static FontRenderContext getFontRenderContext(Component c, FontMetrics fm) { 1215 assert fm != null || c!= null; 1216 return (fm != null) ? fm.getFontRenderContext() 1217 : getFontRenderContext(c); 1218 } 1219 1220 /* 1221 * This method is to be used only for JComponent.getFontMetrics. 1222 * In all other places to get FontMetrics we need to use 1223 * JComponent.getFontMetrics. 1224 * 1225 */ 1226 public static FontMetrics getFontMetrics(JComponent c, Font font) { 1227 FontRenderContext frc = getFRCProperty(c); 1228 if (frc == null) { 1229 frc = DEFAULT_FRC; 1230 } 1231 return FontDesignMetrics.getMetrics(font, frc); 1232 } 1233 1234 1235 /* Get any FontRenderContext associated with a JComponent 1236 * - may return null 1237 */ 1238 private static FontRenderContext getFRCProperty(JComponent c) { 1239 if (c != null) { 1240 1241 GraphicsConfiguration gc = c.getGraphicsConfiguration(); 1242 AffineTransform tx = (gc == null) ? null : gc.getDefaultTransform(); 1243 Object aaHint = c.getClientProperty(KEY_TEXT_ANTIALIASING); 1244 return getFRCFromCache(tx, aaHint); 1245 } 1246 return null; 1247 } 1248 1249 private static final Object APP_CONTEXT_FRC_CACHE_KEY = new Object(); 1250 1251 private static FontRenderContext getFRCFromCache(AffineTransform tx, 1252 Object aaHint) { 1253 if (tx == null && aaHint == null) { 1254 return null; 1255 } 1256 1257 @SuppressWarnings("unchecked") 1258 Map<Object, FontRenderContext> cache = (Map<Object, FontRenderContext>) 1259 AppContext.getAppContext().get(APP_CONTEXT_FRC_CACHE_KEY); 1260 1261 if (cache == null) { 1262 cache = new HashMap<>(); 1263 AppContext.getAppContext().put(APP_CONTEXT_FRC_CACHE_KEY, cache); 1264 } 1265 1266 Object key = (tx == null) 1267 ? aaHint 1268 : (aaHint == null ? tx : new KeyPair(tx, aaHint)); 1269 1270 FontRenderContext frc = cache.get(key); 1271 if (frc == null) { 1272 aaHint = (aaHint == null) ? VALUE_TEXT_ANTIALIAS_OFF : aaHint; 1273 frc = new FontRenderContext(tx, aaHint, 1274 VALUE_FRACTIONALMETRICS_DEFAULT); 1275 cache.put(key, frc); 1276 } 1277 return frc; 1278 } 1279 1280 private static class KeyPair { 1281 1282 private final Object key1; 1283 private final Object key2; 1284 1285 public KeyPair(Object key1, Object key2) { 1286 this.key1 = key1; 1287 this.key2 = key2; 1288 } 1289 1290 @Override 1291 public boolean equals(Object obj) { 1292 if (!(obj instanceof KeyPair)) { 1293 return false; 1294 } 1295 KeyPair that = (KeyPair) obj; 1296 return this.key1.equals(that.key1) && this.key2.equals(that.key2); 1297 } 1298 1299 @Override 1300 public int hashCode() { 1301 return key1.hashCode() + 37 * key2.hashCode(); 1302 } 1303 } 1304 1305 /* 1306 * returns true if the Graphics is print Graphics 1307 * false otherwise 1308 */ 1309 static boolean isPrinting(Graphics g) { 1310 return (g instanceof PrinterGraphics || g instanceof PrintGraphics); 1311 } 1312 1313 private static String trimTrailingSpaces(String s) { 1314 int i = s.length() - 1; 1315 while(i >= 0 && Character.isWhitespace(s.charAt(i))) { 1316 i--; 1317 } 1318 return s.substring(0, i + 1); 1319 } 1320 1321 private static AttributedCharacterIterator getTrimmedTrailingSpacesIterator 1322 (AttributedCharacterIterator iterator) { 1323 int curIdx = iterator.getIndex(); 1324 1325 char c = iterator.last(); 1326 while(c != CharacterIterator.DONE && Character.isWhitespace(c)) { 1327 c = iterator.previous(); 1328 } 1329 1330 if (c != CharacterIterator.DONE) { 1331 int endIdx = iterator.getIndex(); 1332 1333 if (endIdx == iterator.getEndIndex() - 1) { 1334 iterator.setIndex(curIdx); 1335 return iterator; 1336 } else { 1337 AttributedString trimmedText = new AttributedString(iterator, 1338 iterator.getBeginIndex(), endIdx + 1); 1339 return trimmedText.getIterator(); 1340 } 1341 } else { 1342 return null; 1343 } 1344 } 1345 1346 /** 1347 * Determines whether the SelectedTextColor should be used for painting text 1348 * foreground for the specified highlight. 1349 * 1350 * Returns true only if the highlight painter for the specified highlight 1351 * is the swing painter (whether inner class of javax.swing.text.DefaultHighlighter 1352 * or com.sun.java.swing.plaf.windows.WindowsTextUI) and its background color 1353 * is null or equals to the selection color of the text component. 1354 * 1355 * This is a hack for fixing both bugs 4761990 and 5003294 1356 */ 1357 public static boolean useSelectedTextColor(Highlighter.Highlight h, JTextComponent c) { 1358 Highlighter.HighlightPainter painter = h.getPainter(); 1359 String painterClass = painter.getClass().getName(); 1360 if (painterClass.indexOf("javax.swing.text.DefaultHighlighter") != 0 && 1361 painterClass.indexOf("com.sun.java.swing.plaf.windows.WindowsTextUI") != 0) { 1362 return false; 1363 } 1364 try { 1365 DefaultHighlighter.DefaultHighlightPainter defPainter = 1366 (DefaultHighlighter.DefaultHighlightPainter) painter; 1367 if (defPainter.getColor() != null && 1368 !defPainter.getColor().equals(c.getSelectionColor())) { 1369 return false; 1370 } 1371 } catch (ClassCastException e) { 1372 return false; 1373 } 1374 return true; 1375 } 1376 1377 /** 1378 * LSBCacheEntry is used to cache the left side bearing (lsb) for 1379 * a particular {@code Font} and {@code FontRenderContext}. 1380 * This only caches characters that fall in the range 1381 * {@code MIN_CHAR_INDEX} to {@code MAX_CHAR_INDEX}. 1382 */ 1383 private static class LSBCacheEntry { 1384 // Used to indicate a particular entry in lsb has not been set. 1385 private static final byte UNSET = Byte.MAX_VALUE; 1386 // Used in creating a GlyphVector to get the lsb 1387 private static final char[] oneChar = new char[1]; 1388 1389 private byte[] lsbCache; 1390 private Font font; 1391 private FontRenderContext frc; 1392 1393 1394 public LSBCacheEntry(FontRenderContext frc, Font font) { 1395 lsbCache = new byte[MAX_CHAR_INDEX - MIN_CHAR_INDEX]; 1396 reset(frc, font); 1397 1398 } 1399 1400 public void reset(FontRenderContext frc, Font font) { 1401 this.font = font; 1402 this.frc = frc; 1403 for (int counter = lsbCache.length - 1; counter >= 0; counter--) { 1404 lsbCache[counter] = UNSET; 1405 } 1406 } 1407 1408 public int getLeftSideBearing(char aChar) { 1409 int index = aChar - MIN_CHAR_INDEX; 1410 assert (index >= 0 && index < (MAX_CHAR_INDEX - MIN_CHAR_INDEX)); 1411 byte lsb = lsbCache[index]; 1412 if (lsb == UNSET) { 1413 oneChar[0] = aChar; 1414 GlyphVector gv = font.createGlyphVector(frc, oneChar); 1415 lsb = (byte) gv.getGlyphPixelBounds(0, frc, 0f, 0f).x; 1416 if (lsb < 0) { 1417 /* HRGB/HBGR LCD glyph images will always have a pixel 1418 * on the left used in colour fringe reduction. 1419 * Text rendering positions this correctly but here 1420 * we are using the glyph image to adjust that position 1421 * so must account for it. 1422 */ 1423 Object aaHint = frc.getAntiAliasingHint(); 1424 if (aaHint == VALUE_TEXT_ANTIALIAS_LCD_HRGB || 1425 aaHint == VALUE_TEXT_ANTIALIAS_LCD_HBGR) { 1426 lsb++; 1427 } 1428 } 1429 lsbCache[index] = lsb; 1430 } 1431 return lsb; 1432 1433 1434 } 1435 1436 public boolean equals(Object entry) { 1437 if (entry == this) { 1438 return true; 1439 } 1440 if (!(entry instanceof LSBCacheEntry)) { 1441 return false; 1442 } 1443 LSBCacheEntry oEntry = (LSBCacheEntry) entry; 1444 return (font.equals(oEntry.font) && 1445 frc.equals(oEntry.frc)); 1446 } 1447 1448 public int hashCode() { 1449 int result = 17; 1450 if (font != null) { 1451 result = 37 * result + font.hashCode(); 1452 } 1453 if (frc != null) { 1454 result = 37 * result + frc.hashCode(); 1455 } 1456 return result; 1457 } 1458 } 1459 1460 /* 1461 * here goes the fix for 4856343 [Problem with applet interaction 1462 * with system selection clipboard] 1463 * 1464 * NOTE. In case isTrustedContext() no checking 1465 * are to be performed 1466 */ 1467 1468 /** 1469 * checks the security permissions for accessing system clipboard 1470 * 1471 * for untrusted context (see isTrustedContext) checks the 1472 * permissions for the current event being handled 1473 * 1474 */ 1475 public static boolean canAccessSystemClipboard() { 1476 boolean canAccess = false; 1477 if (!GraphicsEnvironment.isHeadless()) { 1478 SecurityManager sm = System.getSecurityManager(); 1479 if (sm == null) { 1480 canAccess = true; 1481 } else { 1482 try { 1483 sm.checkPermission(AWTPermissions.ACCESS_CLIPBOARD_PERMISSION); 1484 canAccess = true; 1485 } catch (SecurityException e) { 1486 } 1487 if (canAccess && ! isTrustedContext()) { 1488 canAccess = canCurrentEventAccessSystemClipboard(true); 1489 } 1490 } 1491 } 1492 return canAccess; 1493 } 1494 /** 1495 * Returns true if EventQueue.getCurrentEvent() has the permissions to 1496 * access the system clipboard 1497 */ 1498 public static boolean canCurrentEventAccessSystemClipboard() { 1499 return isTrustedContext() 1500 || canCurrentEventAccessSystemClipboard(false); 1501 } 1502 1503 /** 1504 * Returns true if the given event has permissions to access the 1505 * system clipboard 1506 * 1507 * @param e AWTEvent to check 1508 */ 1509 public static boolean canEventAccessSystemClipboard(AWTEvent e) { 1510 return isTrustedContext() 1511 || canEventAccessSystemClipboard(e, false); 1512 } 1513 1514 /** 1515 * Returns true if the given event is corrent gesture for 1516 * accessing clipboard 1517 * 1518 * @param ie InputEvent to check 1519 */ 1520 @SuppressWarnings("deprecation") 1521 private static boolean isAccessClipboardGesture(InputEvent ie) { 1522 boolean allowedGesture = false; 1523 if (ie instanceof KeyEvent) { //we can validate only keyboard gestures 1524 KeyEvent ke = (KeyEvent)ie; 1525 int keyCode = ke.getKeyCode(); 1526 int keyModifiers = ke.getModifiers(); 1527 switch(keyCode) { 1528 case KeyEvent.VK_C: 1529 case KeyEvent.VK_V: 1530 case KeyEvent.VK_X: 1531 allowedGesture = (keyModifiers == InputEvent.CTRL_MASK); 1532 break; 1533 case KeyEvent.VK_INSERT: 1534 allowedGesture = (keyModifiers == InputEvent.CTRL_MASK || 1535 keyModifiers == InputEvent.SHIFT_MASK); 1536 break; 1537 case KeyEvent.VK_COPY: 1538 case KeyEvent.VK_PASTE: 1539 case KeyEvent.VK_CUT: 1540 allowedGesture = true; 1541 break; 1542 case KeyEvent.VK_DELETE: 1543 allowedGesture = ( keyModifiers == InputEvent.SHIFT_MASK); 1544 break; 1545 } 1546 } 1547 return allowedGesture; 1548 } 1549 1550 /** 1551 * Returns true if e has the permissions to 1552 * access the system clipboard and if it is allowed gesture (if 1553 * checkGesture is true) 1554 * 1555 * @param e AWTEvent to check 1556 * @param checkGesture boolean 1557 */ 1558 private static boolean canEventAccessSystemClipboard(AWTEvent e, 1559 boolean checkGesture) { 1560 if (EventQueue.isDispatchThread()) { 1561 /* 1562 * Checking event permissions makes sense only for event 1563 * dispathing thread 1564 */ 1565 if (e instanceof InputEvent 1566 && (! checkGesture || isAccessClipboardGesture((InputEvent)e))) { 1567 return AWTAccessor.getInputEventAccessor(). 1568 canAccessSystemClipboard((InputEvent) e); 1569 } else { 1570 return false; 1571 } 1572 } else { 1573 return true; 1574 } 1575 } 1576 1577 /** 1578 * Utility method that throws SecurityException if SecurityManager is set 1579 * and modifiers are not public 1580 * 1581 * @param modifiers a set of modifiers 1582 */ 1583 public static void checkAccess(int modifiers) { 1584 if (System.getSecurityManager() != null 1585 && !Modifier.isPublic(modifiers)) { 1586 throw new SecurityException("Resource is not accessible"); 1587 } 1588 } 1589 1590 /** 1591 * Returns true if EventQueue.getCurrentEvent() has the permissions to 1592 * access the system clipboard and if it is allowed gesture (if 1593 * checkGesture true) 1594 * 1595 * @param checkGesture boolean 1596 */ 1597 private static boolean canCurrentEventAccessSystemClipboard(boolean 1598 checkGesture) { 1599 AWTEvent event = EventQueue.getCurrentEvent(); 1600 return canEventAccessSystemClipboard(event, checkGesture); 1601 } 1602 1603 /** 1604 * see RFE 5012841 [Per AppContect security permissions] for the 1605 * details 1606 * 1607 */ 1608 private static boolean isTrustedContext() { 1609 return (System.getSecurityManager() == null) 1610 || (AppContext.getAppContext(). 1611 get(UntrustedClipboardAccess) == null); 1612 } 1613 1614 public static String displayPropertiesToCSS(Font font, Color fg) { 1615 StringBuilder rule = new StringBuilder("body {"); 1616 if (font != null) { 1617 rule.append(" font-family: "); 1618 rule.append(font.getFamily()); 1619 rule.append(" ; "); 1620 rule.append(" font-size: "); 1621 rule.append(font.getSize()); 1622 rule.append("pt ;"); 1623 if (font.isBold()) { 1624 rule.append(" font-weight: 700 ; "); 1625 } 1626 if (font.isItalic()) { 1627 rule.append(" font-style: italic ; "); 1628 } 1629 } 1630 if (fg != null) { 1631 rule.append(" color: #"); 1632 if (fg.getRed() < 16) { 1633 rule.append('0'); 1634 } 1635 rule.append(Integer.toHexString(fg.getRed())); 1636 if (fg.getGreen() < 16) { 1637 rule.append('0'); 1638 } 1639 rule.append(Integer.toHexString(fg.getGreen())); 1640 if (fg.getBlue() < 16) { 1641 rule.append('0'); 1642 } 1643 rule.append(Integer.toHexString(fg.getBlue())); 1644 rule.append(" ; "); 1645 } 1646 rule.append(" }"); 1647 return rule.toString(); 1648 } 1649 1650 /** 1651 * Utility method that creates a {@code UIDefaults.LazyValue} that 1652 * creates an {@code ImageIcon} {@code UIResource} for the 1653 * specified image file name. The image is loaded using 1654 * {@code getResourceAsStream}, starting with a call to that method 1655 * on the base class parameter. If it cannot be found, searching will 1656 * continue through the base class' inheritance hierarchy, up to and 1657 * including {@code rootClass}. 1658 * 1659 * @param baseClass the first class to use in searching for the resource 1660 * @param rootClass an ancestor of {@code baseClass} to finish the 1661 * search at 1662 * @param imageFile the name of the file to be found 1663 * @return a lazy value that creates the {@code ImageIcon} 1664 * {@code UIResource} for the image, 1665 * or null if it cannot be found 1666 */ 1667 public static Object makeIcon(final Class<?> baseClass, 1668 final Class<?> rootClass, 1669 final String imageFile) { 1670 return makeIcon(baseClass, rootClass, imageFile, true); 1671 } 1672 1673 /** 1674 * Utility method that creates a {@code UIDefaults.LazyValue} that 1675 * creates an {@code ImageIcon} {@code UIResource} for the 1676 * specified image file name. The image is loaded using 1677 * {@code getResourceAsStream}, starting with a call to that method 1678 * on the base class parameter. If it cannot be found, searching will 1679 * continue through the base class' inheritance hierarchy, up to and 1680 * including {@code rootClass}. 1681 * 1682 * Finds an image with a given name without privileges enabled. 1683 * 1684 * @param baseClass the first class to use in searching for the resource 1685 * @param rootClass an ancestor of {@code baseClass} to finish the 1686 * search at 1687 * @param imageFile the name of the file to be found 1688 * @return a lazy value that creates the {@code ImageIcon} 1689 * {@code UIResource} for the image, 1690 * or null if it cannot be found 1691 */ 1692 public static Object makeIcon_Unprivileged(final Class<?> baseClass, 1693 final Class<?> rootClass, 1694 final String imageFile) { 1695 return makeIcon(baseClass, rootClass, imageFile, false); 1696 } 1697 1698 private static Object makeIcon(final Class<?> baseClass, 1699 final Class<?> rootClass, 1700 final String imageFile, 1701 final boolean enablePrivileges) { 1702 return (UIDefaults.LazyValue) (table) -> { 1703 byte[] buffer = enablePrivileges ? AccessController.doPrivileged( 1704 (PrivilegedAction<byte[]>) () 1705 -> getIconBytes(baseClass, rootClass, imageFile)) 1706 : getIconBytes(baseClass, rootClass, imageFile); 1707 1708 if (buffer == null) { 1709 return null; 1710 } 1711 if (buffer.length == 0) { 1712 System.err.println("warning: " + imageFile 1713 + " is zero-length"); 1714 return null; 1715 } 1716 1717 return new ImageIconUIResource(buffer); 1718 }; 1719 } 1720 1721 private static byte[] getIconBytes(final Class<?> baseClass, 1722 final Class<?> rootClass, 1723 final String imageFile) { 1724 /* Copy resource into a byte array. This is 1725 * necessary because several browsers consider 1726 * Class.getResource a security risk because it 1727 * can be used to load additional classes. 1728 * Class.getResourceAsStream just returns raw 1729 * bytes, which we can convert to an image. 1730 */ 1731 Class<?> srchClass = baseClass; 1732 1733 while (srchClass != null) { 1734 1735 try (InputStream resource = 1736 srchClass.getResourceAsStream(imageFile)) { 1737 if (resource == null) { 1738 if (srchClass == rootClass) { 1739 break; 1740 } 1741 srchClass = srchClass.getSuperclass(); 1742 continue; 1743 } 1744 1745 try (BufferedInputStream in 1746 = new BufferedInputStream(resource); 1747 ByteArrayOutputStream out 1748 = new ByteArrayOutputStream(1024)) { 1749 byte[] buffer = new byte[1024]; 1750 int n; 1751 while ((n = in.read(buffer)) > 0) { 1752 out.write(buffer, 0, n); 1753 } 1754 out.flush(); 1755 return out.toByteArray(); 1756 } 1757 } catch (IOException ioe) { 1758 System.err.println(ioe.toString()); 1759 } 1760 } 1761 return null; 1762 } 1763 1764 /* Used to help decide if AA text rendering should be used, so 1765 * this local display test should be additionally qualified 1766 * against whether we have XRender support on both ends of the wire, 1767 * as with that support remote performance may be good enough to turn 1768 * on by default. An additional complication there is XRender does not 1769 * appear capable of performing gamma correction needed for LCD text. 1770 */ 1771 public static boolean isLocalDisplay() { 1772 boolean isLocal; 1773 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 1774 if (ge instanceof SunGraphicsEnvironment) { 1775 isLocal = ((SunGraphicsEnvironment) ge).isDisplayLocal(); 1776 } else { 1777 isLocal = true; 1778 } 1779 return isLocal; 1780 } 1781 1782 /** 1783 * Returns an integer from the defaults table. If {@code key} does 1784 * not map to a valid {@code Integer}, or can not be convered from 1785 * a {@code String} to an integer, the value 0 is returned. 1786 * 1787 * @param key an {@code Object} specifying the int. 1788 * @return the int 1789 */ 1790 public static int getUIDefaultsInt(Object key) { 1791 return getUIDefaultsInt(key, 0); 1792 } 1793 1794 /** 1795 * Returns an integer from the defaults table that is appropriate 1796 * for the given locale. If {@code key} does not map to a valid 1797 * {@code Integer}, or can not be convered from a {@code String} 1798 * to an integer, the value 0 is returned. 1799 * 1800 * @param key an {@code Object} specifying the int. Returned value 1801 * is 0 if {@code key} is not available, 1802 * @param l the {@code Locale} for which the int is desired 1803 * @return the int 1804 */ 1805 public static int getUIDefaultsInt(Object key, Locale l) { 1806 return getUIDefaultsInt(key, l, 0); 1807 } 1808 1809 /** 1810 * Returns an integer from the defaults table. If {@code key} does 1811 * not map to a valid {@code Integer}, or can not be convered from 1812 * a {@code String} to an integer, {@code default} is 1813 * returned. 1814 * 1815 * @param key an {@code Object} specifying the int. Returned value 1816 * is 0 if {@code key} is not available, 1817 * @param defaultValue Returned value if {@code key} is not available, 1818 * or is not an Integer 1819 * @return the int 1820 */ 1821 public static int getUIDefaultsInt(Object key, int defaultValue) { 1822 return getUIDefaultsInt(key, null, defaultValue); 1823 } 1824 1825 /** 1826 * Returns an integer from the defaults table that is appropriate 1827 * for the given locale. If {@code key} does not map to a valid 1828 * {@code Integer}, or can not be convered from a {@code String} 1829 * to an integer, {@code default} is returned. 1830 * 1831 * @param key an {@code Object} specifying the int. Returned value 1832 * is 0 if {@code key} is not available, 1833 * @param l the {@code Locale} for which the int is desired 1834 * @param defaultValue Returned value if {@code key} is not available, 1835 * or is not an Integer 1836 * @return the int 1837 */ 1838 public static int getUIDefaultsInt(Object key, Locale l, int defaultValue) { 1839 Object value = UIManager.get(key, l); 1840 1841 if (value instanceof Integer) { 1842 return ((Integer)value).intValue(); 1843 } 1844 if (value instanceof String) { 1845 try { 1846 return Integer.parseInt((String)value); 1847 } catch (NumberFormatException nfe) {} 1848 } 1849 return defaultValue; 1850 } 1851 1852 // At this point we need this method here. But we assume that there 1853 // will be a common method for this purpose in the future releases. 1854 public static Component compositeRequestFocus(Component component) { 1855 if (component instanceof Container) { 1856 Container container = (Container)component; 1857 if (container.isFocusCycleRoot()) { 1858 FocusTraversalPolicy policy = container.getFocusTraversalPolicy(); 1859 Component comp = policy.getDefaultComponent(container); 1860 if (comp!=null) { 1861 comp.requestFocus(); 1862 return comp; 1863 } 1864 } 1865 Container rootAncestor = container.getFocusCycleRootAncestor(); 1866 if (rootAncestor!=null) { 1867 FocusTraversalPolicy policy = rootAncestor.getFocusTraversalPolicy(); 1868 Component comp = policy.getComponentAfter(rootAncestor, container); 1869 1870 if (comp!=null && SwingUtilities.isDescendingFrom(comp, container)) { 1871 comp.requestFocus(); 1872 return comp; 1873 } 1874 } 1875 } 1876 if (component.isFocusable()) { 1877 component.requestFocus(); 1878 return component; 1879 } 1880 return null; 1881 } 1882 1883 /** 1884 * Change focus to the visible component in {@code JTabbedPane}. 1885 * This is not a general-purpose method and is here only to permit 1886 * sharing code. 1887 */ 1888 @SuppressWarnings("deprecation") 1889 public static boolean tabbedPaneChangeFocusTo(Component comp) { 1890 if (comp != null) { 1891 if (comp.isFocusTraversable()) { 1892 SwingUtilities2.compositeRequestFocus(comp); 1893 return true; 1894 } else if (comp instanceof JComponent 1895 && ((JComponent)comp).requestDefaultFocus()) { 1896 1897 return true; 1898 } 1899 } 1900 1901 return false; 1902 } 1903 1904 /** 1905 * Submits a value-returning task for execution on the EDT and 1906 * returns a Future representing the pending results of the task. 1907 * 1908 * @param task the task to submit 1909 * @return a Future representing pending completion of the task 1910 * @throws NullPointerException if the task is null 1911 */ 1912 public static <V> Future<V> submit(Callable<V> task) { 1913 if (task == null) { 1914 throw new NullPointerException(); 1915 } 1916 FutureTask<V> future = new FutureTask<V>(task); 1917 execute(future); 1918 return future; 1919 } 1920 1921 /** 1922 * Submits a Runnable task for execution on the EDT and returns a 1923 * Future representing that task. 1924 * 1925 * @param task the task to submit 1926 * @param result the result to return upon successful completion 1927 * @return a Future representing pending completion of the task, 1928 * and whose {@code get()} method will return the given 1929 * result value upon completion 1930 * @throws NullPointerException if the task is null 1931 */ 1932 public static <V> Future<V> submit(Runnable task, V result) { 1933 if (task == null) { 1934 throw new NullPointerException(); 1935 } 1936 FutureTask<V> future = new FutureTask<V>(task, result); 1937 execute(future); 1938 return future; 1939 } 1940 1941 /** 1942 * Sends a Runnable to the EDT for the execution. 1943 */ 1944 private static void execute(Runnable command) { 1945 SwingUtilities.invokeLater(command); 1946 } 1947 1948 /** 1949 * Sets the {@code SKIP_CLICK_COUNT} client property on the component 1950 * if it is an instance of {@code JTextComponent} with a 1951 * {@code DefaultCaret}. This property, used for text components acting 1952 * as editors in a table or tree, tells {@code DefaultCaret} how many 1953 * clicks to skip before starting selection. 1954 */ 1955 public static void setSkipClickCount(Component comp, int count) { 1956 if (comp instanceof JTextComponent 1957 && ((JTextComponent) comp).getCaret() instanceof DefaultCaret) { 1958 1959 ((JTextComponent) comp).putClientProperty(SKIP_CLICK_COUNT, count); 1960 } 1961 } 1962 1963 /** 1964 * Return the MouseEvent's click count, possibly reduced by the value of 1965 * the component's {@code SKIP_CLICK_COUNT} client property. Clears 1966 * the {@code SKIP_CLICK_COUNT} property if the mouse event's click count 1967 * is 1. In order for clearing of the property to work correctly, there 1968 * must be a mousePressed implementation on the caller with this 1969 * call as the first line. 1970 */ 1971 public static int getAdjustedClickCount(JTextComponent comp, MouseEvent e) { 1972 int cc = e.getClickCount(); 1973 1974 if (cc == 1) { 1975 comp.putClientProperty(SKIP_CLICK_COUNT, null); 1976 } else { 1977 Integer sub = (Integer) comp.getClientProperty(SKIP_CLICK_COUNT); 1978 if (sub != null) { 1979 return cc - sub; 1980 } 1981 } 1982 1983 return cc; 1984 } 1985 1986 /** 1987 * Used by the {@code liesIn} method to return which section 1988 * the point lies in. 1989 * 1990 * @see #liesIn 1991 */ 1992 public enum Section { 1993 1994 /** The leading section */ 1995 LEADING, 1996 1997 /** The middle section */ 1998 MIDDLE, 1999 2000 /** The trailing section */ 2001 TRAILING 2002 } 2003 2004 /** 2005 * This method divides a rectangle into two or three sections along 2006 * the specified axis and determines which section the given point 2007 * lies in on that axis; used by drag and drop when calculating drop 2008 * locations. 2009 * <p> 2010 * For two sections, the rectangle is divided equally and the method 2011 * returns whether the point lies in {@code Section.LEADING} or 2012 * {@code Section.TRAILING}. For horizontal divisions, the calculation 2013 * respects component orientation. 2014 * <p> 2015 * For three sections, if the rectangle is greater than or equal to 2016 * 30 pixels in length along the axis, the calculation gives 10 pixels 2017 * to each of the leading and trailing sections and the remainder to the 2018 * middle. For smaller sizes, the rectangle is divided equally into three 2019 * sections. 2020 * <p> 2021 * Note: This method assumes that the point is within the bounds of 2022 * the given rectangle on the specified axis. However, in cases where 2023 * it isn't, the results still have meaning: {@code Section.MIDDLE} 2024 * remains the same, {@code Section.LEADING} indicates that the point 2025 * is in or somewhere before the leading section, and 2026 * {@code Section.TRAILING} indicates that the point is in or somewhere 2027 * after the trailing section. 2028 * 2029 * @param rect the rectangle 2030 * @param p the point the check 2031 * @param horizontal {@code true} to use the horizontal axis, 2032 * or {@code false} for the vertical axis 2033 * @param ltr {@code true} for left to right orientation, 2034 * or {@code false} for right to left orientation; 2035 * only used for horizontal calculations 2036 * @param three {@code true} for three sections, 2037 * or {@code false} for two 2038 * 2039 * @return the {@code Section} where the point lies 2040 * 2041 * @throws NullPointerException if {@code rect} or {@code p} are 2042 * {@code null} 2043 */ 2044 private static Section liesIn(Rectangle rect, Point p, boolean horizontal, 2045 boolean ltr, boolean three) { 2046 2047 /* beginning of the rectangle on the axis */ 2048 int p0; 2049 2050 /* point on the axis we're interested in */ 2051 int pComp; 2052 2053 /* length of the rectangle on the axis */ 2054 int length; 2055 2056 /* value of ltr if horizontal, else true */ 2057 boolean forward; 2058 2059 if (horizontal) { 2060 p0 = rect.x; 2061 pComp = p.x; 2062 length = rect.width; 2063 forward = ltr; 2064 } else { 2065 p0 = rect.y; 2066 pComp = p.y; 2067 length = rect.height; 2068 forward = true; 2069 } 2070 2071 if (three) { 2072 int boundary = (length >= 30) ? 10 : length / 3; 2073 2074 if (pComp < p0 + boundary) { 2075 return forward ? Section.LEADING : Section.TRAILING; 2076 } else if (pComp >= p0 + length - boundary) { 2077 return forward ? Section.TRAILING : Section.LEADING; 2078 } 2079 2080 return Section.MIDDLE; 2081 } else { 2082 int middle = p0 + length / 2; 2083 if (forward) { 2084 return pComp >= middle ? Section.TRAILING : Section.LEADING; 2085 } else { 2086 return pComp < middle ? Section.TRAILING : Section.LEADING; 2087 } 2088 } 2089 } 2090 2091 /** 2092 * This method divides a rectangle into two or three sections along 2093 * the horizontal axis and determines which section the given point 2094 * lies in; used by drag and drop when calculating drop locations. 2095 * <p> 2096 * See the documentation for {@link #liesIn} for more information 2097 * on how the section is calculated. 2098 * 2099 * @param rect the rectangle 2100 * @param p the point the check 2101 * @param ltr {@code true} for left to right orientation, 2102 * or {@code false} for right to left orientation 2103 * @param three {@code true} for three sections, 2104 * or {@code false} for two 2105 * 2106 * @return the {@code Section} where the point lies 2107 * 2108 * @throws NullPointerException if {@code rect} or {@code p} are 2109 * {@code null} 2110 */ 2111 public static Section liesInHorizontal(Rectangle rect, Point p, 2112 boolean ltr, boolean three) { 2113 return liesIn(rect, p, true, ltr, three); 2114 } 2115 2116 /** 2117 * This method divides a rectangle into two or three sections along 2118 * the vertical axis and determines which section the given point 2119 * lies in; used by drag and drop when calculating drop locations. 2120 * <p> 2121 * See the documentation for {@link #liesIn} for more information 2122 * on how the section is calculated. 2123 * 2124 * @param rect the rectangle 2125 * @param p the point the check 2126 * @param three {@code true} for three sections, 2127 * or {@code false} for two 2128 * 2129 * @return the {@code Section} where the point lies 2130 * 2131 * @throws NullPointerException if {@code rect} or {@code p} are 2132 * {@code null} 2133 */ 2134 public static Section liesInVertical(Rectangle rect, Point p, 2135 boolean three) { 2136 return liesIn(rect, p, false, false, three); 2137 } 2138 2139 /** 2140 * Maps the index of the column in the view at 2141 * {@code viewColumnIndex} to the index of the column 2142 * in the table model. Returns the index of the corresponding 2143 * column in the model. If {@code viewColumnIndex} 2144 * is less than zero, returns {@code viewColumnIndex}. 2145 * 2146 * @param cm the table model 2147 * @param viewColumnIndex the index of the column in the view 2148 * @return the index of the corresponding column in the model 2149 * 2150 * @see JTable#convertColumnIndexToModel(int) 2151 * @see javax.swing.plaf.basic.BasicTableHeaderUI 2152 */ 2153 public static int convertColumnIndexToModel(TableColumnModel cm, 2154 int viewColumnIndex) { 2155 if (viewColumnIndex < 0) { 2156 return viewColumnIndex; 2157 } 2158 return cm.getColumn(viewColumnIndex).getModelIndex(); 2159 } 2160 2161 /** 2162 * Maps the index of the column in the {@code cm} at 2163 * {@code modelColumnIndex} to the index of the column 2164 * in the view. Returns the index of the 2165 * corresponding column in the view; returns {@code -1} if this column 2166 * is not being displayed. If {@code modelColumnIndex} is less than zero, 2167 * returns {@code modelColumnIndex}. 2168 * 2169 * @param cm the table model 2170 * @param modelColumnIndex the index of the column in the model 2171 * @return the index of the corresponding column in the view 2172 * 2173 * @see JTable#convertColumnIndexToView(int) 2174 * @see javax.swing.plaf.basic.BasicTableHeaderUI 2175 */ 2176 public static int convertColumnIndexToView(TableColumnModel cm, 2177 int modelColumnIndex) { 2178 if (modelColumnIndex < 0) { 2179 return modelColumnIndex; 2180 } 2181 for (int column = 0; column < cm.getColumnCount(); column++) { 2182 if (cm.getColumn(column).getModelIndex() == modelColumnIndex) { 2183 return column; 2184 } 2185 } 2186 return -1; 2187 } 2188 2189 /** 2190 * Sets the InputEvent.ALT_GRAPH mask on any modifier passed to the function 2191 * @param modifier the modifier passed 2192 * @return the modifier retiurned with ALT_GRAPH flag set 2193 */ 2194 public static int setAltGraphMask(int modifier) { 2195 return (modifier | InputEvent.ALT_GRAPH_DOWN_MASK); 2196 } 2197 2198 @SuppressWarnings("deprecation") 2199 public static int getSystemMnemonicKeyMask() { 2200 Toolkit toolkit = Toolkit.getDefaultToolkit(); 2201 if (toolkit instanceof SunToolkit) { 2202 return ((SunToolkit) toolkit).getFocusAcceleratorKeyMask(); 2203 } 2204 return InputEvent.ALT_MASK; 2205 } 2206 2207 /** 2208 * Returns the {@link TreePath} that identifies the changed nodes. 2209 * 2210 * @param event changes in a tree model 2211 * @param model corresponing tree model 2212 * @return the path to the changed nodes 2213 */ 2214 public static TreePath getTreePath(TreeModelEvent event, TreeModel model) { 2215 TreePath path = event.getTreePath(); 2216 if ((path == null) && (model != null)) { 2217 Object root = model.getRoot(); 2218 if (root != null) { 2219 path = new TreePath(root); 2220 } 2221 } 2222 return path; 2223 } 2224 2225 public static boolean isScaledGraphics(Graphics g) { 2226 if (g instanceof Graphics2D) { 2227 AffineTransform tx = ((Graphics2D) g).getTransform(); 2228 return (tx.getType() & ~(TYPE_TRANSLATION | TYPE_FLIP)) != 0; 2229 } 2230 return false; 2231 } 2232 2233 /** 2234 * Enables the antialiasing rendering hint for the scaled graphics and 2235 * returns the previous hint value. 2236 * The returned null value indicates that the passed graphics is not 2237 * instance of Graphics2D. 2238 * 2239 * @param g the graphics 2240 * @return the previous antialiasing rendering hint value if the passed 2241 * graphics is instance of Graphics2D, null otherwise. 2242 */ 2243 public static Object getAndSetAntialisingHintForScaledGraphics(Graphics g) { 2244 if (isScaledGraphics(g) && isLocalDisplay()) { 2245 Graphics2D g2d = (Graphics2D) g; 2246 Object hint = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING); 2247 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, 2248 RenderingHints.VALUE_ANTIALIAS_ON); 2249 return hint; 2250 } 2251 return null; 2252 } 2253 2254 /** 2255 * Sets the antialiasing rendering hint if its value is not null. 2256 * Null hint value indicates that the passed graphics is not instance of 2257 * Graphics2D. 2258 * 2259 * @param g the graphics 2260 * @param hint the antialiasing rendering hint 2261 */ 2262 public static void setAntialiasingHintForScaledGraphics(Graphics g, Object hint) { 2263 if (hint != null) { 2264 ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint); 2265 } 2266 } 2267 2268 public static boolean isFloatingPointScale(AffineTransform tx) { 2269 int type = tx.getType() & ~(TYPE_FLIP | TYPE_TRANSLATION); 2270 if (type == 0) { 2271 return false; 2272 } else if ((type & ~TYPE_MASK_SCALE) == 0) { 2273 double scaleX = tx.getScaleX(); 2274 double scaleY = tx.getScaleY(); 2275 return (scaleX != (int) scaleX) || (scaleY != (int) scaleY); 2276 } else { 2277 return false; 2278 } 2279 } 2280 2281 /** 2282 * Returns the client property for the given key if it is set; otherwise 2283 * returns the {@L&F} property. 2284 * 2285 * @param component the component 2286 * @param key an {@code String} specifying the key for the desired boolean value 2287 * @return the boolean value of the client property if it is set or the {@L&F} 2288 * property in other case. 2289 */ 2290 public static boolean getBoolean(JComponent component, String key) { 2291 Object clientProperty = component.getClientProperty(key); 2292 2293 if (clientProperty instanceof Boolean) { 2294 return Boolean.TRUE.equals(clientProperty); 2295 } 2296 2297 return UIManager.getBoolean(key); 2298 } 2299 2300 /** 2301 * Used to listen to "blit" repaints in RepaintManager. 2302 */ 2303 public interface RepaintListener { 2304 void repaintPerformed(JComponent c, int x, int y, int w, int h); 2305 } 2306 2307 /** 2308 * Returns whether or not the scale used by {@code GraphicsConfiguration} 2309 * was changed. 2310 * 2311 * @param ev a {@code PropertyChangeEvent} 2312 * @return whether or not the scale was changed 2313 * @since 11 2314 */ 2315 public static boolean isScaleChanged(final PropertyChangeEvent ev) { 2316 return isScaleChanged(ev.getPropertyName(), ev.getOldValue(), 2317 ev.getNewValue()); 2318 } 2319 2320 /** 2321 * Returns whether or not the scale used by {@code GraphicsConfiguration} 2322 * was changed. 2323 * 2324 * @param name the name of the property 2325 * @param oldValue the old value of the property 2326 * @param newValue the new value of the property 2327 * @return whether or not the scale was changed 2328 * @since 11 2329 */ 2330 public static boolean isScaleChanged(final String name, 2331 final Object oldValue, 2332 final Object newValue) { 2333 if (oldValue == newValue || !"graphicsConfiguration".equals(name)) { 2334 return false; 2335 } 2336 var newGC = (GraphicsConfiguration) oldValue; 2337 var oldGC = (GraphicsConfiguration) newValue; 2338 var newTx = newGC != null ? newGC.getDefaultTransform() : null; 2339 var oldTx = oldGC != null ? oldGC.getDefaultTransform() : null; 2340 return !Objects.equals(newTx, oldTx); 2341 } 2342 }