gov.va.isaac.gui.preferences.plugins.ViewCoordinatePreferencesPluginViewController.java Source code

Java tutorial

Introduction

Here is the source code for gov.va.isaac.gui.preferences.plugins.ViewCoordinatePreferencesPluginViewController.java

Source

/**
 * Copyright Notice
 *
 * This is a work of the U.S. Government and is not subject to copyright
 * protection in the United States. Foreign copyrights may apply.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package gov.va.isaac.gui.preferences.plugins;

import gov.va.isaac.AppContext;
import gov.va.isaac.ExtendedAppContext;
import gov.va.isaac.config.generated.StatedInferredOptions;
import gov.va.isaac.config.profiles.UserProfile;
import gov.va.isaac.config.profiles.UserProfileDefaults;
import gov.va.isaac.config.profiles.UserProfileManager;
import gov.va.isaac.config.users.InvalidUserException;
import gov.va.isaac.gui.util.TextErrorColorHelper;
import gov.va.isaac.util.OTFUtility;
import gov.va.isaac.util.Utility;
import gov.va.isaac.util.ValidBooleanBinding;
import gov.vha.isaac.metadata.coordinates.ViewCoordinates;
import gov.vha.isaac.metadata.source.IsaacMetadataAuxiliaryBinding;

import java.io.IOException;
import java.net.URL;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;

import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.ReadOnlySetProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleSetProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.control.ComboBox;
import javafx.scene.control.DatePicker;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.RadioButton;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import javafx.scene.control.Tooltip;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.scene.layout.RowConstraints;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.util.StringConverter;

import org.apache.commons.lang3.time.DateUtils;
import org.ihtsdo.otf.tcc.api.concept.ConceptChronicleBI;
import org.ihtsdo.otf.tcc.api.concept.ConceptVersionBI;
import org.ihtsdo.otf.tcc.api.coordinate.Status;
import org.ihtsdo.otf.tcc.api.coordinate.ViewCoordinate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.javafx.collections.ObservableSetWrapper;

/**
 * {@link ViewCoordinatePreferencesPluginViewController}
 *
 * @author <a href="mailto:joel.kniaz@gmail.com">Joel Kniaz</a>
 */

public class ViewCoordinatePreferencesPluginViewController {
    private final static Logger log = LoggerFactory.getLogger(ViewCoordinatePreferencesPluginViewController.class);

    /**
     * @author <a href="mailto:joel.kniaz@gmail.com">Joel Kniaz</a>
     *
     * For populating dateSelectorComboBox
     */
    private enum DateSelectionMethod {
        SPECIFY("Specify Date"), USE_LATEST("Use Latest Date");

        private final String displayName;

        private DateSelectionMethod(String dn) {
            displayName = dn;
        }

        public String getDisplayName() {
            return displayName;
        }
    }

    @FXML
    StackPane rootStackPaneInTab;
    @FXML
    GridPane gridPaneInRootStackPane;
    @FXML
    GridPane topGridPane;
    @FXML
    GridPane bottomGridPane;
    @FXML
    DatePicker datePicker;
    @FXML
    ComboBox<DateSelectionMethod> dateSelectionMethodComboBox;
    @FXML
    ComboBox<UUID> pathComboBox;
    @FXML
    ListView<SelectableModule> selectableModuleListView;
    @FXML
    VBox statusesToggleGroupVBox;
    @FXML
    VBox statedInferredToggleGroupVBox;

    private boolean contentLoaded = false;

    final ViewCoordinate panelViewCoordinate;

    final private ProgressIndicator progressIndicator = new ProgressIndicator();
    {
        progressIndicator.setMaxWidth(50.0);
        progressIndicator.setMaxHeight(50.0);
    }

    private ToggleGroup statusesToggleGroup = null;
    private ToggleGroup statedInferredToggleGroup = null;

    private ObservableList<SelectableModule> selectableModules = FXCollections
            .observableArrayList(new ArrayList<>());

    private ValidBooleanBinding allValid_ = null;

    // ValidBooleanBinding allValid_ dependencies
    private final ObjectProperty<StatedInferredOptions> currentStatedInferredOptionProperty = new SimpleObjectProperty<>();
    private final ObjectProperty<UUID> currentPathProperty = new SimpleObjectProperty<>();
    private final ObjectProperty<Long> currentTimeProperty = new SimpleObjectProperty<>();
    private final SimpleSetProperty<Status> currentStatusesProperty = new SimpleSetProperty<>(
            new ObservableSetWrapper<Status>(new HashSet<Status>()));
    private final SelectableModule allModulesMarker = new SelectableModule();
    private final ObservableSet<UUID> selectedModules = FXCollections.observableSet(new HashSet<UUID>());

    private final List<RadioButton> statedInferredOptionButtons = new ArrayList<>();

