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 }