001package fi.jyu.mit.fxgui; 002 003import java.util.ArrayList; 004import java.util.List; 005import java.util.function.Consumer; 006 007import javafx.beans.property.BooleanProperty; 008import javafx.beans.property.SimpleBooleanProperty; 009import javafx.beans.property.SimpleStringProperty; 010import javafx.beans.property.StringProperty; 011import javafx.beans.value.ChangeListener; 012import javafx.beans.value.ObservableValue; 013import javafx.collections.FXCollections; 014import javafx.collections.ListChangeListener; 015import javafx.collections.ObservableList; 016import javafx.geometry.Insets; 017import javafx.scene.Node; 018import javafx.scene.layout.VBox; 019 020/** 021 * Base class for any single object that want to be added in multiples and then selected 022 * @author terop 023 * @version 13.1.2017 024 * @param <TO> Type of objects to be stored. 025 * @param <TC> Type of components to be used 026 */ 027public abstract class BaseBoxChooser<TO, TC extends Node> extends VBox implements MultipleChooser<TO> { 028 /** 029 * StringProperty for asking the example data from the scenebuilder 030 */ 031 protected StringProperty rivit = new SimpleStringProperty("\n"); 032 /** 033 * Should the components example data be reset at start 034 */ 035 protected BooleanProperty nollataanko = new SimpleBooleanProperty(false); 036 /** 037 * All of the components that were created 038 */ 039 protected ArrayList<TC> boxes = new ArrayList<TC>(); 040 /** 041 * The stored items inside this system 042 */ 043 protected ObservableList<StringAndObject<TO>> items = FXCollections.observableArrayList(); 044 /** 045 * Listeners for everyone who registered to any kind of changes for this object 046 */ 047 protected ArrayList<Consumer<TO>> lambdaListeners = new ArrayList<Consumer<TO>>(); 048 /** 049 * Stored listeners for easy removal 050 */ 051 protected ArrayList<ChangeListener<Boolean>> changeListenersForRemoval = new ArrayList<>(); 052 053 054 /** 055 * Initializes the control. 056 */ 057 public BaseBoxChooser() { 058 super(); 059 items.addListener(new ListChangeListener<StringAndObject<TO>>() { 060 061 @Override 062 public void onChanged(Change<? extends StringAndObject<TO>> c) { 063 //TODO: change the components according to these changes 064 while (c.next()) { 065 if (c.wasPermutated()) { 066 /* for (int i = c.getFrom(); i < c.getTo(); ++i) { 067 //permutate 068 }*/ 069 //UpdateComponents(items); 070 } else if (c.wasUpdated()) { 071 //update item 072 } else { 073 /* for (StringAndObject<TO> remitem : c.getRemoved()) { 074 075 } 076 for (StringAndObject<TO> additem : c.getAddedSubList()) { 077 //additem.add(Outer.this); 078 }*/ 079 //UpdateComponents(items); 080 } 081 } 082 083 } 084 }); 085 this.setSpacing(10.0); 086 this.setPadding(new Insets(10, 0, 10, 20)); 087 088 } 089 090 091 /** 092 * Adds a selection listener to the base component so when someone does selection this event is called 093 * @param event what to do when event happens 094 */ 095 @Override 096 public void addSelectionListener(Consumer<TO> event){ 097 this.lambdaListeners.add(event); 098 } 099 100 101 /** 102 * Adds the given object with given name 103 * @param name objektin kohdalla näkyvä teksti 104 * @param object tallennettava olio 105 */ 106 @Override 107 public void add(String name, TO object){ 108 add(new StringAndObject<TO>(name, object)); 109 } 110 111 112 /** 113 * Adds the StringAndObject to the system and creates components necessary 114 * @param so object to add 115 */ 116 public void add(StringAndObject<TO> so) { 117 getItems().add(so); 118 119 //Create a new component and combine all the listeners to it 120 final TC box = addCorrectComponent(so.getName(), so.getObject()); //must be effectively final for the changed 121 122 ChangeListener<Boolean> listener = new ChangeListener<Boolean>() { 123 @Override 124 public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) { 125 126 selectedChanged(); 127 } 128 }; 129 changeListenersForRemoval.add(listener); 130 131 addChangeListener(box, listener); 132 this.getChildren().add(box); 133 boxes.add(box); 134 } 135 136 137 /** 138 * This calls the listeners in the class! Probably not useful elsewhere 139 * We scope this into the class for easier access to the baseClass instead of every this pointing to the ChangeListener-anonymous class 140 * The visibility of this should be package. NOT public or private. Java seems to do this by not having a keyword at all! We don't what this definitely as public but nor do we want to create synthetic accessor(which might be preferred over public still) 141 * @param box the box that was selected 142 */ 143 void selectedChanged() { 144 for (Consumer<TO> checkBoxListener : lambdaListeners) { 145 TO selected = getSelectedObject(); 146 checkBoxListener.accept(selected); 147 } 148 } 149 150 151 /** 152 * Adds to the component a listener when its state is changed. Base cannot know which property is to be cared about 153 * @param box componetn to use 154 * @param listener function listen 155 */ 156 protected abstract void addChangeListener(TC box, ChangeListener<Boolean> listener); 157 158 159 /** 160 * Will give the created new component to this class so no reflection is needed. 161 * Since the class is abstract anyways 162 * @param name ??? 163 * @param object ??? 164 * @return ?? 165 */ 166 protected abstract TC addCorrectComponent(String name, TO object); 167 168 169 /** 170 * Returns the inner ObservableList. Will break functionality. You have been warned 171 * @return the inner observableList 172 */ 173 public ObservableList<StringAndObject<TO>> getItems() { 174 return items; 175 } 176 177 178 /** 179 * Updates the components to match given list 180 * @param list the objects to make the gui look like 181 */ 182 public void UpdateComponents(ObservableList<StringAndObject<TO>> list){ 183 clear(); 184 for (StringAndObject<TO> stringAndObject : list) { 185 add(stringAndObject); 186 } 187 } 188 189 190 /** 191 * Addes the given object to the list 192 * @param object tallennettava olio 193 */ 194 @Override 195 public void add(TO object){ 196 add(null, object); 197 } 198 199 200 /** 201 * Adds example text to the list 202 * @param text teksti joka näytetään 203 */ 204 @Override 205 public void addExample(String text){ 206 add(text, null); 207 } 208 209 210 /** 211 * Clears the ObservableList that holds the objects 212 */ 213 @Override 214 public void clear(){ 215 getItems().clear(); 216 ObservableList<Node> children = this.getChildren(); //Only removes the comboboxes added with the inner workings 217 for (TC node : boxes) { 218 children.remove(node); 219 220 removeListener(node, changeListenersForRemoval); 221 /*for (ChangeListener<Boolean> listener : changeListenersForRemoval) {//Extra work to remove all of the listeners 222 //node.selectedProperty().removeListener(listener); 223 }*/ 224 } 225 changeListenersForRemoval.clear(); 226 boxes.clear(); 227 } 228 229 230 /** 231 * Removes all the listeners from the node from the property they may be linked to 232 * @param node ??? 233 * @param list ??? 234 */ 235 protected abstract void removeListener(TC node, ArrayList<ChangeListener<Boolean>> list); 236 237 238 private void setRows(String[] strings) { 239 ObservableList<StringAndObject<TO>> objects = getItems(); 240 objects.clear(); 241 for (String string : strings) { 242 add(string, null); 243 } 244 } 245 246 247 /** 248 * Sets the rows. 249 * 250 * @param jono 251 * A multiline string, each line representing a single row. 252 */ 253 @Override 254 public void setRivit(String jono) { 255 this.rivit.set(jono); 256 String[] strings = jono.split("\n"); // erotinMerkki.get()); 257 setRows(strings); 258 } 259 260 261 /** 262 * Sets the rows. 263 * 264 * @param rivit 265 * A string arrays, each line representing a single row. 266 */ 267 @Override 268 public void setRivit(String[] rivit) { 269 StringBuilder sb = new StringBuilder(); 270 String sep = ""; 271 for (String s: rivit) { sb.append(sep).append(s); sep = "\n"; } 272 this.rivit.set(sb.toString()); 273 setRows(rivit); 274 } 275 276 277 /** 278 * Palauttaa valitun olion tai null 279 * @return valittu olio tai null 280 */ 281 @Override 282 public TO getSelectedObject() { 283 for (int i = 0; i < boxes.size(); i++) { 284 if(isComponentSelected(boxes.get(i))) 285 { 286 return items.get(i).getObject(); 287 } 288 } 289 return null; 290 } 291 292 293 /** 294 * Returns the first selected text component 295 */ 296 @Override 297 public String getSelectedText() { 298 for (int i = 0; i < boxes.size(); i++) { 299 if(isComponentSelected(boxes.get(i))) 300 { 301 return removeMnemonic(items.get(i).getName()); 302 } 303 } 304 return null; 305 } 306 307 308 /** 309 * Returns the first selected index 310 */ 311 @Override 312 public int getSelectedIndex() { 313 for (int i = 0; i < boxes.size(); i++) { 314 if(isComponentSelected(boxes.get(i))) 315 { 316 return i; 317 } 318 } 319 return -1; 320 } 321 322 323 @Override 324 public int setSelectedIndex(int index) { 325 int oldIndex = getSelectedIndex(); 326 if ( index < 0 || boxes.size() <= index ) 327 return oldIndex; 328 setComponentSelected(boxes.get(index), true); 329 return oldIndex; 330 } 331 332 333 /** 334 * Returns if the component should be selected by the baseboxchooser for getItems 335 * @param component the generic component that we want to know if is selected 336 * @return if the component is selected or not 337 */ 338 protected abstract boolean isComponentSelected(TC component); 339 340 /** 341 * Sets component selected 342 * @param component the generic component that we want to know if is selected 343 * @param value value to set for component 344 * @return if the component was selected or not 345 */ 346 protected abstract boolean setComponentSelected(TC component, boolean value); 347 348 349 @Override 350 public List<TO> getSelectedObjects() { 351 List<TO> objects = new ArrayList<TO>(); 352 for (int i = 0; i < boxes.size(); i++) { 353 if(isComponentSelected(boxes.get(i))){ 354 objects.add(items.get(i).getObject()); 355 } 356 357 } 358 return objects; 359 } 360 361 362 @Override 363 public List<String> getSelectedTexts() { 364 List<String> objects = new ArrayList<String>(); 365 for (int i = 0; i < boxes.size(); i++) { 366 if(isComponentSelected(boxes.get(i))){ 367 objects.add(removeMnemonic(items.get(i).getName())); 368 } 369 370 } 371 return objects; 372 } 373 374 375 @Override 376 public List<Integer> getSelectedIndices() { 377 List<Integer> objects = new ArrayList<Integer>(); 378 for (int i = 0; i < boxes.size(); i++) { 379 if(isComponentSelected(boxes.get(i))){ 380 objects.add(i); 381 } 382 383 } 384 return objects; 385 } 386 387 388 /** 389 * Returns the contents of the ListChooser as a multiline string. 390 * 391 * @return The contents of the ListChooser as a multiline string. 392 */ 393 public String getRivit() { 394 return rivit.get(); 395 } 396 397 398 /** 399 * Sets the value if the component should be reset after the original example data 400 * @param reset should it be reset 401 */ 402 public void setNollataanko(boolean reset){ 403 nollataanko.set(reset); 404 } 405 406 407 /** 408 * Returns if the component was selected to be cleared 409 * @return should it be cleared 410 */ 411 public boolean getNollataanko(){ 412 return nollataanko.get(); 413 } 414 415 416 /** 417 * Poistetaan alleviiva 418 * @param item mistä poistetaan 419 * @return ilman 1. alleviivaa 420 */ 421 @Override 422 public String removeMnemonic(String item) { 423 return item.replaceFirst("_", ""); 424 } 425 426}