    private RadioButton activeStatusButton;
    private RadioButton inactiveStatusButton;
    private RadioButton activeAndInactiveStatusButton;

    protected static ViewCoordinatePreferencesPluginViewController construct() throws IOException {
        // Load from FXML.
        URL resource = ViewCoordinatePreferencesPluginViewController.class
                .getResource("ViewCoordinatePreferencesPluginView.fxml");
        log.debug("Loaded URL {}", resource);
        FXMLLoader loader = new FXMLLoader(resource);
        loader.load();
        return loader.getController();
    }

    {
        try {
            panelViewCoordinate = ViewCoordinates.getDevelopmentStatedLatest();
        } catch (IOException e) {
            String msg = "Caught " + e.getClass().getName() + " " + e.getLocalizedMessage()
                    + " attempting to initialize ViewCoordinate";
            log.error(msg, e);
            e.printStackTrace();

            throw new RuntimeException(msg, e);
        }
    }

    @FXML
    void initialize() {
        assert rootStackPaneInTab != null : "fx:id=\"rootStackPaneInTab\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert gridPaneInRootStackPane != null : "fx:id=\"gridPaneInRootStackPane\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert topGridPane != null : "fx:id=\"topGridPane\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert bottomGridPane != null : "fx:id=\"bottomGridPane\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert datePicker != null : "fx:id=\"datePicker\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert dateSelectionMethodComboBox != null : "fx:id=\"dateSelectionMethodComboBox\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert pathComboBox != null : "fx:id=\"pathComboBox\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert selectableModuleListView != null : "fx:id=\"selectableModuleListView\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert statusesToggleGroupVBox != null : "fx:id=\"statusesToggleGroupVBox\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";
        assert statedInferredToggleGroupVBox != null : "fx:id=\"statedInferredToggleGroupVBox\" was not injected: check your FXML file 'ViewCoordinatePreferencesPluginView.fxml'.";

        RowConstraints gridPaneRowConstraints = new RowConstraints();
        gridPaneRowConstraints.setVgrow(Priority.NEVER);

        addGridPaneRowConstraintsToAllRows(gridPaneInRootStackPane, gridPaneRowConstraints);
        addGridPaneRowConstraintsToAllRows(topGridPane, gridPaneRowConstraints);
        addGridPaneRowConstraintsToAllRows(bottomGridPane, gridPaneRowConstraints);

        //      currentPathProperty.addListener((observable, oldValue, newValue) -> {
        //         log.debug("currentPathProperty changed from {} to {}", oldValue, newValue);
        //      });
        //      currentStatedInferredOptionProperty.addListener((observable, oldValue, newValue) -> {
        //         log.debug("currentStatedInferredOptionProperty changed from {} to {}", oldValue, newValue);
        //      });
        //      currentTimeProperty.addListener((observable, oldValue, newValue) -> {
        //         log.debug("currentTimeProperty changed from {} to {}", oldValue, newValue);
        //      });
        //      currentStatusesProperty.addListener((observable, oldValue, newValue) -> {
        //         log.debug("currentStatusesProperty changed from {} to {}", Arrays.toString(oldValue.toArray()), Arrays.toString(newValue.toArray()));
        //      });

        initializeDatePicker();
        initializeDateSelectionMethodComboBox();
        initializePathComboBox();
        initializeSelectableModuleListView();
        initializeStatusesToggleGroup();
        initializeStatedInferredToggleGroup();
        initializeValidBooleanBinding();
    }

    private void setCurrentTimePropertyFromDatePicker() {
        Long dateSelected = null;
        if (datePicker.getValue() != null) {
            Instant instant = Instant.from(datePicker.getValue().atStartOfDay(ZoneId.systemDefault()));
            dateSelected = getStartOfNextDay(Date.from(instant)).getTime();
        } else {
            dateSelected = Long.MAX_VALUE;
        }
        currentTimeProperty.set(dateSelected);
    }

    private void setDatePickerFromCurrentTimeProperty() {
        if (currentTimeProperty.get() != Long.MAX_VALUE) {
            runLaterIfNotFXApplicationThread(
                    () -> datePicker.setValue(getPriorDay(new Date(currentTimeProperty.get())).toInstant()
                            .atZone(ZoneId.systemDefault()).toLocalDate()));
        } else {
            runLaterIfNotFXApplicationThread(
                    () -> datePicker.setValue(new Date().toInstant().atZone(ZoneId.systemDefault()).toLocalDate()));
        }
    }

