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}