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