    private void initializeDatePicker() {
        datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
            if (dateSelectionMethodComboBox.getSelectionModel().getSelectedItem() == DateSelectionMethod.SPECIFY) {
                setCurrentTimePropertyFromDatePicker();
            }
        });
        //      datePicker.valueProperty().addListener((observable, oldValue, newValue) -> {
        //         log.debug("datePicker value changed from {} to {} while DateSelectionMethod={}", oldValue, newValue, dateSelectionMethodComboBox.getSelectionModel().getSelectedItem());
        //      });
        //      datePicker.setOnAction((event) -> {
        //         log.debug("DatePicker activated with value=" + datePicker.getValue() + ", selectionMode=" + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem() + " and current time=" + currentTimeProperty.get());
        //      });
        datePicker.setTooltip(new Tooltip(
                "Enter valid date or click to select date from calendar representing\nan historical snapshot version of the database"));
    }

    private void initializeDateSelectionMethodComboBox() {
        dateSelectionMethodComboBox.setCellFactory((param) -> {
            final ListCell<DateSelectionMethod> cell = new ListCell<DateSelectionMethod>() {
                @Override
                protected void updateItem(DateSelectionMethod selectionMethod, boolean emptyRow) {
                    super.updateItem(selectionMethod, emptyRow);
                    if (selectionMethod == null) {
                        setText(null);
                    } else {
                        setText(selectionMethod.getDisplayName());
                    }
                }
            };
            return cell;
        });

        dateSelectionMethodComboBox.setButtonCell(new ListCell<DateSelectionMethod>() {
            @Override
            protected void updateItem(DateSelectionMethod selectionMethod, boolean emptyRow) {
                super.updateItem(selectionMethod, emptyRow);
                if (emptyRow) {
                    setText("");
                } else {
                    switch (selectionMethod) {
                    case SPECIFY:
                        runLaterIfNotFXApplicationThread(() -> datePicker.setVisible(true));
                        setText(selectionMethod.getDisplayName());
                        //                  log.debug("dateSelectorComboBox button cell set to " + getText() + ", datePicker has value=" + datePicker.getValue() + ", selectionMode=" + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem() + " and current time=" + currentTimeProperty.get());

                        // This should change if default time ever changes
                        runLaterIfNotFXApplicationThread(
                                () -> dateSelectionMethodComboBox.setTooltip(new Tooltip(getText()
                                        + " is selected.  Use date picker control to specify a date\nin the past representing an historical snapshot version of the database\nor click and select "
                                        + DateSelectionMethod.USE_LATEST.getDisplayName()
                                        + " to always use latest.\nDefault is "
                                        + DateSelectionMethod.USE_LATEST.getDisplayName() + ".")));
                        break;
                    case USE_LATEST:
                        runLaterIfNotFXApplicationThread(() -> datePicker.setVisible(false));
                        setText(selectionMethod.getDisplayName());
                        //                  log.debug("dateSelectorComboBox button cell set to " + getText() + ", datePicker has value=" + datePicker.getValue() + ", selectionMode=" + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem() + " and current time=" + currentTimeProperty.get());

                        runLaterIfNotFXApplicationThread(
                                () -> dateSelectionMethodComboBox.setTooltip(new Tooltip(getText()
                                        + " is selected, so latest (most recent) date will always be used.\nClick and select "
                                        + DateSelectionMethod.SPECIFY.getDisplayName()
                                        + " to use date picker control to specify a date\nin the past representing an historical snapshot version of the database.\nDefault is "
                                        + DateSelectionMethod.USE_LATEST.getDisplayName() + ".")));
                        break;
                    default:
                        // Should never happen
                        throw new IllegalArgumentException(
                                "Failed setting dateSelectorComboBox ButtonCell. Unsupported "
                                        + selectionMethod.getClass().getName() + " value " + selectionMethod.name()
                                        + ".  Must be " + DateSelectionMethod.SPECIFY.name() + " or "
                                        + DateSelectionMethod.USE_LATEST.name() + ".");
                    }
                }
            }
        });
        dateSelectionMethodComboBox.setOnAction((event) -> {
            //         log.debug("dateSelectorComboBox activated. datePicker has value=" + datePicker.getValue() + ", selectionMode=" + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem() + " and current time=" + currentTimeProperty.get());

            switch (dateSelectionMethodComboBox.getSelectionModel().getSelectedItem()) {
            case SPECIFY:
                if (currentTimeProperty.get() == Long.MAX_VALUE) {
                    setCurrentTimePropertyFromDatePicker();
                } else {
                    setDatePickerFromCurrentTimeProperty();
                }
                break;
            case USE_LATEST:
                currentTimeProperty.set(Long.MAX_VALUE);
                break;
            default:
                // Should never happen
                throw new IllegalArgumentException("Unsupported "
                        + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem().getClass().getName()
                        + " value " + dateSelectionMethodComboBox.getSelectionModel().getSelectedItem().name()
                        + ".  Must be " + DateSelectionMethod.SPECIFY.name() + " or "
                        + DateSelectionMethod.USE_LATEST.name() + ".");
            }
        });
        dateSelectionMethodComboBox.getItems().addAll(DateSelectionMethod.values());
        dateSelectionMethodComboBox.getSelectionModel().select(DateSelectionMethod.USE_LATEST);
    }

    private void initializePathComboBox() {
        pathComboBox.setCellFactory((param) -> {
            final ListCell<UUID> cell = new ListCell<UUID>() {
                @Override
                protected void updateItem(UUID c, boolean emptyRow) {
                    super.updateItem(c, emptyRow);
                    if (c == null) {
                        setText(null);
                    } else {
                        String desc = OTFUtility.getDescription(c, panelViewCoordinate);
                        setText(desc);
                    }
                }
            };
            return cell;
        });
        pathComboBox.setButtonCell(new ListCell<UUID>() {
            @Override
            protected void updateItem(UUID c, boolean emptyRow) {
                super.updateItem(c, emptyRow);
                if (emptyRow) {
                    setText("");
                } else {
                    String desc = OTFUtility.getDescription(c, panelViewCoordinate);
                    //               log.debug("Setting path button cell to \"" + desc + "\"");
                    setText(desc);
                }
            }
        });
        pathComboBox.setConverter(new StringConverter<UUID>() {
            @Override
            public String toString(UUID uuid) {
                if (uuid == null) {
                    return null;
                } else {
                    return OTFUtility.getDescription(uuid, panelViewCoordinate);
                }
            }

            @Override
            public UUID fromString(String userId) {
                return null;
            }
        });
        currentPathProperty.bind(pathComboBox.getSelectionModel().selectedItemProperty());
    }

    private void initializeSelectableModuleListView() {
        allModulesMarker.selectedProperty().set(true); // default only.  may be changed in getContent()
        selectableModuleListView.setCellFactory(CheckBoxListCell.forListView(SelectableModule::selectedProperty,
                new StringConverter<SelectableModule>() {
                    @Override
                    public String toString(SelectableModule object) {
                        return object.getDescription();
                    }

                    @Override
                    public SelectableModule fromString(String string) {
                        return null;
                    }
                }));
        selectableModuleListView.setTooltip(new Tooltip(
                "Select one or more modules to enable filtering for selected modules\nor deselect all to disable filtering by module.\nDefault module list is "
                        + Arrays.toString(getDefaultModules().toArray(new UUID[getDefaultModules().size()]))));
    }

    private void initializeStatusesToggleGroup() {
        statusesToggleGroup = new ToggleGroup();
        Tooltip statusButtonsTooltip = new Tooltip("Default Status(es) is " + getDefaultStatuses());
        activeStatusButton = new RadioButton();
        activeStatusButton.setText("Active");
        activeStatusButton.setTooltip(statusButtonsTooltip);
        statusesToggleGroup.getToggles().add(activeStatusButton);
        activeStatusButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue) {
                currentStatusesProperty.get().clear();
                currentStatusesProperty.add(Status.ACTIVE);
            }
        });
        statusesToggleGroupVBox.getChildren().add(activeStatusButton);

        inactiveStatusButton = new RadioButton();
        inactiveStatusButton.setText("Inactive");
        inactiveStatusButton.setTooltip(statusButtonsTooltip);
        statusesToggleGroup.getToggles().add(inactiveStatusButton);
        inactiveStatusButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue) {
                currentStatusesProperty.get().clear();
                currentStatusesProperty.add(Status.INACTIVE);
            }
        });
        statusesToggleGroupVBox.getChildren().add(inactiveStatusButton);

        activeAndInactiveStatusButton = new RadioButton();
        activeAndInactiveStatusButton.setText("All");
        activeAndInactiveStatusButton.setTooltip(statusButtonsTooltip);
        statusesToggleGroup.getToggles().add(activeAndInactiveStatusButton);
        activeAndInactiveStatusButton.selectedProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue) {
                currentStatusesProperty.get().clear();
                currentStatusesProperty.add(Status.ACTIVE);
                currentStatusesProperty.add(Status.INACTIVE);
            }
        });
        statusesToggleGroupVBox.getChildren().add(activeAndInactiveStatusButton);
    }

    private void initializeStatedInferredToggleGroup() {
        statedInferredToggleGroup = new ToggleGroup(); //Stated / Inferred

        for (StatedInferredOptions option : StatedInferredOptions.values()) {
            RadioButton optionButton = new RadioButton();
            if (option == StatedInferredOptions.STATED) {
                optionButton.setText("Stated");
            } else if (option == StatedInferredOptions.INFERRED) {
                optionButton.setText("Inferred");
            } else {
                throw new RuntimeException("oops");
            }
            optionButton.setUserData(option);
            optionButton
                    .setTooltip(new Tooltip("Default StatedInferredOption is " + getDefaultStatedInferredOption()));
            statedInferredToggleGroup.getToggles().add(optionButton);
            statedInferredToggleGroupVBox.getChildren().add(optionButton);
            statedInferredOptionButtons.add(optionButton);
        }
        statedInferredToggleGroup.selectedToggleProperty()
                .addListener((observable, oldValue,
                        newValue) -> runLaterIfNotFXApplicationThread(() -> currentStatedInferredOptionProperty
                                .set((StatedInferredOptions) newValue.getUserData())));
    }

    private void initializeValidBooleanBinding() {
        allValid_ = new ValidBooleanBinding() {
            {
                bind(currentStatedInferredOptionProperty, currentPathProperty, currentTimeProperty,
                        currentStatusesProperty, selectedModules, allModulesMarker.selectedProperty(),
                        dateSelectionMethodComboBox.getSelectionModel().selectedItemProperty());
                setComputeOnInvalidate(true);
            }

            @Override
            protected boolean computeValue() {
                if (currentStatedInferredOptionProperty.get() == null) {
                    this.setInvalidReason("Null/unset/unselected StatedInferredOption");
                    for (RadioButton button : statedInferredOptionButtons) {
                        TextErrorColorHelper.setTextErrorColor(button);
                    }
                    return false;
                } else {
                    for (RadioButton button : statedInferredOptionButtons) {
                        TextErrorColorHelper.clearTextErrorColor(button);
                    }
                }
                if (currentPathProperty.get() == null) {
                    this.setInvalidReason("Null/unset/unselected path");
                    TextErrorColorHelper.setTextErrorColor(pathComboBox);

                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(pathComboBox);
                }
                if (OTFUtility.getConceptVersion(currentPathProperty.get()) == null) {
                    this.setInvalidReason("Invalid path");
                    TextErrorColorHelper.setTextErrorColor(pathComboBox);

                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(pathComboBox);
                }
                if (currentStatusesProperty.get() == null || currentStatusesProperty.get().size() < 1) {
                    this.setInvalidReason("Status unset");
                    TextErrorColorHelper.setTextErrorColor(activeStatusButton);
                    TextErrorColorHelper.setTextErrorColor(inactiveStatusButton);
                    TextErrorColorHelper.setTextErrorColor(activeAndInactiveStatusButton);

                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(activeStatusButton);
                    TextErrorColorHelper.clearTextErrorColor(inactiveStatusButton);
                    TextErrorColorHelper.clearTextErrorColor(activeAndInactiveStatusButton);
                }
                if (currentTimeProperty.get() == null) {
                    this.setInvalidReason("View Coordinate Time is unset");
                    TextErrorColorHelper.setTextErrorColor(dateSelectionMethodComboBox);
                    TextErrorColorHelper.setTextErrorColor(datePicker);
                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(dateSelectionMethodComboBox);
                    TextErrorColorHelper.clearTextErrorColor(datePicker);
                }
                if (currentTimeProperty.get() == Long.MAX_VALUE && dateSelectionMethodComboBox.getSelectionModel()
                        .selectedItemProperty().get() == DateSelectionMethod.SPECIFY) {
                    this.setInvalidReason(
                            "View Coordinate Time is unselected while selection method is not Latest");
                    TextErrorColorHelper.setTextErrorColor(dateSelectionMethodComboBox);
                    TextErrorColorHelper.setTextErrorColor(datePicker);
                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(dateSelectionMethodComboBox);
                    TextErrorColorHelper.clearTextErrorColor(datePicker);
                }

                if (selectedModules.size() > 0) {
                    // Remove this check when modules supported by ViewCoordinateFactory.get()
                    this.setInvalidReason(
                            "Selection of specific module(s) not currently supported.  Currently, only ALL is supported.");
                    TextErrorColorHelper.setTextErrorColor(selectableModuleListView);
                    return false;
                } else if (allModulesMarker.selectedProperty().get() && selectedModules.size() > 0) {
                    this.setInvalidReason("ALL module cannot be selected while any (currently "
                            + selectedModules.size() + ") specific module selected");
                    TextErrorColorHelper.setTextErrorColor(selectableModuleListView);
                    return false;
                } else if (!allModulesMarker.selectedProperty().get() && selectedModules.size() == 0) {
                    this.setInvalidReason("ALL module must be selected if no specific module(s) selected");
                    TextErrorColorHelper.setTextErrorColor(selectableModuleListView);
                    return false;
                } else {
                    TextErrorColorHelper.clearTextErrorColor(selectableModuleListView);
                }

                this.clearInvalidReason();
                return true;
            }
        };
    }

    public Region getContent() {
        Task<Void> task = new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                log.debug("initializing content");

                try {
                    if (!contentLoaded) {
                        contentLoaded = true;

                        // Populate selectableModules
                        final ConceptVersionBI moduleRootConcept = OTFUtility.getConceptVersion(
                                IsaacMetadataAuxiliaryBinding.MODULE.getPrimodialUuid(), panelViewCoordinate);
                        final Set<ConceptVersionBI> moduleConcepts = new HashSet<>();
                        try {
                            moduleConcepts.addAll(OTFUtility.getAllChildrenOfConcept(moduleRootConcept.getNid(),
                                    panelViewCoordinate, false));
                        } catch (Exception e) {
                            log.error("Failed loading module concepts as children of " + moduleRootConcept, e);
                            e.printStackTrace();
                            AppContext.getCommonDialogs()
                                    .showErrorDialog("Failed loading module concepts as children of "
                                            + moduleRootConcept + ". See logs.", e);
                        }
                        List<SelectableModule> modules = new ArrayList<>();
                        for (ConceptVersionBI cv : moduleConcepts) {
                            modules.add(new SelectableModule(cv.getNid()));
                        }
                        selectableModules.clear();
                        selectableModules.addAll(modules);

                        allModulesMarker.selected.addListener((observable, oldValue, newValue) -> {
                            if (newValue) {
                                for (SelectableModule module : selectableModules) {
                                    module.selectedProperty().set(false);
                                }
                            }
                        });
                        selectableModules.forEach(selectableModule -> selectableModule.selectedProperty()
                                .addListener((observable, wasSelected, isSelected) -> {
                                    if (isSelected) {
                                        if (!wasSelected) {
                                            //log.debug("Adding module nid={}, uuid={}, desc={}", selectableModule.getNid(), selectableModule.getUuid(), selectableModule.getDescription());
                                            selectedModules.add(selectableModule.getUuid());
                                            allModulesMarker.selectedProperty().set(false);
                                        }
                                    } else {
                                        if (wasSelected) {
                                            //log.debug("Removing module nid={}, uuid={}, desc={}", selectableModule.getNid(), selectableModule.getUuid(), selectableModule.getDescription());
                                            selectedModules.remove(selectableModule.getUuid());

                                            if (selectedModules.size() == 0) {
                                                allModulesMarker.selectedProperty().set(true);
                                            }
                                        }
                                    }
                                }));
                        selectableModuleListView.getItems().clear();
                        selectableModuleListView.getItems().add(allModulesMarker);
                        Collections.sort(selectableModules);
                        selectableModuleListView.getItems().addAll(selectableModules);

                        runLaterIfNotFXApplicationThread(
                                () -> pathComboBox.setTooltip(new Tooltip("Default path is \""
                                        + OTFUtility.getDescription(getDefaultPath(), panelViewCoordinate)
                                        + "\"")));

                        pathComboBox.getItems().clear();
                        pathComboBox.getItems().addAll(getPathOptions());
                    }

                    // Reload persisted values every time

                    UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
                    pathComboBox.getSelectionModel().select(loggedIn.getViewCoordinatePath());

                    // Reload storedStatedInferredOption
                    loadStoredStatedInferredOption();

                    // Reload storedStatuses
                    loadStoredStatuses();

                    // Reload storedModules
                    final Set<UUID> storedModuleUuids = getStoredModules();
                    if (storedModuleUuids.size() == 0) {
                        allModulesMarker.setSelected(true);
                    } else {
                        // Check to make sure that stored UUID refers to an existing, known module
                        for (UUID storedModuleUuid : storedModuleUuids) {
                            boolean foundStoredUuidModuleInSelectableModules = false;
                            for (SelectableModule selectableModule : selectableModules) {
                                if (storedModuleUuid.equals(selectableModule.getUuid())) {
                                    foundStoredUuidModuleInSelectableModules = true;
                                    break;
                                }
                            }

                            if (!foundStoredUuidModuleInSelectableModules) {
                                log.error(
                                        "Loaded module (uuid={}) from user preferences that does not currently exist",
                                        storedModuleUuid);
                                AppContext.getCommonDialogs().showErrorDialog("Unsupported Module",
                                        "Loaded a module UUID from UserProfile that does not correspond to existing module",
                                        "Concept (UUID=" + storedModuleUuid
                                                + ") not a valid module.  Must be one of "
                                                + Arrays.toString(selectableModules.toArray()));
                            }
                        }
                        for (SelectableModule module : selectableModules) {
                            if (storedModuleUuids.contains(module.getUuid())) {
                                module.setSelected(true);
                            } else {
                                module.setSelected(false);
                            }
                        }
                    }

                    Long storedTime = getStoredTime();
                    if (storedTime.equals(Long.MAX_VALUE)) {
                        dateSelectionMethodComboBox.getSelectionModel().select(DateSelectionMethod.USE_LATEST);
                        currentTimeProperty.set(Long.MAX_VALUE);
                        runLaterIfNotFXApplicationThread(() -> datePicker.setValue(LocalDate.now()));
                    } else {
                        dateSelectionMethodComboBox.getSelectionModel().select(DateSelectionMethod.SPECIFY);
                        currentTimeProperty.set(storedTime);
                        setDatePickerFromCurrentTimeProperty();
                    }

                    return null;
                } catch (Exception e) {
                    log.error("initContent() task caught " + e.getClass().getName() + " " + e.getLocalizedMessage(),
                            e);
                    e.printStackTrace();
                    throw e;
                }
            }

            @Override
            protected void succeeded() {
                log.debug("Content initialization succeeded");

                removeProgressIndicator();
            }

            @Override
            protected void failed() {
                removeProgressIndicator();

                Throwable ex = getException();
                log.error("loadContent() caught " + ex.getClass().getName() + " " + ex.getLocalizedMessage(), ex);
                AppContext.getCommonDialogs().showErrorDialog("Failed loading content. See logs.", ex);
            }
        };

        addProgressIndicator();

        Utility.execute(task);

        return gridPaneInRootStackPane;
    }

    private void loadStoredStatedInferredOption() {
        // Reload storedStatedInferredOption
        final StatedInferredOptions storedStatedInferredOption = getStoredStatedInferredOption();
        for (Toggle toggle : statedInferredToggleGroup.getToggles()) {
            if (toggle.getUserData() == storedStatedInferredOption) {
                toggle.setSelected(true);
            }
        }
    }

    private void loadStoredStatuses() {
        // Reload storedStatuses
        final Set<Status> storedStatuses = getStoredStatuses();
        if (storedStatuses.contains(Status.ACTIVE) && storedStatuses.contains(Status.INACTIVE)) {
            statusesToggleGroup.selectToggle(activeAndInactiveStatusButton);
        } else if (storedStatuses.contains(Status.ACTIVE)) {
            statusesToggleGroup.selectToggle(activeStatusButton);
        } else if (storedStatuses.contains(Status.INACTIVE)) {
            statusesToggleGroup.selectToggle(inactiveStatusButton);
        } else if (storedStatuses.size() == 0) {
            log.warn("UserProfile does not contain any view coordinate Status values");
        } else {
            log.error("UserProfile contains unsupported view coordinate Status values: {}",
                    storedStatuses.toArray());
            AppContext.getCommonDialogs().showErrorDialog("Unsupported View Coordinate Status",
                    "UserProfile contains unsupported view coordinate Status values",
                    Arrays.toString(storedStatuses.toArray()));
        }
    }

    private void addProgressIndicator() {
        if (!rootStackPaneInTab.getChildren().contains(progressIndicator)) {
            rootStackPaneInTab.getChildren().add(0, progressIndicator);
        }
        progressIndicator.setVisible(true);
    }

    private void removeProgressIndicator() {
        rootStackPaneInTab.getChildren().remove(progressIndicator);
    }

    public ReadOnlyStringProperty validationFailureMessageProperty() {
        return allValid_.getReasonWhyInvalid();
    }

    public void save() throws IOException {
        log.debug("Saving ViewCoordinatePreferencesPluginView data");
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();

        //Path Property
        log.debug("Setting stored VC path (currently \"{}\") to {}", loggedIn.getViewCoordinatePath(),
                currentPathProperty().get());
        loggedIn.setViewCoordinatePath(currentPathProperty().get());

        //Stated/Inferred Policy
        log.debug("Setting stored VC StatedInferredPolicy (currently \"{}\") to {}",
                loggedIn.getStatedInferredPolicy(), currentStatedInferredOptionProperty().get());
        loggedIn.setStatedInferredPolicy(currentStatedInferredOptionProperty().get());

        //Time Property
        log.debug("Setting stored VC time to :" + currentTimeProperty().get());
        loggedIn.setViewCoordinateTime(currentTimeProperty().get());

        //Statuses Property
        log.debug("Setting stored VC statuses to :" + currentStatusesProperty().get());
        loggedIn.setViewCoordinateStatuses(currentStatusesProperty().get());

        //Modules Property
        log.debug("Setting stored VC modules to :" + selectedModules);
        loggedIn.setViewCoordinateModules(selectedModules);

        try {
            AppContext.getService(UserProfileManager.class).saveChanges(loggedIn);
        } catch (InvalidUserException e) {
            String msg = "Caught " + e.getClass().getName() + " " + e.getLocalizedMessage()
                    + " attempting to save UserProfile for " + getClass().getName();

            log.error(msg, e);
            throw new IOException(msg, e);
        }
    }

    public static Date getEndOfDay(Date date) {
        return DateUtils.addMilliseconds(DateUtils.ceiling(date, Calendar.DATE), -1);
    }

    public static Date getStartOfDay(Date date) {
        return DateUtils.truncate(date, Calendar.DATE);
    }

    public static Date getStartOfNextDay(Date date) {
        return getStartOfDay(DateUtils.addDays(date, 1));
    }

    public static Date getPriorDay(Date date) {
        return DateUtils.addDays(date, -1);
    }

    protected Collection<UUID> getPathOptions() {
        List<UUID> list = new ArrayList<>();

        try {
            List<ConceptChronicleBI> pathConcepts = OTFUtility.getPathConcepts();
            for (ConceptChronicleBI cc : pathConcepts) {
                list.add(cc.getPrimordialUuid());
            }
        } catch (Exception e) {
            log.error("Failed loading path concepts. Caught {} {}", e.getClass().getName(),
                    e.getLocalizedMessage());
            e.printStackTrace();
            AppContext.getCommonDialogs().showErrorDialog("Failed loading path options.  See logs.", e);
        }
        // Add currently-stored value to list of options, if not already there
        UUID storedPath = getStoredPath();
        if (storedPath != null && !list.contains(storedPath)) {
            list.add(storedPath);
        }

        return list;
    }

    protected Long getStoredTime() {
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
        return loggedIn.getViewCoordinateTime();
    }

    protected UUID getStoredPath() {
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
        return loggedIn.getViewCoordinatePath();
    }

    protected StatedInferredOptions getStoredStatedInferredOption() {
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
        return loggedIn.getStatedInferredPolicy();
    }

    protected Set<Status> getStoredStatuses() {
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
        return loggedIn.getViewCoordinateStatuses();
    }

    protected Set<UUID> getStoredModules() {
        UserProfile loggedIn = ExtendedAppContext.getCurrentlyLoggedInUserProfile();
        return loggedIn.getViewCoordinateModules();
    }

    protected Set<UUID> getDefaultModules() {
        return UserProfileDefaults.getDefaultViewCoordinateModules();
    }

    protected UUID getDefaultPath() {
        return UserProfileDefaults.getDefaultViewCoordinatePath();
    }

    protected Long getDefaultTime() {
        return UserProfileDefaults.getDefaultViewCoordinateTime();
    }

    protected StatedInferredOptions getDefaultStatedInferredOption() {
        return UserProfileDefaults.getDefaultStatedInferredPolicy();
    }

    protected Set<Status> getDefaultStatuses() {
        return UserProfileDefaults.getDefaultViewCoordinateStatuses();
    }

    public ReadOnlySetProperty<Status> currentStatusesProperty() {
        return currentStatusesProperty;
    }

    public ReadOnlyObjectProperty<Long> currentTimeProperty() {
        return currentTimeProperty;
    }

    public ReadOnlyObjectProperty<StatedInferredOptions> currentStatedInferredOptionProperty() {
        return currentStatedInferredOptionProperty;
    }

    public ReadOnlyObjectProperty<UUID> currentPathProperty() {
        return currentPathProperty;
    }

    private class SelectableModule implements Comparable<SelectableModule> {
        private final IntegerProperty nid = new SimpleIntegerProperty();
        private final BooleanProperty selected = new SimpleBooleanProperty(false);
        private final String description;
        private final UUID uuid;

        /**
         * Constructor for returning SelectableModule representing ALL modules
         */
        private SelectableModule() {
            nid.set(0);
            description = "ALL";
            uuid = null;
        }

        public SelectableModule(Integer nid) {
            this.nid.set(nid);

            String desc = null;
            try {
                desc = OTFUtility.getDescription(nid, panelViewCoordinate);
            } catch (Exception e) {
                log.error("Failed to set description for concept with nid={}", nid);
            }
            description = desc;

            UUID aUuid = null;
            try {
                ConceptVersionBI cv = OTFUtility.getConceptVersion(nid, panelViewCoordinate);
                aUuid = cv.getPrimordialUuid();
            } catch (Exception e) {
                log.error("Failed to set uuid for concept with nid={} and desc={}", nid, description);
            }
            uuid = aUuid;
        }

        public Integer getNid() {
            return nid.get();
        }

        public BooleanProperty selectedProperty() {
            return selected;
        }

        public void setSelected(boolean selected) {
            this.selected.set(selected);
        }

        public String getDescription() {
            return description;
        }

        public UUID getUuid() {
            return uuid;
        }

        @Override
        public int compareTo(SelectableModule o) {
            return description.compareTo(o.description);
        }
    }

    private static int getNumGridPaneRows(GridPane gridPane) {
        int numGridPaneRows = 0;
        for (javafx.scene.Node node : gridPane.getChildren()) {
            if (node != null) {
                Integer rowIndex = GridPane.getRowIndex(node);
                if (rowIndex != null && rowIndex >= numGridPaneRows) {
                    ++numGridPaneRows;
                }
            }
        }

        return numGridPaneRows;
    }

    private static void addGridPaneRowConstraintsToAllRows(GridPane gridPane, RowConstraints rowConstraints) {
        final int numGridPaneRows = getNumGridPaneRows(gridPane);
        for (int i = 0; i < numGridPaneRows; ++i) {
            gridPane.getRowConstraints().add(i, rowConstraints);
        }
    }

    public static void runLaterIfNotFXApplicationThread(Runnable work) {
        if (Platform.isFxApplicationThread()) {
            work.run();
        } else {
            Platform.runLater(work);
        }
    }
}