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 com.sun.javafx.scene.control.skin; 26 27 import java.util.ArrayList; 28 import java.util.Collections; 29 import java.util.HashMap; 30 import java.util.List; 31 import java.util.Map; 32 33 import javafx.collections.ListChangeListener; 34 import javafx.geometry.Pos; 35 import javafx.scene.Node; 36 import javafx.scene.control.ButtonBar; 37 import javafx.scene.control.ButtonBar.ButtonData; 38 import javafx.scene.layout.HBox; 39 import javafx.scene.layout.Pane; 40 import javafx.scene.layout.Priority; 41 import javafx.scene.layout.Region; 42 43 import com.sun.javafx.scene.control.behavior.BehaviorBase; 44 import com.sun.javafx.scene.control.behavior.KeyBinding; 45 import com.sun.javafx.scene.control.skin.BehaviorSkinBase; 46 47 public class ButtonBarSkin extends BehaviorSkinBase<ButtonBar, BehaviorBase<ButtonBar>> { 48 49 /************************************************************************** 50 * 51 * Static fields 52 * 53 **************************************************************************/ 54 55 private static final double GAP_SIZE = 10; 56 57 private static final String CATEGORIZED_TYPES = "LRHEYNXBIACO"; //$NON-NLS-1$ 58 59 // represented as a ButtonType 60 public static final String BUTTON_DATA_PROPERTY = "controlfx.button.type"; //$NON-NLS-1$ 61 62 // allows to exclude button from uniform resizing 63 public static final String BUTTON_SIZE_INDEPENDENCE = "controlfx.button.size.indepenence"; //$NON-NLS-1$ 64 65 // pick an arbitrary number 66 private static final double DO_NOT_CHANGE_SIZE = Double.MAX_VALUE - 100; 67 68 69 /************************************************************************** 70 * 71 * fields 72 * 73 **************************************************************************/ 74 75 private HBox layout; 76 77 78 79 /************************************************************************** 80 * 81 * Constructors 82 * 83 **************************************************************************/ 84 85 public ButtonBarSkin(final ButtonBar control) { 86 super(control, new BehaviorBase<>(control, Collections.<KeyBinding> emptyList())); 87 88 this.layout = new HBox(GAP_SIZE) { 89 @Override 90 protected void layoutChildren() { 91 // has to be called first or layout is not correct somtimes 92 resizeButtons(); 93 super.layoutChildren(); 94 } 95 }; 96 this.layout.setAlignment(Pos.CENTER); 97 this.layout.getStyleClass().add("container"); 98 getChildren().add(layout); 99 100 layoutButtons(); 101 102 control.getButtons().addListener(new ListChangeListener<Node>() { 103 @Override public void onChanged(ListChangeListener.Change<? extends Node> change) { 104 layoutButtons(); 105 } 106 }); 107 108 registerChangeListener(control.buttonOrderProperty(), "BUTTON_ORDER"); //$NON-NLS-1$ 109 registerChangeListener(control.buttonMinWidthProperty(), "BUTTON_MIN_WIDTH"); //$NON-NLS-1$ 110 } 111 112 113 /************************************************************************** 114 * 115 * Overriding public API 116 * 117 **************************************************************************/ 118 119 @Override protected void handleControlPropertyChanged(String p) { 120 super.handleControlPropertyChanged(p); 121 122 if ("BUTTON_ORDER".equals(p)) { //$NON-NLS-1$ 123 layoutButtons(); 124 } else if ("BUTTON_MIN_WIDTH".equals(p)) { //$NON-NLS-1$ 125 // layoutButtons(); 126 resizeButtons(); 127 } 128 } 129 130 131 132 /************************************************************************** 133 * 134 * Implementation 135 * 136 **************************************************************************/ 137 138 private void layoutButtons() { 139 final ButtonBar buttonBar = getSkinnable(); 140 final List<? extends Node> buttons = buttonBar.getButtons(); 141 final double buttonMinWidth = buttonBar.getButtonMinWidth(); 142 143 Map<String, List<Node>> buttonMap = buildButtonMap(buttons); 144 String buttonOrder = getSkinnable().getButtonOrder(); 145 146 if (buttonOrder == null || buttonOrder.isEmpty()) { 147 throw new IllegalStateException("ButtonBar buttonOrder string can not be null or empty"); //$NON-NLS-1$ 148 } 149 150 char[] buttonOrderArr = buttonOrder.toCharArray(); 151 layout.getChildren().clear(); 152 153 int buttonIndex = 0; // to determine edge cases 154 Spacer spacer = Spacer.NONE; 155 156 for (int i = 0; i < buttonOrderArr.length; i++) { 157 char type = buttonOrderArr[i]; 158 boolean edgeCase = buttonIndex <= 0 && buttonIndex >= buttons.size()-1; 159 boolean hasChildren = ! layout.getChildren().isEmpty(); 160 if (type == '+') { 161 spacer = spacer.replace(Spacer.DYNAMIC); 162 } else if (type == '_' && hasChildren) { 163 spacer = spacer.replace(Spacer.FIXED); 164 } else { 165 List<Node> buttonList = buttonMap.get(String.valueOf(type).toUpperCase()); 166 if (buttonList != null) { 167 168 spacer.add(layout,edgeCase); 169 170 for (Node btn: buttonList) { 171 sizeButton(btn, buttonMinWidth, DO_NOT_CHANGE_SIZE, Double.MAX_VALUE); 172 173 layout.getChildren().add(btn); 174 HBox.setHgrow(btn, Priority.NEVER); 175 buttonIndex++; 176 } 177 spacer = spacer.replace(Spacer.NONE); 178 } 179 } 180 } 181 182 } 183 184 // Button sizing. If buttonUniformSize is true button size = max(buttonMinSize, max(all button pref sizes)) 185 // otherwise button size = max(buttonBar.buttonMinSize, button pref size) 186 private void resizeButtons() { 187 final ButtonBar buttonBar = getSkinnable(); 188 double buttonMinWidth = buttonBar.getButtonMinWidth(); 189 final List<? extends Node> buttons = buttonBar.getButtons(); 190 191 // determine the widest button 192 double widest = buttonMinWidth; 193 for (Node button : buttons) { 194 if (!ButtonBar.isButtonUniformSize(button)) { 195 widest = Math.max(button.prefWidth(-1), widest); 196 } 197 } 198 199 // set the width of all buttons 200 for (Node button : buttons) { 201 if (!ButtonBar.isButtonUniformSize(button)) { 202 sizeButton(button, DO_NOT_CHANGE_SIZE, widest, DO_NOT_CHANGE_SIZE); 203 } 204 } 205 } 206 207 private void sizeButton(Node btn, double min, double pref, double max) { 208 if (btn instanceof Region) { 209 Region regionBtn = (Region)btn; 210 211 if (min != DO_NOT_CHANGE_SIZE) { 212 regionBtn.setMinWidth(min); 213 } 214 if (pref != DO_NOT_CHANGE_SIZE) { 215 regionBtn.setPrefWidth(pref); 216 } 217 if (max != DO_NOT_CHANGE_SIZE) 218 regionBtn.setMaxWidth(max); 219 } 220 } 221 222 private String getButtonType(Node btn) { 223 ButtonData buttonType = (ButtonData) btn.getProperties().get(BUTTON_DATA_PROPERTY); 224 225 if (buttonType == null) { 226 // just assume it is ButtonType.OTHER 227 buttonType = ButtonData.OTHER; 228 } 229 230 String typeCode = buttonType.getTypeCode(); 231 typeCode = typeCode.length() > 0? typeCode.substring(0,1): ""; //$NON-NLS-1$ 232 return CATEGORIZED_TYPES.contains(typeCode.toUpperCase())? typeCode : ButtonData.OTHER.getTypeCode(); 233 } 234 235 private Map<String, List<Node>> buildButtonMap( List<? extends Node> buttons ) { 236 Map<String, List<Node>> buttonMap = new HashMap<>(); 237 for (Node btn : buttons) { 238 if ( btn == null ) continue; 239 String type = getButtonType(btn); 240 List<Node> typedButtons = buttonMap.get(type); 241 if ( typedButtons == null ) { 242 typedButtons = new ArrayList<Node>(); 243 buttonMap.put(type, typedButtons); 244 } 245 typedButtons.add( btn ); 246 } 247 return buttonMap; 248 } 249 250 251 252 /************************************************************************** 253 * 254 * Support classes / enums 255 * 256 **************************************************************************/ 257 258 private enum Spacer { 259 FIXED { 260 @Override protected Node create(boolean edgeCase) { 261 if ( edgeCase ) return null; 262 Region spacer = new Region(); 263 spacer.setMinWidth(GAP_SIZE); 264 HBox.setHgrow(spacer, Priority.NEVER); 265 return spacer; 266 } 267 }, 268 DYNAMIC { 269 @Override protected Node create(boolean edgeCase) { 270 Region spacer = new Region(); 271 spacer.setMinWidth(edgeCase ? 0 : GAP_SIZE); 272 HBox.setHgrow(spacer, Priority.ALWAYS); 273 return spacer; 274 } 275 276 @Override public Spacer replace(Spacer spacer) { 277 return FIXED == spacer? this: spacer; 278 } 279 }, 280 NONE; 281 282 protected Node create(boolean edgeCase) { 283 return null; 284 } 285 286 public Spacer replace(Spacer spacer) { 287 return spacer; 288 } 289 290 public void add(Pane pane, boolean edgeCase) { 291 Node spacer = create(edgeCase); 292 if (spacer != null) { 293 pane.getChildren().add(spacer); 294 } 295 } 296 } 297 }