1 /*
   2  * Copyright (c) 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 package hello.dialog.dialogs;
  26 
  27 import java.util.ArrayList;
  28 import java.util.Collections;
  29 import java.util.HashSet;
  30 import java.util.List;
  31 import java.util.Set;
  32 import java.util.function.Predicate;
  33 
  34 import javafx.application.Platform;
  35 import javafx.beans.binding.DoubleBinding;
  36 import javafx.beans.value.ChangeListener;
  37 import javafx.beans.value.ObservableValue;
  38 import javafx.collections.FXCollections;
  39 import javafx.collections.transformation.FilteredList;
  40 import javafx.geometry.Pos;
  41 import javafx.scene.control.ButtonType;
  42 import javafx.scene.control.Dialog;
  43 import javafx.scene.control.DialogPane;
  44 import javafx.scene.control.Label;
  45 import javafx.scene.control.ListCell;
  46 import javafx.scene.control.ListView;
  47 import javafx.scene.image.Image;
  48 import javafx.scene.image.ImageView;
  49 import javafx.scene.layout.ColumnConstraints;
  50 import javafx.scene.layout.GridPane;
  51 import javafx.scene.layout.Priority;
  52 import javafx.scene.layout.RowConstraints;
  53 import javafx.scene.layout.StackPane;
  54 import javafx.scene.shape.Rectangle;
  55 import javafx.scene.text.Font;
  56 import javafx.scene.text.FontPosture;
  57 import javafx.scene.text.FontWeight;
  58 import javafx.scene.text.Text;
  59 import javafx.util.Callback;
  60 
  61 public class FontSelectorDialog extends Dialog<Font> {
  62     
  63     private FontPanel fontPanel;
  64     private Font defaultFont;
  65 
  66     public FontSelectorDialog(Font defaultFont) {
  67         fontPanel = new FontPanel();
  68         fontPanel.setFont(defaultFont);
  69         
  70         this.defaultFont = defaultFont;
  71         
  72         setResultConverter(dialogButton -> dialogButton == ButtonType.OK ? fontPanel.getFont() : null);
  73                 
  74         final DialogPane dialogPane = getDialogPane();
  75         
  76         setTitle("Select font");
  77         dialogPane.setHeaderText("Select font");
  78 
  79         // FIXME extract to CSS
  80         dialogPane.setGraphic(new ImageView(new Image(getClass().getResource("/hello/dialog/dialog-confirm.png").toExternalForm())));
  81         dialogPane.getButtonTypes().addAll(ButtonType.OK, ButtonType.CANCEL);
  82         dialogPane.setContent(fontPanel);
  83     }
  84     
  85 
  86     
  87     /**************************************************************************
  88      * 
  89      * Support classes
  90      * 
  91      **************************************************************************/
  92 
  93     /**
  94      * Font style as combination of font weight and font posture. 
  95      * Weight does not have to be there (represented by null)
  96      * Posture is required, null posture is converted to REGULAR
  97      */
  98     private static class FontStyle implements Comparable<FontStyle> {
  99 
 100         private FontPosture posture; 
 101         private FontWeight weight;
 102 
 103         public FontStyle( FontWeight weight, FontPosture posture ) {
 104             this.posture = posture == null? FontPosture.REGULAR: posture;
 105             this.weight = weight;
 106         }
 107 
 108         public FontStyle() {
 109             this( null, null);
 110         }
 111 
 112         public FontStyle(String styles) {
 113             this();
 114             String[] fontStyles = (styles == null? "": styles.trim().toUpperCase()).split(" ");
 115             for ( String style: fontStyles) {
 116                 FontWeight w = FontWeight.findByName(style);
 117                 if ( w != null ) {
 118                     weight = w;
 119                 } else {
 120                     FontPosture p = FontPosture.findByName(style);
 121                     if ( p != null ) posture = p;
 122                 }
 123             }
 124         }
 125 
 126         public FontStyle(Font font) {
 127             this( font.getStyle());
 128         }
 129 
 130         public FontPosture getPosture() {
 131             return posture;
 132         }
 133 
 134         public FontWeight getWeight() {
 135             return weight;
 136         }
 137 
 138 
 139         @Override public int hashCode() {
 140             final int prime = 31;
 141             int result = 1;
 142             result = prime * result + ((posture == null) ? 0 : posture.hashCode());
 143             result = prime * result + ((weight == null) ? 0 : weight.hashCode());
 144             return result;
 145         }
 146 
 147         @Override public boolean equals(Object that) {
 148             if (this == that)
 149                 return true;
 150             if (that == null)
 151                 return false;
 152             if (getClass() != that.getClass())
 153                 return false;
 154             FontStyle other = (FontStyle) that;
 155             if (posture != other.posture)
 156                 return false;
 157             if (weight != other.weight)
 158                 return false;
 159             return true;
 160         }
 161 
 162         private static String makePretty(Object o) {
 163             String s = o == null? "": o.toString();
 164             if ( !s.isEmpty()) { 
 165                 s = s.replace("_", " ");
 166                 s = s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase();
 167             }
 168             return s;
 169         }
 170 
 171         @Override public String toString() {
 172             return String.format("%s %s", makePretty(weight), makePretty(posture) ).trim();
 173         }
 174 
 175         private <T extends Enum<T>> int compareEnums( T e1, T e2) {
 176             if ( e1 == e2 ) return 0;
 177             if ( e1 == null ) return -1;
 178             if ( e2 == null ) return 1;
 179             return e1.compareTo(e2);
 180         }
 181 
 182         @Override public int compareTo(FontStyle fs) {
 183             int result = compareEnums(weight,fs.weight);
 184             return ( result != 0 )? result: compareEnums(posture,fs.posture);
 185         }
 186 
 187     }    
 188 
 189 
 190     private static class FontPanel extends GridPane {
 191         private static final double HGAP = 10;
 192         private static final double VGAP = 5;
 193 
 194         private static final Predicate<Object> MATCH_ALL = new Predicate<Object>() {
 195             @Override public boolean test(Object t) {
 196                 return true;
 197             }
 198         };
 199 
 200         private static final Double[] fontSizes = new Double[] {8d,9d,11d,12d,14d,16d,18d,20d,22d,24d,26d,28d,36d,48d,72d};
 201 
 202         private static List<FontStyle> getFontStyles( String fontFamily ) {
 203             Set<FontStyle> set = new HashSet<FontStyle>();
 204             for (String f : Font.getFontNames(fontFamily)) {
 205                 set.add(new FontStyle(f.replace(fontFamily, "")));
 206             }
 207 
 208             List<FontStyle> result =  new ArrayList<FontStyle>(set);
 209             Collections.sort(result);
 210             return result;
 211 
 212         }
 213 
 214 
 215         private final FilteredList<String> filteredFontList = new FilteredList<>(FXCollections.observableArrayList(Font.getFamilies()), MATCH_ALL);
 216         private final FilteredList<FontStyle> filteredStyleList = new FilteredList<>(FXCollections.<FontStyle>observableArrayList(), MATCH_ALL);
 217         private final FilteredList<Double> filteredSizeList = new FilteredList<>(FXCollections.observableArrayList(fontSizes), MATCH_ALL);
 218 
 219         private final ListView<String> fontListView = new ListView<String>(filteredFontList);
 220         private final ListView<FontStyle> styleListView = new ListView<FontStyle>(filteredStyleList);
 221         private final ListView<Double> sizeListView = new ListView<Double>(filteredSizeList);
 222         private final Text sample = new Text("Sample");
 223 
 224         public FontPanel() {
 225             setHgap(HGAP);
 226             setVgap(VGAP);
 227             setPrefSize(500, 300);
 228             setMinSize(500, 300);
 229 
 230             ColumnConstraints c0 = new ColumnConstraints();
 231             c0.setPercentWidth(60);
 232             ColumnConstraints c1 = new ColumnConstraints();
 233             c1.setPercentWidth(25);
 234             ColumnConstraints c2 = new ColumnConstraints();
 235             c2.setPercentWidth(15);
 236             getColumnConstraints().addAll(c0, c1, c2);
 237 
 238             RowConstraints r0 = new RowConstraints();
 239             r0.setVgrow(Priority.NEVER);
 240             RowConstraints r1 = new RowConstraints();
 241             r1.setVgrow(Priority.NEVER);
 242             RowConstraints r2 = new RowConstraints();
 243             r2.setFillHeight(true);
 244             r2.setVgrow(Priority.NEVER);
 245             RowConstraints r3 = new RowConstraints();
 246             r3.setPrefHeight(250);
 247             r3.setVgrow(Priority.NEVER);
 248             getRowConstraints().addAll(r0, r1, r2, r3);
 249 
 250             // layout hello.dialog
 251             add(new Label("Font"), 0, 0);
 252             //            fontSearch.setMinHeight(Control.USE_PREF_SIZE);
 253             //            add( fontSearch, 0, 1);
 254             add(fontListView, 0, 1);
 255             fontListView.setCellFactory(new Callback<ListView<String>, ListCell<String>>() {
 256                 @Override public ListCell<String> call(ListView<String> listview) {
 257                     return new ListCell<String>() {
 258                         @Override protected void updateItem(String family, boolean empty) {
 259                             super.updateItem(family, empty);
 260 
 261                             if (! empty) {
 262                                 setFont(Font.font(family));
 263                                 setText(family);
 264                             } else {
 265                                 setText(null);
 266                             }
 267                         }
 268                     };
 269                 }
 270             });
 271 
 272 
 273             ChangeListener<Object> sampleRefreshListener = new ChangeListener<Object>() {
 274                 @Override public void changed(ObservableValue<? extends Object> arg0, Object arg1, Object arg2) {
 275                     refreshSample();
 276                 }
 277             };
 278 
 279             fontListView.selectionModelProperty().get().selectedItemProperty().addListener( new ChangeListener<String>() {
 280 
 281                 @Override public void changed(ObservableValue<? extends String> arg0, String arg1, String arg2) {
 282                     String fontFamily = listSelection(fontListView);
 283                     styleListView.setItems(FXCollections.<FontStyle>observableArrayList(getFontStyles(fontFamily)));       
 284                     refreshSample();
 285                 }});
 286 
 287             add( new Label("Style"), 1, 0);
 288             //            postureSearch.setMinHeight(Control.USE_PREF_SIZE);
 289             //            add( postureSearch, 1, 1);
 290             add(styleListView, 1, 1);
 291             styleListView.selectionModelProperty().get().selectedItemProperty().addListener(sampleRefreshListener);
 292 
 293             add( new Label("Size"), 2, 0);
 294             //            sizeSearch.setMinHeight(Control.USE_PREF_SIZE);
 295             //            add( sizeSearch, 2, 1);
 296             add(sizeListView, 2, 1);
 297             sizeListView.selectionModelProperty().get().selectedItemProperty().addListener(sampleRefreshListener);
 298 
 299             final double height = 45;
 300             final DoubleBinding sampleWidth = new DoubleBinding() {
 301                 {
 302                     bind(fontListView.widthProperty(), styleListView.widthProperty(), sizeListView.widthProperty());
 303                 }
 304 
 305                 @Override protected double computeValue() {
 306                     return fontListView.getWidth() + styleListView.getWidth() + sizeListView.getWidth() + 3 * HGAP;
 307                 }
 308             };
 309             StackPane sampleStack = new StackPane(sample);
 310             sampleStack.setAlignment(Pos.CENTER_LEFT);
 311             sampleStack.setMinHeight(height);
 312             sampleStack.setPrefHeight(height);
 313             sampleStack.setMaxHeight(height);
 314             sampleStack.prefWidthProperty().bind(sampleWidth);
 315             Rectangle clip = new Rectangle(0, height);
 316             clip.widthProperty().bind(sampleWidth);
 317             sampleStack.setClip(clip);
 318             add(sampleStack, 0, 3, 1, 3);
 319         }
 320 
 321         public void setFont(final Font font) {
 322             final Font _font = font == null ? Font.getDefault() : font;
 323             if (_font != null) {
 324                 selectInList( fontListView,  _font.getFamily() );
 325                 selectInList( styleListView, new FontStyle(_font));
 326                 selectInList( sizeListView, _font.getSize() );
 327             }
 328         }
 329 
 330         public Font getFont() {
 331             try {
 332                 FontStyle style = listSelection(styleListView);
 333                 if ( style == null ) {
 334                     return Font.font(
 335                             listSelection(fontListView),
 336                             listSelection(sizeListView));
 337 
 338                 } else { 
 339                     return Font.font(
 340                             listSelection(fontListView),
 341                             style.getWeight(),
 342                             style.getPosture(),
 343                             listSelection(sizeListView));
 344                 }
 345 
 346             } catch( Throwable ex ) {
 347                 return null;
 348             }
 349         }
 350 
 351         private void refreshSample() {
 352             System.out.println(getFont());
 353             sample.setFont(getFont());
 354         }
 355 
 356         private <T> void selectInList( final ListView<T> listView, final T selection ) {
 357             Platform.runLater(new Runnable() {
 358                 @Override public void run() {
 359                     listView.scrollTo(selection);
 360                     listView.getSelectionModel().select(selection);
 361                 }
 362             });
 363         }
 364 
 365         private <T> T listSelection(final ListView<T> listView) {
 366             return listView.selectionModelProperty().get().getSelectedItem();
 367         }
 368     }  
 369 }