< prev index next >

modules/controls/src/main/java/com/sun/javafx/scene/control/skin/Utils.java

Print this page


   1 /*
   2  * Copyright (c) 2011, 2015, 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


  37 import javafx.application.Platform;
  38 import javafx.beans.InvalidationListener;
  39 import javafx.beans.Observable;
  40 import javafx.beans.value.ObservableValue;
  41 import javafx.collections.ObservableList;
  42 import javafx.geometry.Bounds;
  43 import javafx.geometry.HPos;
  44 import javafx.geometry.Point2D;
  45 import javafx.geometry.VPos;
  46 import javafx.scene.Scene;
  47 import javafx.scene.control.ContextMenu;
  48 import javafx.scene.control.MenuItem;
  49 import javafx.scene.control.OverrunStyle;
  50 import com.sun.javafx.scene.control.ContextMenuContent;
  51 import javafx.scene.input.KeyCombination;
  52 import javafx.scene.input.Mnemonic;
  53 import javafx.scene.paint.Color;
  54 import javafx.scene.text.Font;
  55 import javafx.scene.text.Text;
  56 import javafx.scene.text.TextBoundsType;

  57 
  58 import java.text.Bidi;
  59 import java.text.BreakIterator;
  60 import java.util.Locale;
  61 import java.util.function.Consumer;
  62 
  63 import static javafx.scene.control.OverrunStyle.CENTER_ELLIPSIS;
  64 import static javafx.scene.control.OverrunStyle.CENTER_WORD_ELLIPSIS;
  65 import static javafx.scene.control.OverrunStyle.CLIP;
  66 import static javafx.scene.control.OverrunStyle.ELLIPSIS;
  67 import static javafx.scene.control.OverrunStyle.LEADING_ELLIPSIS;
  68 import static javafx.scene.control.OverrunStyle.LEADING_WORD_ELLIPSIS;
  69 import static javafx.scene.control.OverrunStyle.WORD_ELLIPSIS;
  70 import static javafx.scene.control.skin.TextFieldSkin.TextPosInfo;
  71 
  72 /**
  73  * BE REALLY CAREFUL WITH RESTORING OR RESETTING STATE OF helper NODE AS LEFTOVER
  74  * STATE CAUSES REALLY ODD NASTY BUGS!
  75  *
  76  * We expect all methods to set the Font property of helper but other than that
  77  * any properties set should be restored to defaults.
  78  */
  79 public class Utils {
  80 
  81     static final Text helper = new Text();
  82     static final double DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth();
  83     static final double DEFAULT_LINE_SPACING = helper.getLineSpacing();
  84     static final String DEFAULT_TEXT = helper.getText();
  85     static final TextBoundsType DEFAULT_BOUNDS_TYPE = helper.getBoundsType();
  86 
  87     /* Using TextLayout directly for simple text measurement.
  88      * Instead of restoring the TextLayout attributes to default values
  89      * (each renders the TextLayout unable to efficiently cache layout data).
  90      * It always sets all the attributes pertinent to calculation being performed.


 137         layout.setWrapWidth((float)wrappingWidth);
 138         layout.setLineSpacing((float)lineSpacing);
 139         if (boundsType == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
 140             layout.setBoundsType(TextLayout.BOUNDS_CENTER);
 141         } else {
 142             layout.setBoundsType(0);
 143         }
 144         return layout.getBounds().getHeight();
 145     }
 146 
 147     public static int computeTruncationIndex(Font font, String text, double width) {
 148         helper.setText(text);
 149         helper.setFont(font);
 150         helper.setWrappingWidth(0);
 151         helper.setLineSpacing(0);
 152         // The -2 is a fudge to make sure the result more often matches
 153         // what we get from using computeTextWidth instead. It's not yet
 154         // clear what causes the small discrepancies.
 155         Bounds bounds = helper.getLayoutBounds();
 156         Point2D endPoint = new Point2D(width - 2, bounds.getMinY() + bounds.getHeight() / 2);
 157         final int index = helper.impl_hitTestChar(endPoint).getCharIndex();
 158         // RESTORE STATE
 159         helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH);
 160         helper.setLineSpacing(DEFAULT_LINE_SPACING);
 161         helper.setText(DEFAULT_TEXT);
 162         return index;
 163     }
 164 
 165     public static String computeClippedText(Font font, String text, double width,
 166                                      OverrunStyle type, String ellipsisString) {
 167         if (font == null) {
 168             throw new IllegalArgumentException("Must specify a font");
 169         }
 170         OverrunStyle style = (type == null || type == CLIP) ? ELLIPSIS : type;
 171         final String ellipsis = (type == CLIP) ? "" : ellipsisString;
 172         // if the text is empty or null or no ellipsis, then it always fits
 173         if (text == null || "".equals(text)) {
 174             return text;
 175         }
 176         // if the string width is < the available width, then it fits and
 177         // doesn't need to be clipped.  We use a double point comparison


 393                             truncationStyle == CENTER_WORD_ELLIPSIS);
 394         boolean trailing = !(leading || center);
 395         boolean wordTrim = (truncationStyle == WORD_ELLIPSIS ||
 396                             truncationStyle == LEADING_WORD_ELLIPSIS ||
 397                             truncationStyle == CENTER_WORD_ELLIPSIS);
 398 
 399         String result = text;
 400         int len = (result != null) ? result.length() : 0;
 401         int centerLen = -1;
 402 
 403         Point2D centerPoint = null;
 404         if (center) {
 405             // Find index of character in the middle of the visual text area
 406             centerPoint = new Point2D((width - eWidth) / 2, height / 2 - helper.getBaselineOffset());
 407         }
 408 
 409         // Find index of character at the bottom left of the text area.
 410         // This should be the first character of a line that would be clipped.
 411         Point2D endPoint = new Point2D(0, height - helper.getBaselineOffset());
 412 
 413         int hit = helper.impl_hitTestChar(endPoint).getCharIndex();
 414         if (hit >= len) {
 415             helper.setBoundsType(TextBoundsType.LOGICAL); // restore
 416             return text;
 417         }
 418         if (center) {
 419             hit = helper.impl_hitTestChar(centerPoint).getCharIndex();
 420         }
 421 
 422         if (hit > 0 && hit < len) {
 423             // Step one, make a truncation estimate.
 424 
 425             if (center || trailing) {
 426                 int ind = hit;
 427                 if (center) {
 428                     // This is for the first part, i.e. beginning of text up to ellipsis.
 429                     if (wordTrim) {
 430                         int brInd = lastBreakCharIndex(text, ind + 1);
 431                         if (brInd >= 0) {
 432                             ind = brInd + 1;
 433                         } else {
 434                             brInd = firstBreakCharIndex(text, ind);
 435                             if (brInd >= 0) {
 436                                 ind = brInd + 1;
 437                             }
 438                         }
 439                     }


 460                         ind = brInd + 1;
 461                     } else {
 462                         brInd = firstBreakCharIndex(text, ind);
 463                         if (brInd >= 0) {
 464                             ind = brInd + 1;
 465                         }
 466                     }
 467                 }
 468                 if (center) {
 469                     // This is for the second part, i.e. from ellipsis to end of text.
 470                     result = result + text.substring(ind);
 471                 } else {
 472                     result = ellipsis + text.substring(ind);
 473                 }
 474             }
 475 
 476             // Step two, check if text still overflows after we added the ellipsis.
 477             // If so, remove one char or word at a time.
 478             while (true) {
 479                 helper.setText(result);
 480                 int hit2 = helper.impl_hitTestChar(endPoint).getCharIndex();
 481                 if (center && hit2 < centerLen) {
 482                     // No room for text after ellipsis. Maybe there is a newline
 483                     // here, and the next line falls outside the view.
 484                     if (hit2 > 0 && result.charAt(hit2-1) == '\n') {
 485                         hit2--;
 486                     }
 487                     result = text.substring(0, hit2) + ellipsis;
 488                     break;
 489                 } else if (hit2 > 0 && hit2 < result.length()) {
 490                     if (leading) {
 491                         int ind = eLen + 1; // Past ellipsis and first char.
 492                         if (wordTrim) {
 493                             int brInd = firstBreakCharIndex(result, ind);
 494                             if (brInd >= 0) {
 495                                 ind = brInd + 1;
 496                             }
 497                         }
 498                         result = ellipsis + result.substring(ind);
 499                     } else if (center) {
 500                         int ind = centerLen + 1; // Past ellipsis and first char.


 737             case BOTTOM:
 738                return height - contentHeight;
 739             default:
 740                 return 0;
 741         }
 742     }
 743 
 744     /*
 745     ** Returns true if the platform is to use Two-Level-Focus.
 746     ** This is in the Util class to ease any changes in
 747     ** the criteria for enabling this feature.
 748     **
 749     ** TwoLevelFocus is needed on platforms that
 750     ** only support 5-button navigation (arrow keys and Select/OK).
 751     **
 752     */
 753     public static boolean isTwoLevelFocus() {
 754         return Platform.isSupported(ConditionalFeature.TWO_LEVEL_FOCUS);
 755     }
 756 
 757 
 758     // Workaround for RT-26961. HitInfo.getInsertionIndex() doesn't skip
 759     // complex character clusters / ligatures.
 760     private static BreakIterator charIterator = null;
 761     public static int getHitInsertionIndex(TextPosInfo hit, String text) {
 762         int charIndex = hit.getCharIndex();
 763         if (text != null && !hit.isLeading()) {
 764             if (charIterator == null) {
 765                 charIterator = BreakIterator.getCharacterInstance();
 766             }
 767             charIterator.setText(text);
 768             int next = charIterator.following(charIndex);
 769             if (next == BreakIterator.DONE) {
 770                 charIndex = hit.getInsertionIndex();
 771             } else {
 772                 charIndex = next;
 773             }
 774         }
 775         return charIndex;
 776     }
 777 
 778     // useful method for linking things together when before a property is
 779     // necessarily set
 780     public static <T> void executeOnceWhenPropertyIsNonNull(ObservableValue<T> p, Consumer<T> consumer) {
 781         if (p == null) return;
 782 
 783         T value = p.getValue();
 784         if (value != null) {
 785             consumer.accept(value);
 786         } else {
 787             final InvalidationListener listener = new InvalidationListener() {
 788                 @Override public void invalidated(Observable observable) {
 789                     T value = p.getValue();
 790 
 791                     if (value != null) {
 792                         p.removeListener(this);
 793                         consumer.accept(value);
 794                     }
 795                 }
 796             };
   1 /*
   2  * Copyright (c) 2011, 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


  37 import javafx.application.Platform;
  38 import javafx.beans.InvalidationListener;
  39 import javafx.beans.Observable;
  40 import javafx.beans.value.ObservableValue;
  41 import javafx.collections.ObservableList;
  42 import javafx.geometry.Bounds;
  43 import javafx.geometry.HPos;
  44 import javafx.geometry.Point2D;
  45 import javafx.geometry.VPos;
  46 import javafx.scene.Scene;
  47 import javafx.scene.control.ContextMenu;
  48 import javafx.scene.control.MenuItem;
  49 import javafx.scene.control.OverrunStyle;
  50 import com.sun.javafx.scene.control.ContextMenuContent;
  51 import javafx.scene.input.KeyCombination;
  52 import javafx.scene.input.Mnemonic;
  53 import javafx.scene.paint.Color;
  54 import javafx.scene.text.Font;
  55 import javafx.scene.text.Text;
  56 import javafx.scene.text.TextBoundsType;
  57 import javafx.scene.text.HitInfo;
  58 
  59 import java.text.Bidi;

  60 import java.util.Locale;
  61 import java.util.function.Consumer;
  62 
  63 import static javafx.scene.control.OverrunStyle.CENTER_ELLIPSIS;
  64 import static javafx.scene.control.OverrunStyle.CENTER_WORD_ELLIPSIS;
  65 import static javafx.scene.control.OverrunStyle.CLIP;
  66 import static javafx.scene.control.OverrunStyle.ELLIPSIS;
  67 import static javafx.scene.control.OverrunStyle.LEADING_ELLIPSIS;
  68 import static javafx.scene.control.OverrunStyle.LEADING_WORD_ELLIPSIS;
  69 import static javafx.scene.control.OverrunStyle.WORD_ELLIPSIS;

  70 
  71 /**
  72  * BE REALLY CAREFUL WITH RESTORING OR RESETTING STATE OF helper NODE AS LEFTOVER
  73  * STATE CAUSES REALLY ODD NASTY BUGS!
  74  *
  75  * We expect all methods to set the Font property of helper but other than that
  76  * any properties set should be restored to defaults.
  77  */
  78 public class Utils {
  79 
  80     static final Text helper = new Text();
  81     static final double DEFAULT_WRAPPING_WIDTH = helper.getWrappingWidth();
  82     static final double DEFAULT_LINE_SPACING = helper.getLineSpacing();
  83     static final String DEFAULT_TEXT = helper.getText();
  84     static final TextBoundsType DEFAULT_BOUNDS_TYPE = helper.getBoundsType();
  85 
  86     /* Using TextLayout directly for simple text measurement.
  87      * Instead of restoring the TextLayout attributes to default values
  88      * (each renders the TextLayout unable to efficiently cache layout data).
  89      * It always sets all the attributes pertinent to calculation being performed.


 136         layout.setWrapWidth((float)wrappingWidth);
 137         layout.setLineSpacing((float)lineSpacing);
 138         if (boundsType == TextBoundsType.LOGICAL_VERTICAL_CENTER) {
 139             layout.setBoundsType(TextLayout.BOUNDS_CENTER);
 140         } else {
 141             layout.setBoundsType(0);
 142         }
 143         return layout.getBounds().getHeight();
 144     }
 145 
 146     public static int computeTruncationIndex(Font font, String text, double width) {
 147         helper.setText(text);
 148         helper.setFont(font);
 149         helper.setWrappingWidth(0);
 150         helper.setLineSpacing(0);
 151         // The -2 is a fudge to make sure the result more often matches
 152         // what we get from using computeTextWidth instead. It's not yet
 153         // clear what causes the small discrepancies.
 154         Bounds bounds = helper.getLayoutBounds();
 155         Point2D endPoint = new Point2D(width - 2, bounds.getMinY() + bounds.getHeight() / 2);
 156         final int index = helper.hitTest(endPoint).getCharIndex();
 157         // RESTORE STATE
 158         helper.setWrappingWidth(DEFAULT_WRAPPING_WIDTH);
 159         helper.setLineSpacing(DEFAULT_LINE_SPACING);
 160         helper.setText(DEFAULT_TEXT);
 161         return index;
 162     }
 163 
 164     public static String computeClippedText(Font font, String text, double width,
 165                                      OverrunStyle type, String ellipsisString) {
 166         if (font == null) {
 167             throw new IllegalArgumentException("Must specify a font");
 168         }
 169         OverrunStyle style = (type == null || type == CLIP) ? ELLIPSIS : type;
 170         final String ellipsis = (type == CLIP) ? "" : ellipsisString;
 171         // if the text is empty or null or no ellipsis, then it always fits
 172         if (text == null || "".equals(text)) {
 173             return text;
 174         }
 175         // if the string width is < the available width, then it fits and
 176         // doesn't need to be clipped.  We use a double point comparison


 392                             truncationStyle == CENTER_WORD_ELLIPSIS);
 393         boolean trailing = !(leading || center);
 394         boolean wordTrim = (truncationStyle == WORD_ELLIPSIS ||
 395                             truncationStyle == LEADING_WORD_ELLIPSIS ||
 396                             truncationStyle == CENTER_WORD_ELLIPSIS);
 397 
 398         String result = text;
 399         int len = (result != null) ? result.length() : 0;
 400         int centerLen = -1;
 401 
 402         Point2D centerPoint = null;
 403         if (center) {
 404             // Find index of character in the middle of the visual text area
 405             centerPoint = new Point2D((width - eWidth) / 2, height / 2 - helper.getBaselineOffset());
 406         }
 407 
 408         // Find index of character at the bottom left of the text area.
 409         // This should be the first character of a line that would be clipped.
 410         Point2D endPoint = new Point2D(0, height - helper.getBaselineOffset());
 411 
 412         int hit = helper.hitTest(endPoint).getCharIndex();
 413         if (hit >= len) {
 414             helper.setBoundsType(TextBoundsType.LOGICAL); // restore
 415             return text;
 416         }
 417         if (center) {
 418             hit = helper.hitTest(centerPoint).getCharIndex();
 419         }
 420 
 421         if (hit > 0 && hit < len) {
 422             // Step one, make a truncation estimate.
 423 
 424             if (center || trailing) {
 425                 int ind = hit;
 426                 if (center) {
 427                     // This is for the first part, i.e. beginning of text up to ellipsis.
 428                     if (wordTrim) {
 429                         int brInd = lastBreakCharIndex(text, ind + 1);
 430                         if (brInd >= 0) {
 431                             ind = brInd + 1;
 432                         } else {
 433                             brInd = firstBreakCharIndex(text, ind);
 434                             if (brInd >= 0) {
 435                                 ind = brInd + 1;
 436                             }
 437                         }
 438                     }


 459                         ind = brInd + 1;
 460                     } else {
 461                         brInd = firstBreakCharIndex(text, ind);
 462                         if (brInd >= 0) {
 463                             ind = brInd + 1;
 464                         }
 465                     }
 466                 }
 467                 if (center) {
 468                     // This is for the second part, i.e. from ellipsis to end of text.
 469                     result = result + text.substring(ind);
 470                 } else {
 471                     result = ellipsis + text.substring(ind);
 472                 }
 473             }
 474 
 475             // Step two, check if text still overflows after we added the ellipsis.
 476             // If so, remove one char or word at a time.
 477             while (true) {
 478                 helper.setText(result);
 479                 int hit2 = helper.hitTest(endPoint).getCharIndex();
 480                 if (center && hit2 < centerLen) {
 481                     // No room for text after ellipsis. Maybe there is a newline
 482                     // here, and the next line falls outside the view.
 483                     if (hit2 > 0 && result.charAt(hit2-1) == '\n') {
 484                         hit2--;
 485                     }
 486                     result = text.substring(0, hit2) + ellipsis;
 487                     break;
 488                 } else if (hit2 > 0 && hit2 < result.length()) {
 489                     if (leading) {
 490                         int ind = eLen + 1; // Past ellipsis and first char.
 491                         if (wordTrim) {
 492                             int brInd = firstBreakCharIndex(result, ind);
 493                             if (brInd >= 0) {
 494                                 ind = brInd + 1;
 495                             }
 496                         }
 497                         result = ellipsis + result.substring(ind);
 498                     } else if (center) {
 499                         int ind = centerLen + 1; // Past ellipsis and first char.


 736             case BOTTOM:
 737                return height - contentHeight;
 738             default:
 739                 return 0;
 740         }
 741     }
 742 
 743     /*
 744     ** Returns true if the platform is to use Two-Level-Focus.
 745     ** This is in the Util class to ease any changes in
 746     ** the criteria for enabling this feature.
 747     **
 748     ** TwoLevelFocus is needed on platforms that
 749     ** only support 5-button navigation (arrow keys and Select/OK).
 750     **
 751     */
 752     public static boolean isTwoLevelFocus() {
 753         return Platform.isSupported(ConditionalFeature.TWO_LEVEL_FOCUS);
 754     }
 755 




















 756 
 757     // useful method for linking things together when before a property is
 758     // necessarily set
 759     public static <T> void executeOnceWhenPropertyIsNonNull(ObservableValue<T> p, Consumer<T> consumer) {
 760         if (p == null) return;
 761 
 762         T value = p.getValue();
 763         if (value != null) {
 764             consumer.accept(value);
 765         } else {
 766             final InvalidationListener listener = new InvalidationListener() {
 767                 @Override public void invalidated(Observable observable) {
 768                     T value = p.getValue();
 769 
 770                     if (value != null) {
 771                         p.removeListener(this);
 772                         consumer.accept(value);
 773                     }
 774                 }
 775             };
< prev index next >