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