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 }