1 /* 2 * Copyright (c) 2010, 2016, 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 com.sun.javafx.util; 27 28 import static com.sun.javafx.FXPermissions.ACCESS_WINDOW_LIST_PERMISSION; 29 import javafx.geometry.BoundingBox; 30 import javafx.geometry.Bounds; 31 import javafx.geometry.HPos; 32 import javafx.geometry.NodeOrientation; 33 import javafx.geometry.Point2D; 34 import javafx.geometry.Rectangle2D; 35 import javafx.geometry.VPos; 36 import javafx.scene.Node; 37 import javafx.scene.Scene; 38 import javafx.scene.paint.Color; 39 import javafx.scene.paint.Stop; 40 import javafx.stage.Screen; 41 import javafx.stage.Stage; 42 import javafx.stage.Window; 43 import java.util.List; 44 import com.sun.javafx.PlatformUtil; 45 import java.security.AccessController; 46 import java.security.PrivilegedAction; 47 48 /** 49 * Some basic utilities which need to be in java (for shifting operations or 50 * other reasons), which are not toolkit dependent. 51 * 52 */ 53 public class Utils { 54 55 /*************************************************************************** 56 * * 57 * Math-related utilities * 58 * * 59 **************************************************************************/ 60 61 /** 62 * Simple utility function which clamps the given value to be strictly 63 * between the min and max values. 64 */ 65 public static float clamp(float min, float value, float max) { 66 if (value < min) return min; 67 if (value > max) return max; 68 return value; 69 } 70 71 /** 72 * Simple utility function which clamps the given value to be strictly 73 * between the min and max values. 74 */ 75 public static int clamp(int min, int value, int max) { 76 if (value < min) return min; 77 if (value > max) return max; 78 return value; 79 } 80 81 /** 82 * Simple utility function which clamps the given value to be strictly 83 * between the min and max values. 84 */ 85 public static double clamp(double min, double value, double max) { 86 if (value < min) return min; 87 if (value > max) return max; 88 return value; 89 } 90 91 /** 92 * Simple utility function which clamps the given value to be strictly 93 * above the min value. 94 */ 95 public static double clampMin(double value, double min) { 96 if (value < min) return min; 97 return value; 98 } 99 100 /** 101 * Simple utility function which clamps the given value to be strictly 102 * under the max value. 103 */ 104 public static int clampMax(int value, int max) { 105 if (value > max) return max; 106 return value; 107 } 108 109 /** 110 * Utility function which returns either {@code less} or {@code more} 111 * depending on which {@code value} is closer to. If {@code value} 112 * is perfectly between them, then either may be returned. 113 */ 114 public static double nearest(double less, double value, double more) { 115 double lessDiff = value - less; 116 double moreDiff = more - value; 117 if (lessDiff < moreDiff) return less; 118 return more; 119 } 120 121 /*************************************************************************** 122 * * 123 * String-related utilities * 124 * * 125 **************************************************************************/ 126 127 /** 128 * Helper to remove leading and trailing quotes from a string. 129 * Works with single or double quotes. 130 */ 131 public static String stripQuotes(String str) { 132 if (str == null) return str; 133 if (str.length() == 0) return str; 134 135 int beginIndex = 0; 136 final char openQuote = str.charAt(beginIndex); 137 if ( openQuote == '\"' || openQuote=='\'' ) beginIndex += 1; 138 139 int endIndex = str.length(); 140 final char closeQuote = str.charAt(endIndex - 1); 141 if ( closeQuote == '\"' || closeQuote=='\'' ) endIndex -= 1; 142 143 if ((endIndex - beginIndex) < 0) return str; 144 145 // note that String.substring returns "this" if beginIndex == 0 && endIndex == count 146 // or a new string that shares the character buffer with the original string. 147 return str.substring(beginIndex, endIndex); 148 } 149 150 /** 151 * Because mobile doesn't have string.split(s) function, this function 152 * was written. 153 */ 154 public static String[] split(String str, String separator) { 155 if (str == null || str.length() == 0) return new String[] { }; 156 if (separator == null || separator.length() == 0) return new String[] { }; 157 if (separator.length() > str.length()) return new String[] { }; 158 159 java.util.List<String> result = new java.util.ArrayList<String>(); 160 161 int index = str.indexOf(separator); 162 while (index >= 0) { 163 String newStr = str.substring(0, index); 164 if (newStr != null && newStr.length() > 0) { 165 result.add(newStr); 166 } 167 str = str.substring(index + separator.length()); 168 index = str.indexOf(separator); 169 } 170 171 if (str != null && str.length() > 0) { 172 result.add(str); 173 } 174 175 return result.toArray(new String[] { }); 176 } 177 178 /** 179 * Because mobile doesn't have string.contains(s) function, this function 180 * was written. 181 */ 182 public static boolean contains(String src, String s) { 183 if (src == null || src.length() == 0) return false; 184 if (s == null || s.length() == 0) return false; 185 if (s.length() > src.length()) return false; 186 187 return src.indexOf(s) > -1; 188 } 189 190 /*************************************************************************** 191 * * 192 * Color-related utilities * 193 * * 194 **************************************************************************/ 195 196 /** 197 * Calculates a perceptual brightness for a color between 0.0 black and 1.0 while 198 */ 199 public static double calculateBrightness(Color color) { 200 return (0.3*color.getRed()) + (0.59*color.getGreen()) + (0.11*color.getBlue()); 201 } 202 203 /** 204 * Derives a lighter or darker of a given color. 205 * 206 * @param c The color to derive from 207 * @param brightness The brightness difference for the new color -1.0 being 100% dark which is always black, 0.0 being 208 * no change and 1.0 being 100% lighter which is always white 209 */ 210 public static Color deriveColor(Color c, double brightness) { 211 double baseBrightness = calculateBrightness(c); 212 double calcBrightness = brightness; 213 // Fine adjustments to colors in ranges of brightness to adjust the contrast for them 214 if (brightness > 0) { 215 if (baseBrightness > 0.85) { 216 calcBrightness = calcBrightness * 1.6; 217 } else if (baseBrightness > 0.6) { 218 // no change 219 } else if (baseBrightness > 0.5) { 220 calcBrightness = calcBrightness * 0.9; 221 } else if (baseBrightness > 0.4) { 222 calcBrightness = calcBrightness * 0.8; 223 } else if (baseBrightness > 0.3) { 224 calcBrightness = calcBrightness * 0.7; 225 } else { 226 calcBrightness = calcBrightness * 0.6; 227 } 228 } else { 229 if (baseBrightness < 0.2) { 230 calcBrightness = calcBrightness * 0.6; 231 } 232 } 233 // clamp brightness 234 if (calcBrightness < -1) { calcBrightness = -1; } else if (calcBrightness > 1) {calcBrightness = 1;} 235 // window two take the calculated brightness multiplyer and derive color based on source color 236 double[] hsb = RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue()); 237 // change brightness 238 if (calcBrightness > 0) { // brighter 239 hsb[1] *= 1 - calcBrightness; 240 hsb[2] += (1 - hsb[2]) * calcBrightness; 241 } else { // darker 242 hsb[2] *= calcBrightness + 1; 243 } 244 // clip saturation and brightness 245 if (hsb[1] < 0) { hsb[1] = 0;} else if (hsb[1] > 1) {hsb[1] = 1;} 246 if (hsb[2] < 0) { hsb[2] = 0;} else if (hsb[2] > 1) {hsb[2] = 1;} 247 // convert back to color 248 Color c2 = Color.hsb((int)hsb[0], hsb[1], hsb[2],c.getOpacity()); 249 return Color.hsb((int)hsb[0], hsb[1], hsb[2],c.getOpacity()); 250 251 /* var hsb:Number[] = RGBtoHSB(c.red,c.green,c.blue); 252 // change brightness 253 if (brightness > 0) { 254 //var bright:Number = brightness * (1-calculateBrightness(c)); 255 var bright:Number = if (calculateBrightness(c)<0.65 and brightness > 0.5) { 256 if (calculateBrightness(c)<0.2) then brightness * 0.55 else brightness * 0.7 257 } else brightness; 258 // brighter 259 hsb[1] *= 1 - bright; 260 hsb[2] += (1 - hsb[2]) * bright; 261 } else { 262 // darker 263 hsb[2] *= brightness+1; 264 } 265 // clip saturation and brightness 266 if (hsb[1] < 0) { hsb[1] = 0;} else if (hsb[1] > 1) {hsb[1] = 1} 267 if (hsb[2] < 0) { hsb[2] = 0;} else if (hsb[2] > 1) {hsb[2] = 1} 268 // convert back to color 269 return Color.hsb(hsb[0],hsb[1],hsb[2]) */ 270 } 271 272 /** 273 * interpolate at a set {@code position} between two colors {@code color1} and {@code color2}. 274 * The interpolation is done is linear RGB color space not the default sRGB color space. 275 */ 276 private static Color interpolateLinear(double position, Color color1, Color color2) { 277 Color c1Linear = convertSRGBtoLinearRGB(color1); 278 Color c2Linear = convertSRGBtoLinearRGB(color2); 279 return convertLinearRGBtoSRGB(Color.color( 280 c1Linear.getRed() + (c2Linear.getRed() - c1Linear.getRed()) * position, 281 c1Linear.getGreen() + (c2Linear.getGreen() - c1Linear.getGreen()) * position, 282 c1Linear.getBlue() + (c2Linear.getBlue() - c1Linear.getBlue()) * position, 283 c1Linear.getOpacity() + (c2Linear.getOpacity() - c1Linear.getOpacity()) * position 284 )); 285 } 286 287 /** 288 * Get the color at the give {@code position} in the ladder of color stops 289 */ 290 private static Color ladder(final double position, final Stop[] stops) { 291 Stop prevStop = null; 292 for (int i=0; i<stops.length; i++) { 293 Stop stop = stops[i]; 294 if(position <= stop.getOffset()){ 295 if (prevStop == null) { 296 return stop.getColor(); 297 } else { 298 return interpolateLinear((position-prevStop.getOffset())/(stop.getOffset()-prevStop.getOffset()), prevStop.getColor(), stop.getColor()); 299 } 300 } 301 prevStop = stop; 302 } 303 // position is greater than biggest stop, so will we biggest stop's color 304 return prevStop.getColor(); 305 } 306 307 /** 308 * Get the color at the give {@code position} in the ladder of color stops 309 */ 310 public static Color ladder(final Color color, final Stop[] stops) { 311 return ladder(calculateBrightness(color), stops); 312 } 313 314 public static double[] HSBtoRGB(double hue, double saturation, double brightness) { 315 // normalize the hue 316 double normalizedHue = ((hue % 360) + 360) % 360; 317 hue = normalizedHue/360; 318 319 double r = 0, g = 0, b = 0; 320 if (saturation == 0) { 321 r = g = b = brightness; 322 } else { 323 double h = (hue - Math.floor(hue)) * 6.0; 324 double f = h - java.lang.Math.floor(h); 325 double p = brightness * (1.0 - saturation); 326 double q = brightness * (1.0 - saturation * f); 327 double t = brightness * (1.0 - (saturation * (1.0 - f))); 328 switch ((int) h) { 329 case 0: 330 r = brightness; 331 g = t; 332 b = p; 333 break; 334 case 1: 335 r = q; 336 g = brightness; 337 b = p; 338 break; 339 case 2: 340 r = p; 341 g = brightness; 342 b = t; 343 break; 344 case 3: 345 r = p; 346 g = q; 347 b = brightness; 348 break; 349 case 4: 350 r = t; 351 g = p; 352 b = brightness; 353 break; 354 case 5: 355 r = brightness; 356 g = p; 357 b = q; 358 break; 359 } 360 } 361 double[] f = new double[3]; 362 f[0] = r; 363 f[1] = g; 364 f[2] = b; 365 return f; 366 } 367 368 public static double[] RGBtoHSB(double r, double g, double b) { 369 double hue, saturation, brightness; 370 double[] hsbvals = new double[3]; 371 double cmax = (r > g) ? r : g; 372 if (b > cmax) cmax = b; 373 double cmin = (r < g) ? r : g; 374 if (b < cmin) cmin = b; 375 376 brightness = cmax; 377 if (cmax != 0) 378 saturation = (double) (cmax - cmin) / cmax; 379 else 380 saturation = 0; 381 382 if (saturation == 0) { 383 hue = 0; 384 } else { 385 double redc = (cmax - r) / (cmax - cmin); 386 double greenc = (cmax - g) / (cmax - cmin); 387 double bluec = (cmax - b) / (cmax - cmin); 388 if (r == cmax) 389 hue = bluec - greenc; 390 else if (g == cmax) 391 hue = 2.0 + redc - bluec; 392 else 393 hue = 4.0 + greenc - redc; 394 hue = hue / 6.0; 395 if (hue < 0) 396 hue = hue + 1.0; 397 } 398 hsbvals[0] = hue * 360; 399 hsbvals[1] = saturation; 400 hsbvals[2] = brightness; 401 return hsbvals; 402 } 403 404 /** 405 * Helper function to convert a color in sRGB space to linear RGB space. 406 */ 407 public static Color convertSRGBtoLinearRGB(Color color) { 408 double[] colors = new double[] { color.getRed(), color.getGreen(), color.getBlue() }; 409 for (int i=0; i<colors.length; i++) { 410 if (colors[i] <= 0.04045) { 411 colors[i] = colors[i] / 12.92; 412 } else { 413 colors[i] = Math.pow((colors[i] + 0.055) / 1.055, 2.4); 414 } 415 } 416 return Color.color(colors[0], colors[1], colors[2], color.getOpacity()); 417 } 418 419 /** 420 * Helper function to convert a color in linear RGB space to SRGB space. 421 */ 422 public static Color convertLinearRGBtoSRGB(Color color) { 423 double[] colors = new double[] { color.getRed(), color.getGreen(), color.getBlue() }; 424 for (int i=0; i<colors.length; i++) { 425 if (colors[i] <= 0.0031308) { 426 colors[i] = colors[i] * 12.92; 427 } else { 428 colors[i] = (1.055 * Math.pow(colors[i], (1.0 / 2.4))) - 0.055; 429 } 430 } 431 return Color.color(colors[0], colors[1], colors[2], color.getOpacity()); 432 } 433 434 /** helper function for calculating the sum of a series of numbers */ 435 public static double sum(double[] values) { 436 double sum = 0; 437 for (double v : values) sum = sum+v; 438 return sum / values.length; 439 } 440 441 public static Point2D pointRelativeTo(Node parent, Node node, HPos hpos, 442 VPos vpos, double dx, double dy, boolean reposition) 443 { 444 final double nodeWidth = node.getLayoutBounds().getWidth(); 445 final double nodeHeight = node.getLayoutBounds().getHeight(); 446 return pointRelativeTo(parent, nodeWidth, nodeHeight, hpos, vpos, dx, dy, reposition); 447 } 448 449 public static Point2D pointRelativeTo(Node parent, double anchorWidth, 450 double anchorHeight, HPos hpos, VPos vpos, double dx, double dy, 451 boolean reposition) 452 { 453 final Bounds parentBounds = getBounds(parent); 454 Scene scene = parent.getScene(); 455 NodeOrientation orientation = parent.getEffectiveNodeOrientation(); 456 457 if (orientation == NodeOrientation.RIGHT_TO_LEFT) { 458 if (hpos == HPos.LEFT) { 459 hpos = HPos.RIGHT; 460 } else if (hpos == HPos.RIGHT) { 461 hpos = HPos.LEFT; 462 } 463 dx *= -1; 464 } 465 466 double layoutX = positionX(parentBounds, anchorWidth, hpos) + dx; 467 final double layoutY = positionY(parentBounds, anchorHeight, vpos) + dy; 468 469 if (orientation == NodeOrientation.RIGHT_TO_LEFT && hpos == HPos.CENTER) { 470 //TODO - testing for an instance of Stage seems wrong but works for menus 471 if (scene.getWindow() instanceof Stage) { 472 layoutX = layoutX + parentBounds.getWidth() - anchorWidth; 473 } else { 474 layoutX = layoutX - parentBounds.getWidth() - anchorWidth; 475 } 476 } 477 478 if (reposition) { 479 return pointRelativeTo(parent, anchorWidth, anchorHeight, layoutX, layoutY, hpos, vpos); 480 } else { 481 return new Point2D(layoutX, layoutY); 482 } 483 } 484 485 /** 486 * This is the fallthrough function that most other functions fall into. It takes 487 * care specifically of the repositioning of the item such that it remains onscreen 488 * as best it can, given it's unique qualities. 489 * 490 * As will all other functions, this one returns a Point2D that represents an x,y 491 * location that should safely position the item onscreen as best as possible. 492 * 493 * Note that <code>width</code> and <height> refer to the width and height of the 494 * node/popup that is needing to be repositioned, not of the parent. 495 * 496 * Don't use the BASELINE vpos, it doesn't make sense and would produce wrong result. 497 */ 498 public static Point2D pointRelativeTo(Object parent, double width, 499 double height, double screenX, double screenY, HPos hpos, VPos vpos) 500 { 501 double finalScreenX = screenX; 502 double finalScreenY = screenY; 503 final Bounds parentBounds = getBounds(parent); 504 505 // ...and then we get the bounds of this screen 506 final Screen currentScreen = getScreen(parent); 507 final Rectangle2D screenBounds = 508 hasFullScreenStage(currentScreen) 509 ? currentScreen.getBounds() 510 : currentScreen.getVisualBounds(); 511 512 // test if this layout will force the node to appear outside 513 // of the screens bounds. If so, we must reposition the item to a better position. 514 // We firstly try to do this intelligently, so as to not overlap the parent if 515 // at all possible. 516 if (hpos != null) { 517 // Firstly we consider going off the right hand side 518 if ((finalScreenX + width) > screenBounds.getMaxX()) { 519 finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos)); 520 } 521 522 // don't let the node go off to the left of the current screen 523 if (finalScreenX < screenBounds.getMinX()) { 524 finalScreenX = positionX(parentBounds, width, getHPosOpposite(hpos, vpos)); 525 } 526 } 527 528 if (vpos != null) { 529 // don't let the node go off the bottom of the current screen 530 if ((finalScreenY + height) > screenBounds.getMaxY()) { 531 finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos,vpos)); 532 } 533 534 // don't let the node out of the top of the current screen 535 if (finalScreenY < screenBounds.getMinY()) { 536 finalScreenY = positionY(parentBounds, height, getVPosOpposite(hpos,vpos)); 537 } 538 } 539 540 // --- after all the moving around, we do one last check / rearrange. 541 // Unlike the check above, this time we are just fully committed to keeping 542 // the item on screen at all costs, regardless of whether or not that results 543 /// in overlapping the parent object. 544 if ((finalScreenX + width) > screenBounds.getMaxX()) { 545 finalScreenX -= (finalScreenX + width - screenBounds.getMaxX()); 546 } 547 if (finalScreenX < screenBounds.getMinX()) { 548 finalScreenX = screenBounds.getMinX(); 549 } 550 if ((finalScreenY + height) > screenBounds.getMaxY()) { 551 finalScreenY -= (finalScreenY + height - screenBounds.getMaxY()); 552 } 553 if (finalScreenY < screenBounds.getMinY()) { 554 finalScreenY = screenBounds.getMinY(); 555 } 556 557 return new Point2D(finalScreenX, finalScreenY); 558 } 559 560 /** 561 * Utility function that returns the x-axis position that an object should be positioned at, 562 * given the parents screen bounds, the width of the object, and 563 * the required HPos. 564 */ 565 private static double positionX(Bounds parentBounds, double width, HPos hpos) { 566 if (hpos == HPos.CENTER) { 567 // this isn't right, but it is needed for root menus to show properly 568 return parentBounds.getMinX(); 569 } else if (hpos == HPos.RIGHT) { 570 return parentBounds.getMaxX(); 571 } else if (hpos == HPos.LEFT) { 572 return parentBounds.getMinX() - width; 573 } else { 574 return 0; 575 } 576 } 577 578 /** 579 * Utility function that returns the y-axis position that an object should be positioned at, 580 * given the parents screen bounds, the height of the object, and 581 * the required VPos. 582 * 583 * The BASELINE vpos doesn't make sense here, 0 is returned for it. 584 */ 585 private static double positionY(Bounds parentBounds, double height, VPos vpos) { 586 if (vpos == VPos.BOTTOM) { 587 return parentBounds.getMaxY(); 588 } else if (vpos == VPos.CENTER) { 589 return parentBounds.getMinY(); 590 } else if (vpos == VPos.TOP) { 591 return parentBounds.getMinY() - height; 592 } else { 593 return 0; 594 } 595 } 596 597 /** 598 * To facilitate multiple types of parent object, we unfortunately must allow for 599 * Objects to be passed in. This method handles determining the bounds of the 600 * given Object. If the Object type is not supported, a default Bounds will be returned. 601 */ 602 private static Bounds getBounds(Object obj) { 603 if (obj instanceof Node) { 604 final Node n = (Node)obj; 605 Bounds b = n.localToScreen(n.getLayoutBounds()); 606 return b != null ? b : new BoundingBox(0, 0, 0, 0); 607 } else if (obj instanceof Window) { 608 final Window window = (Window)obj; 609 return new BoundingBox(window.getX(), window.getY(), window.getWidth(), window.getHeight()); 610 } else { 611 return new BoundingBox(0, 0, 0, 0); 612 } 613 } 614 615 /* 616 * Simple utitilty function to return the 'opposite' value of a given HPos, taking 617 * into account the current VPos value. This is used to try and avoid overlapping. 618 */ 619 private static HPos getHPosOpposite(HPos hpos, VPos vpos) { 620 if (vpos == VPos.CENTER) { 621 if (hpos == HPos.LEFT){ 622 return HPos.RIGHT; 623 } else if (hpos == HPos.RIGHT){ 624 return HPos.LEFT; 625 } else if (hpos == HPos.CENTER){ 626 return HPos.CENTER; 627 } else { 628 // by default center for now 629 return HPos.CENTER; 630 } 631 } else { 632 return HPos.CENTER; 633 } 634 } 635 636 /* 637 * Simple utitilty function to return the 'opposite' value of a given VPos, taking 638 * into account the current HPos value. This is used to try and avoid overlapping. 639 */ 640 private static VPos getVPosOpposite(HPos hpos, VPos vpos) { 641 if (hpos == HPos.CENTER) { 642 if (vpos == VPos.BASELINE){ 643 return VPos.BASELINE; 644 } else if (vpos == VPos.BOTTOM){ 645 return VPos.TOP; 646 } else if (vpos == VPos.CENTER){ 647 return VPos.CENTER; 648 } else if (vpos == VPos.TOP){ 649 return VPos.BOTTOM; 650 } else { 651 // by default center for now 652 return VPos.CENTER; 653 } 654 } else { 655 return VPos.CENTER; 656 } 657 } 658 659 public static boolean hasFullScreenStage(final Screen screen) { 660 final List<Window> allWindows = AccessController.doPrivileged( 661 (PrivilegedAction<List<Window>>) () -> Window.getWindows(), 662 null, 663 ACCESS_WINDOW_LIST_PERMISSION); 664 665 for (final Window window : allWindows) { 666 if (window instanceof Stage) { 667 final Stage stage = (Stage) window; 668 if (stage.isFullScreen() && (getScreen(stage) == screen)) { 669 return true; 670 } 671 } 672 } 673 return false; 674 } 675 676 /* 677 * Returns true if the primary Screen has QVGA dimensions, in landscape or portrait mode. 678 */ 679 public static boolean isQVGAScreen() { 680 Rectangle2D bounds = Screen.getPrimary().getBounds(); 681 return ((bounds.getWidth() == 320 && bounds.getHeight() == 240) || 682 (bounds.getWidth() == 240 && bounds.getHeight() == 320)); 683 } 684 685 /** 686 * This function attempts to determine the best screen given the parent object 687 * from which we are wanting to position another item relative to. This is particularly 688 * important when we want to keep items from going off screen, and for handling 689 * multiple monitor support. 690 */ 691 public static Screen getScreen(Object obj) { 692 final Bounds parentBounds = getBounds(obj); 693 694 final Rectangle2D rect = new Rectangle2D( 695 parentBounds.getMinX(), 696 parentBounds.getMinY(), 697 parentBounds.getWidth(), 698 parentBounds.getHeight()); 699 700 return getScreenForRectangle(rect); 701 } 702 703 public static Screen getScreenForRectangle(final Rectangle2D rect) { 704 final List<Screen> screens = Screen.getScreens(); 705 706 final double rectX0 = rect.getMinX(); 707 final double rectX1 = rect.getMaxX(); 708 final double rectY0 = rect.getMinY(); 709 final double rectY1 = rect.getMaxY(); 710 711 Screen selectedScreen; 712 713 selectedScreen = null; 714 double maxIntersection = 0; 715 for (final Screen screen: screens) { 716 final Rectangle2D screenBounds = screen.getBounds(); 717 final double intersection = 718 getIntersectionLength(rectX0, rectX1, 719 screenBounds.getMinX(), 720 screenBounds.getMaxX()) 721 * getIntersectionLength(rectY0, rectY1, 722 screenBounds.getMinY(), 723 screenBounds.getMaxY()); 724 725 if (maxIntersection < intersection) { 726 maxIntersection = intersection; 727 selectedScreen = screen; 728 } 729 } 730 731 if (selectedScreen != null) { 732 return selectedScreen; 733 } 734 735 selectedScreen = Screen.getPrimary(); 736 double minDistance = Double.MAX_VALUE; 737 for (final Screen screen: screens) { 738 final Rectangle2D screenBounds = screen.getBounds(); 739 final double dx = getOuterDistance(rectX0, rectX1, 740 screenBounds.getMinX(), 741 screenBounds.getMaxX()); 742 final double dy = getOuterDistance(rectY0, rectY1, 743 screenBounds.getMinY(), 744 screenBounds.getMaxY()); 745 final double distance = dx * dx + dy * dy; 746 747 if (minDistance > distance) { 748 minDistance = distance; 749 selectedScreen = screen; 750 } 751 } 752 753 return selectedScreen; 754 } 755 756 public static Screen getScreenForPoint(final double x, final double y) { 757 final List<Screen> screens = Screen.getScreens(); 758 759 // first check whether the point is inside some screen 760 for (final Screen screen: screens) { 761 // can't use screen.bounds.contains, because it returns true for 762 // the min + width point 763 final Rectangle2D screenBounds = screen.getBounds(); 764 if ((x >= screenBounds.getMinX()) 765 && (x < screenBounds.getMaxX()) 766 && (y >= screenBounds.getMinY()) 767 && (y < screenBounds.getMaxY())) { 768 return screen; 769 } 770 } 771 772 // the point is not inside any screen, find the closest screen now 773 Screen selectedScreen = Screen.getPrimary(); 774 double minDistance = Double.MAX_VALUE; 775 for (final Screen screen: screens) { 776 final Rectangle2D screenBounds = screen.getBounds(); 777 final double dx = getOuterDistance(screenBounds.getMinX(), 778 screenBounds.getMaxX(), 779 x); 780 final double dy = getOuterDistance(screenBounds.getMinY(), 781 screenBounds.getMaxY(), 782 y); 783 final double distance = dx * dx + dy * dy; 784 if (minDistance >= distance) { 785 minDistance = distance; 786 selectedScreen = screen; 787 } 788 } 789 790 return selectedScreen; 791 } 792 793 private static double getIntersectionLength( 794 final double a0, final double a1, 795 final double b0, final double b1) { 796 // (a0 <= a1) && (b0 <= b1) 797 return (a0 <= b0) ? getIntersectionLengthImpl(b0, b1, a1) 798 : getIntersectionLengthImpl(a0, a1, b1); 799 } 800 801 private static double getIntersectionLengthImpl( 802 final double v0, final double v1, final double v) { 803 // (v0 <= v1) 804 if (v <= v0) { 805 return 0; 806 } 807 808 return (v <= v1) ? v - v0 : v1 - v0; 809 } 810 811 private static double getOuterDistance( 812 final double a0, final double a1, 813 final double b0, final double b1) { 814 // (a0 <= a1) && (b0 <= b1) 815 if (a1 <= b0) { 816 return b0 - a1; 817 } 818 819 if (b1 <= a0) { 820 return b1 - a0; 821 } 822 823 return 0; 824 } 825 826 private static double getOuterDistance(final double v0, 827 final double v1, 828 final double v) { 829 // (v0 <= v1) 830 if (v <= v0) { 831 return v0 - v; 832 } 833 834 if (v >= v1) { 835 return v - v1; 836 } 837 838 return 0; 839 } 840 841 /*************************************************************************** 842 * * 843 * Miscellaneous utilities * 844 * * 845 **************************************************************************/ 846 847 /** 848 * To force initialization of a class 849 * @param classToInit 850 */ 851 public static void forceInit(final Class<?> classToInit) { 852 try { 853 Class.forName(classToInit.getName(), true, 854 classToInit.getClassLoader()); 855 } catch (final ClassNotFoundException e) { 856 throw new AssertionError(e); // Can't happen 857 } 858 } 859 860 public static boolean assertionEnabled() { 861 boolean assertsEnabled = false; 862 assert assertsEnabled = true; // Intentional side-effect !!! 863 864 return assertsEnabled; 865 } 866 867 /** 868 * Returns true if the operating system is a form of Windows. 869 */ 870 public static boolean isWindows(){ 871 return PlatformUtil.isWindows(); 872 } 873 874 /** 875 * Returns true if the operating system is a form of Mac OS. 876 */ 877 public static boolean isMac(){ 878 return PlatformUtil.isMac(); 879 } 880 881 /** 882 * Returns true if the operating system is a form of Unix, including Linux. 883 */ 884 public static boolean isUnix(){ 885 return PlatformUtil.isUnix(); 886 } 887 888 /*************************************************************************** 889 * * 890 * Unicode-related utilities * 891 * * 892 **************************************************************************/ 893 894 public static String convertUnicode(String src) { 895 /** The input buffer, index of next character to be read, 896 * index of one past last character in buffer. 897 */ 898 char[] buf; 899 int bp; 900 int buflen; 901 902 /** The current character. 903 */ 904 char ch; 905 906 /** The buffer index of the last converted unicode character 907 */ 908 int unicodeConversionBp = -1; 909 910 buf = src.toCharArray(); 911 buflen = buf.length; 912 bp = -1; 913 914 char[] dst = new char[buflen]; 915 int dstIndex = 0; 916 917 while (bp < buflen - 1) { 918 ch = buf[++bp]; 919 if (ch == '\\') { 920 if (unicodeConversionBp != bp) { 921 bp++; ch = buf[bp]; 922 if (ch == 'u') { 923 do { 924 bp++; ch = buf[bp]; 925 } while (ch == 'u'); 926 int limit = bp + 3; 927 if (limit < buflen) { 928 char c = ch; 929 int result = Character.digit(c, 16); 930 if (result >= 0 && c > 0x7f) { 931 //lexError(pos+1, "illegal.nonascii.digit"); 932 ch = "0123456789abcdef".charAt(result); 933 } 934 int d = result; 935 int code = d; 936 while (bp < limit && d >= 0) { 937 bp++; ch = buf[bp]; 938 char c1 = ch; 939 int result1 = Character.digit(c1, 16); 940 if (result1 >= 0 && c1 > 0x7f) { 941 //lexError(pos+1, "illegal.nonascii.digit"); 942 ch = "0123456789abcdef".charAt(result1); 943 } 944 d = result1; 945 code = (code << 4) + d; 946 } 947 if (d >= 0) { 948 ch = (char)code; 949 unicodeConversionBp = bp; 950 } 951 } 952 //lexError(bp, "illegal.unicode.esc"); 953 } else { 954 bp--; 955 ch = '\\'; 956 } 957 } 958 } 959 dst[dstIndex++] = ch; 960 } 961 962 return new String(dst, 0, dstIndex); 963 } 964 }