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