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