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 }