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 }