View Javadoc
1   /*
2    * Metis: Java Data Framework
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.metis.ui;
18  
19  import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventManager;
21  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar;
22  import io.github.tonywasher.joceanus.oceanus.event.OceanusEventRegistrar.OceanusEventProvider;
23  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceEvent;
24  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceResource;
25  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet;
26  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisBooleanPreference;
27  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisDatePreference;
28  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisEnumPreference;
29  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisIntegerPreference;
30  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisPreferenceId;
31  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisPreferenceItem;
32  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceSet.MetisStringPreference;
33  import io.github.tonywasher.joceanus.metis.preference.MetisPreferenceType;
34  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIAlignment;
35  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIComponent;
36  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIConstant;
37  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIEvent;
38  import io.github.tonywasher.joceanus.tethys.api.base.TethysUIGenericWrapper;
39  import io.github.tonywasher.joceanus.tethys.api.button.TethysUIButton;
40  import io.github.tonywasher.joceanus.tethys.api.control.TethysUICheckBox;
41  import io.github.tonywasher.joceanus.tethys.api.control.TethysUIControlFactory;
42  import io.github.tonywasher.joceanus.tethys.api.control.TethysUILabel;
43  import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIDirectorySelector;
44  import io.github.tonywasher.joceanus.tethys.api.dialog.TethysUIFileSelector;
45  import io.github.tonywasher.joceanus.tethys.api.factory.TethysUIFactory;
46  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIColorButtonField;
47  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIDateButtonField;
48  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIIntegerEditField;
49  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIScrollButtonField;
50  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIDataEditField.TethysUIStringEditField;
51  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIFieldAttribute;
52  import io.github.tonywasher.joceanus.tethys.api.field.TethysUIFieldType;
53  import io.github.tonywasher.joceanus.tethys.api.menu.TethysUIScrollMenu;
54  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIBorderPaneManager;
55  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIFlowPaneManager;
56  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIGridPaneManager;
57  import io.github.tonywasher.joceanus.tethys.api.pane.TethysUIPaneFactory;
58  
59  import java.io.File;
60  import java.util.ArrayList;
61  import java.util.List;
62  import java.util.function.Predicate;
63  
64  /**
65   * Panel for editing a preference Set.
66   */
67  public class MetisPreferenceSetView
68          implements OceanusEventProvider<MetisPreferenceEvent>, TethysUIComponent {
69      /**
70       * Colon String.
71       */
72      protected static final String STR_COLON = TethysUIConstant.STR_COLON;
73  
74      /**
75       * Text for Preferences Title.
76       */
77      private static final String NLS_PREFERENCES = MetisPreferenceResource.UI_TITLE_PREFERENCES.getValue();
78  
79      /**
80       * Text for Options Title.
81       */
82      private static final String NLS_OPTIONS = MetisPreferenceResource.UI_TITLE_OPTIONS.getValue();
83  
84      /**
85       * The Event Manager.
86       */
87      private final OceanusEventManager<MetisPreferenceEvent> theEventManager;
88  
89      /**
90       * The PreferenceSet for this panel.
91       */
92      private final MetisPreferenceSet thePreferences;
93  
94      /**
95       * The Border Pane.
96       */
97      private final TethysUIBorderPaneManager thePane;
98  
99      /**
100      * The Grid Pane.
101      */
102     private final TethysUIGridPaneManager theGrid;
103 
104     /**
105      * The GUI factory.
106      */
107     private final TethysUIFactory<?> theGuiFactory;
108 
109     /**
110      * The Element list.
111      */
112     private final List<PreferenceElement> theElements;
113 
114     /**
115      * The Options Pane.
116      */
117     private TethysUIFlowPaneManager theOptions;
118 
119     /**
120      * Constructor.
121      *
122      * @param pFactory       the GUI factory
123      * @param pPreferenceSet the preference set
124      */
125     protected MetisPreferenceSetView(final TethysUIFactory<?> pFactory,
126                                      final MetisPreferenceSet pPreferenceSet) {
127         /* Store parameters */
128         theGuiFactory = pFactory;
129         thePreferences = pPreferenceSet;
130 
131         /* Create the event manager */
132         theEventManager = new OceanusEventManager<>();
133 
134         /* Create the list */
135         theElements = new ArrayList<>();
136 
137         /* Create the Grid Pane */
138         final TethysUIPaneFactory myPanes = theGuiFactory.paneFactory();
139         theGrid = myPanes.newGridPane();
140 
141         /* Loop through the preferences */
142         for (MetisPreferenceItem myPref : thePreferences.getPreferences()) {
143             /* Create element and add it to the list */
144             final PreferenceElement myElement = allocatePreferenceElement(myPref);
145             if (myElement != null) {
146                 theElements.add(myElement);
147             }
148         }
149 
150         /* Create the border pane */
151         thePane = myPanes.newBorderPane();
152         thePane.setBorderTitle(NLS_PREFERENCES);
153         thePane.setCentre(theGrid);
154 
155         /* If we have an options pane */
156         if (theOptions != null) {
157             /* Add to the end */
158             thePane.setSouth(theOptions);
159         }
160 
161         /* initialise the fields */
162         updateFields();
163     }
164 
165     /**
166      * Obtain the GUI factory.
167      *
168      * @return the factory
169      */
170     protected TethysUIFactory<?> getFactory() {
171         return theGuiFactory;
172     }
173 
174     /**
175      * Obtain the grid.
176      *
177      * @return the grid
178      */
179     protected TethysUIGridPaneManager getGrid() {
180         return theGrid;
181     }
182 
183     @Override
184     public String toString() {
185         return thePreferences.getName();
186     }
187 
188     @Override
189     public TethysUIComponent getUnderlying() {
190         return thePane;
191     }
192 
193     @Override
194     public OceanusEventRegistrar<MetisPreferenceEvent> getEventRegistrar() {
195         return theEventManager.getEventRegistrar();
196     }
197 
198     /**
199      * Does the Preference Set have changes.
200      *
201      * @return does the set have changes
202      */
203     boolean hasChanges() {
204         return thePreferences.hasChanges();
205     }
206 
207     /**
208      * Reset changes.
209      */
210     void resetChanges() {
211         /* Reset changes and clear flag */
212         thePreferences.resetChanges();
213 
214         /* Update the fields */
215         updateFields();
216     }
217 
218     /**
219      * Store changes.
220      *
221      * @throws OceanusException on error
222      */
223     void storeChanges() throws OceanusException {
224         /* Reset changes and clear flag */
225         thePreferences.storeChanges();
226 
227         /* Update the fields */
228         updateFields();
229     }
230 
231     /**
232      * Update fields.
233      */
234     private void updateFields() {
235         /* Update the viewer entry */
236         thePreferences.updateViewerEntry();
237 
238         /* Loop through the fields */
239         for (PreferenceElement myItem : theElements) {
240             /* Update the field */
241             myItem.updateField();
242         }
243     }
244 
245     /**
246      * Notify changes.
247      */
248     protected void notifyChanges() {
249         /* AutoCorrect the preferences */
250         thePreferences.autoCorrectPreferences();
251 
252         /* Update the fields */
253         updateFields();
254 
255         /* Notify listeners */
256         theEventManager.fireEvent(MetisPreferenceEvent.PREFCHANGED);
257     }
258 
259     /**
260      * Allocate element.
261      *
262      * @param pItem the preference item
263      * @return the element
264      */
265     protected PreferenceElement allocatePreferenceElement(final MetisPreferenceItem pItem) {
266         if (pItem instanceof MetisEnumPreference<?> myEnum) {
267             return new EnumPreferenceElement<>(myEnum);
268         } else if (pItem instanceof MetisIntegerPreference myInt) {
269             return new IntegerPreferenceElement(myInt);
270         } else if (pItem instanceof MetisDatePreference myDate) {
271             return new DatePreferenceElement(myDate);
272         } else if (pItem instanceof MetisBooleanPreference myBool) {
273             return new BooleanPreferenceElement(myBool);
274         } else if (pItem instanceof MetisStringPreference myString) {
275             final MetisPreferenceId myId = myString.getType();
276             if (myId.equals(MetisPreferenceType.DIRECTORY)) {
277                 return new DirectoryPreferenceElement(myString);
278             } else if (myId.equals(MetisPreferenceType.FILE)) {
279                 return new FilePreferenceElement(myString);
280             } else if (myId.equals(MetisPreferenceType.COLOR)) {
281                 return new ColorPreferenceElement(myString);
282             } else {
283                 return new StringPreferenceElement(myString);
284             }
285         } else {
286             throw new IllegalArgumentException("Bad Preference Type: " + pItem.getType());
287         }
288     }
289 
290     /**
291      * Determine Focus.
292      */
293     protected void determineFocus() {
294         thePreferences.setFocus();
295     }
296 
297     /**
298      * PreferenceElement.
299      */
300     @FunctionalInterface
301     protected interface PreferenceElement {
302         /**
303          * Update the field.
304          */
305         void updateField();
306     }
307 
308     /**
309      * String preference element.
310      */
311     private final class StringPreferenceElement
312             implements PreferenceElement {
313         /**
314          * The Preference item.
315          */
316         private final MetisStringPreference theItem;
317 
318         /**
319          * The Field item.
320          */
321         private final TethysUIStringEditField theField;
322 
323         /**
324          * Constructor.
325          *
326          * @param pItem the item
327          */
328         StringPreferenceElement(final MetisStringPreference pItem) {
329             /* Store parameters */
330             theItem = pItem;
331             theField = theGuiFactory.fieldFactory().newStringField();
332             theField.setEditable(true);
333 
334             /* Create the label */
335             final TethysUILabel myLabel = theGuiFactory.controlFactory().newLabel(pItem.getDisplay() + STR_COLON);
336             myLabel.setAlignment(TethysUIAlignment.EAST);
337 
338             /* Add to the Grid Pane */
339             theGrid.addCell(myLabel);
340             theGrid.setCellAlignment(myLabel, TethysUIAlignment.EAST);
341             theGrid.addCell(theField);
342             theGrid.setCellColumnSpan(theField, 2);
343             theGrid.allowCellGrowth(theField);
344             theGrid.newRow();
345 
346             /* Create listener */
347             theField.getEventRegistrar().addEventListener(e -> {
348                 pItem.setValue(theField.getValue());
349                 notifyChanges();
350             });
351         }
352 
353         @Override
354         public void updateField() {
355             /* Update the field */
356             theField.setValue(theItem.getValue());
357 
358             /* Set changed indication */
359             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
360             theField.adjustField();
361 
362             /* Handle hidden state */
363             theField.setEnabled(!theItem.isHidden());
364         }
365     }
366 
367     /**
368      * Integer preference element.
369      */
370     private final class IntegerPreferenceElement
371             implements PreferenceElement {
372         /**
373          * The Preference item.
374          */
375         private final MetisIntegerPreference theItem;
376 
377         /**
378          * The Field item.
379          */
380         private final TethysUIIntegerEditField theField;
381 
382         /**
383          * The Label item.
384          */
385         private final TethysUILabel theRangeLabel;
386 
387         /**
388          * Constructor.
389          *
390          * @param pItem the item
391          */
392         IntegerPreferenceElement(final MetisIntegerPreference pItem) {
393             /* Store parameters */
394             theItem = pItem;
395             theField = theGuiFactory.fieldFactory().newIntegerField();
396             theField.setEditable(true);
397             theField.setPreferredWidth(TethysUIFieldType.INTEGER.getDefaultWidth());
398 
399             /* Create the label */
400             final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
401             final TethysUILabel myLabel = myControls.newLabel(pItem.getDisplay() + STR_COLON);
402             myLabel.setAlignment(TethysUIAlignment.EAST);
403 
404             /* Create the range label */
405             theRangeLabel = myControls.newLabel();
406 
407             /* Add to the Grid Pane */
408             theGrid.addCell(myLabel);
409             theGrid.setCellAlignment(myLabel, TethysUIAlignment.EAST);
410             theGrid.addCell(theField);
411             theGrid.addCell(theRangeLabel);
412             theGrid.allowCellGrowth(theRangeLabel);
413             theGrid.newRow();
414 
415             /* Create listener */
416             theField.getEventRegistrar().addEventListener(e -> {
417                 pItem.setValue(theField.getValue());
418                 notifyChanges();
419             });
420         }
421 
422         @Override
423         public void updateField() {
424             /* Update the field */
425             theField.setValue(theItem.getValue());
426 
427             /* handle the range */
428             handleRange();
429 
430             /* Set changed indication */
431             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
432             theField.adjustField();
433 
434             /* Handle hidden state */
435             theField.setEnabled(!theItem.isHidden());
436         }
437 
438         /**
439          * handle range.
440          */
441         private void handleRange() {
442             /* Access the minimum/maximum */
443             final Integer myMin = theItem.getMinimum();
444             final Integer myMax = theItem.getMaximum();
445             final boolean hasMin = myMin != null;
446             final boolean hasMax = myMax != null;
447 
448             /* Set the valid range */
449             theField.setValidator(p -> (hasMin && p < myMin) || (hasMax && p > myMax)
450                     ? MetisPreferenceResource.UI_RANGE_ERROR.getValue()
451                     : null);
452             theRangeLabel.setText(null);
453 
454             /* If we have a minimum or maximum */
455             if (hasMin || hasMax) {
456                 /* Format the details */
457                 final StringBuilder myBuilder = new StringBuilder();
458 
459                 /* Handle minimum */
460                 if (hasMin) {
461                     myBuilder.append(MetisPreferenceResource.UI_RANGE_MIN.getValue());
462                     myBuilder.append(' ');
463                     myBuilder.append(myMin);
464                     if (hasMax) {
465                         myBuilder.append(", ");
466                     }
467                 }
468 
469                 /* Handle maximum */
470                 if (hasMax) {
471                     myBuilder.append(MetisPreferenceResource.UI_RANGE_MAX.getValue());
472                     myBuilder.append(' ');
473                     myBuilder.append(myMax);
474                 }
475 
476                 /* Format the field */
477                 theRangeLabel.setText(myBuilder.toString());
478             }
479         }
480     }
481 
482     /**
483      * Boolean preference element.
484      */
485     private final class BooleanPreferenceElement
486             implements PreferenceElement {
487         /**
488          * The Preference item.
489          */
490         private final MetisBooleanPreference theItem;
491 
492         /**
493          * The CheckBox item.
494          */
495         private final TethysUICheckBox theCheckBox;
496 
497         /**
498          * Constructor.
499          *
500          * @param pItem the item
501          */
502         BooleanPreferenceElement(final MetisBooleanPreference pItem) {
503             /* Store parameters */
504             theItem = pItem;
505             theCheckBox = theGuiFactory.controlFactory().newCheckBox(pItem.getDisplay());
506 
507             /* Ensure the options pane */
508             ensureOptionsPane();
509 
510             /* Add to the Options Pane */
511             theOptions.addNode(theCheckBox);
512 
513             /* Create listener */
514             theCheckBox.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> {
515                 pItem.setValue(theCheckBox.isSelected());
516                 notifyChanges();
517             });
518         }
519 
520         @Override
521         public void updateField() {
522             /* Update the field */
523             theCheckBox.setSelected(theItem.getValue());
524 
525             /* Set changed indication */
526             theCheckBox.setChanged(theItem.isChanged());
527 
528             /* Handle hidden state */
529             theCheckBox.setEnabled(!theItem.isHidden());
530         }
531 
532         /**
533          * ensure the options pane.
534          */
535         private void ensureOptionsPane() {
536             /* If we haven't created the options yet */
537             if (theOptions == null) {
538                 /* create the options panel */
539                 theOptions = theGuiFactory.paneFactory().newFlowPane();
540                 theOptions.setBorderTitle(NLS_OPTIONS);
541             }
542         }
543     }
544 
545     /**
546      * Date preference element.
547      */
548     private final class DatePreferenceElement
549             implements PreferenceElement {
550         /**
551          * The Preference item.
552          */
553         private final MetisDatePreference theItem;
554 
555         /**
556          * The Field item.
557          */
558         private final TethysUIDateButtonField theField;
559 
560         /**
561          * Constructor.
562          *
563          * @param pItem the item
564          */
565         DatePreferenceElement(final MetisDatePreference pItem) {
566             /* Store parameters */
567             theItem = pItem;
568             theField = theGuiFactory.fieldFactory().newDateField();
569             theField.setEditable(true);
570 
571             /* Create the label */
572             final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
573             final TethysUILabel myLabel = myControls.newLabel(pItem.getDisplay() + STR_COLON);
574             myLabel.setAlignment(TethysUIAlignment.EAST);
575 
576             /* Create the place-holder */
577             final TethysUILabel myStub = myControls.newLabel();
578 
579             /* Add to the Grid Pane */
580             theGrid.addCell(myLabel);
581             theGrid.setCellAlignment(myLabel, TethysUIAlignment.EAST);
582             theGrid.addCell(theField);
583             theGrid.addCell(myStub);
584             theGrid.allowCellGrowth(myStub);
585             theGrid.newRow();
586 
587             /* Create listener */
588             theField.getEventRegistrar().addEventListener(TethysUIEvent.NEWVALUE, e -> {
589                 pItem.setValue(theField.getValue());
590                 notifyChanges();
591             });
592         }
593 
594         @Override
595         public void updateField() {
596             /* Update the field */
597             theField.setValue(theItem.getValue());
598 
599             /* Set changed indication */
600             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
601             theField.adjustField();
602 
603             /* Handle hidden state */
604             theField.setEnabled(!theItem.isHidden());
605         }
606     }
607 
608     /**
609      * Enum preference element.
610      *
611      * @param <E> the enum type
612      */
613     private final class EnumPreferenceElement<E extends Enum<E>>
614             implements PreferenceElement {
615         /**
616          * The Preference item.
617          */
618         private final MetisEnumPreference<E> theItem;
619 
620         /**
621          * The Field item.
622          */
623         private final TethysUIScrollButtonField<TethysUIGenericWrapper> theField;
624 
625         /**
626          * Constructor.
627          *
628          * @param pItem the item
629          */
630         @SuppressWarnings("unchecked")
631         EnumPreferenceElement(final MetisEnumPreference<E> pItem) {
632             /* Store parameters */
633             theItem = pItem;
634             theField = theGuiFactory.fieldFactory().newScrollField(TethysUIGenericWrapper.class);
635             theField.setEditable(true);
636 
637             /* Create the label */
638             final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
639             final TethysUILabel myLabel = myControls.newLabel(pItem.getDisplay() + STR_COLON);
640             myLabel.setAlignment(TethysUIAlignment.EAST);
641 
642             /* Create the place-holder */
643             final TethysUILabel myStub = myControls.newLabel();
644 
645             /* Add to the Grid Pane */
646             theGrid.addCell(myLabel);
647             theGrid.setCellAlignment(myLabel, TethysUIAlignment.EAST);
648             theGrid.addCell(theField);
649             theGrid.addCell(myStub);
650             theGrid.allowCellGrowth(myStub);
651             theGrid.newRow();
652 
653             /* Create listeners */
654             theField.setMenuConfigurator(this::buildMenu);
655             final OceanusEventRegistrar<TethysUIEvent> myRegistrar = theField.getEventRegistrar();
656             myRegistrar.addEventListener(TethysUIEvent.NEWVALUE, e -> {
657                 pItem.setValue((E) theField.getValue().getData());
658                 notifyChanges();
659             });
660         }
661 
662         /**
663          * Build menu.
664          *
665          * @param pMenu the menu
666          */
667         private void buildMenu(final TethysUIScrollMenu<TethysUIGenericWrapper> pMenu) {
668             /* reset the menu */
669             pMenu.removeAllItems();
670 
671             /* Obtain the filter */
672             final Predicate<E> myFilter = theItem.getFilter();
673 
674             /* For all values */
675             for (E myEnum : theItem.getValues()) {
676                 /* If the element is not filtered */
677                 if (myFilter.test(myEnum)) {
678                     /* Create a new MenuItem and add it to the popUp */
679                     pMenu.addItem(new TethysUIGenericWrapper(myEnum));
680                 }
681             }
682         }
683 
684         @Override
685         public void updateField() {
686             /* Update the field */
687             theField.setValue(new TethysUIGenericWrapper(theItem.getValue()));
688 
689             /* Set changed indication */
690             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
691             theField.adjustField();
692 
693             /* Handle hidden state */
694             theField.setEnabled(!theItem.isHidden());
695         }
696     }
697 
698     /**
699      * Colour preference element.
700      */
701     private final class ColorPreferenceElement
702             implements PreferenceElement {
703         /**
704          * The Preference item.
705          */
706         private final MetisStringPreference theItem;
707 
708         /**
709          * The Field item.
710          */
711         private final TethysUIColorButtonField theField;
712 
713         /**
714          * Constructor.
715          *
716          * @param pItem the item
717          */
718         ColorPreferenceElement(final MetisStringPreference pItem) {
719             /* Store parameters */
720             theItem = pItem;
721             theField = theGuiFactory.fieldFactory().newColorField();
722             theField.setEditable(true);
723 
724             /* Create the label */
725             final TethysUIControlFactory myControls = theGuiFactory.controlFactory();
726             final TethysUILabel myLabel = myControls.newLabel(pItem.getDisplay() + STR_COLON);
727             myLabel.setAlignment(TethysUIAlignment.EAST);
728 
729             /* Create the place-holder */
730             final TethysUILabel myStub = myControls.newLabel();
731 
732             /* Add to the Grid Pane */
733             theGrid.addCell(myLabel);
734             theGrid.setCellAlignment(myLabel, TethysUIAlignment.EAST);
735             theGrid.addCell(theField);
736             theGrid.addCell(myStub);
737             theGrid.allowCellGrowth(myStub);
738             theGrid.newRow();
739 
740             /* Create listener */
741             theField.getEventRegistrar().addEventListener(e -> {
742                 pItem.setValue(theField.getValue());
743                 notifyChanges();
744             });
745         }
746 
747         @Override
748         public void updateField() {
749             /* Update the field */
750             theField.setValue(theItem.getValue());
751 
752             /* Set changed indication */
753             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
754             theField.adjustField();
755 
756             /* Handle hidden state */
757             theField.setEnabled(!theItem.isHidden());
758         }
759     }
760 
761     /**
762      * File preference element.
763      */
764     private final class FilePreferenceElement
765             implements PreferenceElement {
766         /**
767          * The Preference item.
768          */
769         private final MetisStringPreference theItem;
770 
771         /**
772          * The Field item.
773          */
774         private final TethysUIStringEditField theField;
775 
776         /**
777          * The Button.
778          */
779         private final TethysUIButton theButton;
780 
781         /**
782          * The File Selector.
783          */
784         private TethysUIFileSelector theSelector;
785 
786         /**
787          * Constructor.
788          *
789          * @param pItem the item
790          */
791         FilePreferenceElement(final MetisStringPreference pItem) {
792             /* Store parameters */
793             theItem = pItem;
794             theField = theGuiFactory.fieldFactory().newStringField();
795             theField.setEditable(true);
796 
797             /* Create the button */
798             theButton = theGuiFactory.buttonFactory().newButton();
799             theButton.setTextOnly();
800             theButton.setText(pItem.getDisplay());
801 
802             /* Add to the Grid Pane */
803             theGrid.addCell(theButton);
804             theGrid.addCell(theField);
805             theGrid.setCellColumnSpan(theField, 2);
806             theGrid.allowCellGrowth(theField);
807             theGrid.newRow();
808 
809             /* Create listeners */
810             theButton.getEventRegistrar().addEventListener(e -> handleDialog());
811             theField.getEventRegistrar().addEventListener(e -> {
812                 pItem.setValue(theField.getValue());
813                 notifyChanges();
814             });
815         }
816 
817         @Override
818         public void updateField() {
819             /* Update the field */
820             theField.setValue(theItem.getValue());
821 
822             /* Set changed indication */
823             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
824             theField.adjustField();
825 
826             /* Handle hidden state */
827             final boolean isEnabled = !theItem.isHidden();
828             theField.setEnabled(isEnabled);
829             theButton.setEnabled(isEnabled);
830         }
831 
832         /**
833          * Handle Dialog.
834          */
835         private void handleDialog() {
836             ensureSelector();
837             theSelector.setInitialFile(new File(theItem.getValue()));
838             final File myFile = theSelector.selectFile();
839             if (myFile != null) {
840                 theItem.setValue(myFile.getAbsolutePath());
841                 notifyChanges();
842             }
843         }
844 
845         /**
846          * Ensure that selector is created.
847          */
848         private void ensureSelector() {
849             if (theSelector == null) {
850                 theSelector = theGuiFactory.dialogFactory().newFileSelector();
851                 theSelector.setTitle(theItem.getDisplay());
852             }
853         }
854     }
855 
856     /**
857      * Directory preference element.
858      */
859     private final class DirectoryPreferenceElement
860             implements PreferenceElement {
861         /**
862          * The Preference item.
863          */
864         private final MetisStringPreference theItem;
865 
866         /**
867          * The Field item.
868          */
869         private final TethysUIStringEditField theField;
870 
871         /**
872          * The Button.
873          */
874         private final TethysUIButton theButton;
875 
876         /**
877          * The Directory Selector.
878          */
879         private TethysUIDirectorySelector theSelector;
880 
881         /**
882          * Constructor.
883          *
884          * @param pItem the item
885          */
886         DirectoryPreferenceElement(final MetisStringPreference pItem) {
887             /* Store parameters */
888             theItem = pItem;
889             theField = theGuiFactory.fieldFactory().newStringField();
890             theField.setEditable(true);
891 
892             /* Create the button */
893             theButton = theGuiFactory.buttonFactory().newButton();
894             theButton.setTextOnly();
895             theButton.setText(pItem.getDisplay());
896 
897             /* Add to the Grid Pane */
898             theGrid.addCell(theButton);
899             theGrid.addCell(theField);
900             theGrid.setCellColumnSpan(theField, 2);
901             theGrid.allowCellGrowth(theField);
902             theGrid.newRow();
903 
904             /* Create listeners */
905             theButton.getEventRegistrar().addEventListener(e -> handleDialog());
906             theField.getEventRegistrar().addEventListener(e -> {
907                 pItem.setValue(theField.getValue());
908                 notifyChanges();
909             });
910         }
911 
912         @Override
913         public void updateField() {
914             /* Update the field */
915             theField.setValue(theItem.getValue());
916 
917             /* Set changed indication */
918             theField.setTheAttributeState(TethysUIFieldAttribute.CHANGED, theItem.isChanged());
919             theField.adjustField();
920 
921             /* Handle hidden state */
922             final boolean isEnabled = !theItem.isHidden();
923             theField.setEnabled(isEnabled);
924             theButton.setEnabled(isEnabled);
925         }
926 
927         /**
928          * Handle Dialog.
929          */
930         private void handleDialog() {
931             ensureSelector();
932             theSelector.setInitialDirectory(new File(theItem.getValue()));
933             final File myDir = theSelector.selectDirectory();
934             if (myDir != null) {
935                 theItem.setValue(myDir.getAbsolutePath());
936                 notifyChanges();
937             }
938         }
939 
940         /**
941          * Ensure that selector is created.
942          */
943         private void ensureSelector() {
944             if (theSelector == null) {
945                 theSelector = theGuiFactory.dialogFactory().newDirectorySelector();
946                 theSelector.setTitle(theItem.getDisplay());
947             }
948         }
949     }
950 }