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