package org.esa.snap.opendap.ui;
import com.bc.ceres.swing.progress.ProgressBarProgressMonitor;
import com.jidesoft.swing.CheckBoxList;
import com.jidesoft.swing.CheckBoxListSelectionModel;
import com.jidesoft.swing.LabeledTextField;
import org.esa.snap.core.ui.GridBagUtils;
import org.esa.snap.core.ui.util.FilteredListModel;
import org.esa.snap.opendap.datamodel.DAPVariable;
import org.esa.snap.opendap.datamodel.OpendapLeaf;
import org.esa.snap.opendap.utils.VariableCollector;
import org.esa.snap.util.logging.BeamLogManager;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.SwingWorker;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Logger;
public class VariableFilter implements FilterComponent, CatalogTree.CatalogTreeListener {
private static final Logger LOG = Logger.getLogger(VariableFilter.class.getName());
private static final int MAX_THREAD_COUNT = 10;
private final JCheckBox filterCheckBox;
private VariableCollector collector = new VariableCollector();
private VariableListModel listModel;
private JButton selectAllButton;
private JButton selectNoneButton;
private JButton applyButton;
private LabeledTextField filterField;
private FilteredListModel<DAPVariable> filteredListModel;
private CheckBoxList checkBoxList;
private List<FilterChangeListener> listeners;
private final HashSet<VariableFilterPreparator> filterPreparators = new HashSet<>();
private final List<VariableFilterPreparator> filterPreparatorsInWait = new ArrayList<>();
private LabelledProgressBarPM pm;
private JProgressBar progressBar;
private JLabel statusLabel;
private JLabel percentageLabel;
private double totalWork;
private double worked;
public VariableFilter(JCheckBox filterCheckBox, CatalogTree catalogTree) {
this.filterCheckBox = filterCheckBox;
catalogTree.addCatalogTreeListener(this);
listeners = new ArrayList<>();
}
@Override
public JComponent getUI() {
JPanel panel = GridBagUtils.createPanel();
initComponents();
configureComponents();
addComponents(panel);
updateUI(false, false, false);
return panel;
}
private void initComponents() {
applyButton = new JButton("Apply");
selectAllButton = new JButton("Select all");
selectNoneButton = new JButton("Select none");
listModel = new VariableListModel();
filterField = new LabeledTextField();
filteredListModel = new FilteredListModel<>(listModel);
checkBoxList = new ToolTippedCheckBoxList(filteredListModel);
progressBar = new JProgressBar();
statusLabel = new JLabel("");
percentageLabel = new JLabel("");
pm = new VariableFilterProgressBarProgressMonitor(progressBar, statusLabel, percentageLabel);
}
private void configureComponents() {
selectAllButton.addActionListener(e -> {
int variableCount = checkBoxList.getModel().getSize();
int[] selectedIndices = new int[variableCount];
for (int i = 0; i < variableCount; i++) {
selectedIndices[i] = i;
}
checkBoxList.setCheckBoxListSelectedIndices(selectedIndices);
updateUI(true, false, true);
});
selectNoneButton.addActionListener(e -> {
checkBoxList.setCheckBoxListSelectedIndices(new int[0]);
updateUI(true, true, false);
});
applyButton.addActionListener(e -> {
fireFilterChanged();
updateUI(false, selectAllButton.isEnabled(), selectNoneButton.isEnabled());
});
filterCheckBox.setEnabled(false);
filterCheckBox.addActionListener(e -> {
boolean useFilter = filterCheckBox.isSelected();
fireFilterChanged();
updateUI(useFilter, useFilter, useFilter);
});
checkBoxList.getCheckBoxListSelectionModel().addListSelectionListener(e -> {
CheckBoxListSelectionModel model = (CheckBoxListSelectionModel) e.getSource();
int anchorSelectionIndex = model.getAnchorSelectionIndex();
if (e.getValueIsAdjusting() || anchorSelectionIndex == -1) {
return;
}
for (int i = 0; i < listModel.getSize(); i++) {
DAPVariable variable = listModel.getElementAt(i);
DAPVariable currentVariable = (DAPVariable) model.getModel().getElementAt(anchorSelectionIndex);
if (variable.equals(currentVariable)) {
boolean isSelected = model.isSelectedIndex(anchorSelectionIndex);
setVariableSelected(currentVariable, isSelected);
}
}
updateUI(true, true, true);
});
Font font = selectAllButton.getFont().deriveFont(10.0F);
selectAllButton.setFont(font);
selectNoneButton.setFont(font);
filterField.setHintText("Type here to filter variables");
filterField.getTextField().getDocument().addDocumentListener(new FilterDocumentListener());
progressBar.setVisible(false);
}
private void addComponents(JPanel panel) {
GridBagConstraints gbc = new GridBagConstraints();
JScrollPane scrollPane = new JScrollPane(checkBoxList);
scrollPane.setPreferredSize(new Dimension(250, 100));
GridBagUtils.addToPanel(panel, statusLabel, gbc, "insets.top=5, anchor=WEST");
GridBagUtils.addToPanel(panel, progressBar, gbc, "gridx=1,fill=HORIZONTAL, weightx=1.0");
GridBagUtils.addToPanel(panel, percentageLabel, gbc, "insets.left=5, gridx=2, fill=NONE, weightx=0.0");
GridBagUtils.addToPanel(panel, filterField, gbc, "insets.left=0, gridx=0, gridy=1, gridwidth=3, fill=HORIZONTAL, weightx=1.0");
GridBagUtils.addToPanel(panel, scrollPane, gbc, "gridy=2");
GridBagUtils.addToPanel(panel, selectAllButton, gbc, "insets.right=5, gridy=3, gridwidth=1, fill=NONE, weightx=0");
GridBagUtils.addToPanel(panel, selectNoneButton, gbc, "gridx=1");
GridBagUtils.addToPanel(panel, applyButton, gbc, "insets.right=0, gridx=2, gridy=4, anchor=EAST");
}
@Override
public boolean accept(OpendapLeaf leaf) {
DAPVariable[] dapVariables = leaf.getDAPVariables();
if (noVariablesAreSelected()) {
return true;
}
for (DAPVariable dapVariable : dapVariables) {
Boolean isSelected = listModel.variableToSelected.get(dapVariable);
boolean leafContainsVariable = isSelected == null ? false : isSelected;
if (leafContainsVariable) {
return true;
}
}
return false;
}
private boolean noVariablesAreSelected() {
for (Boolean selected : listModel.variableToSelected.values()) {
if (selected) {
return false;
}
}
return true;
}
private void updateUI(boolean enableApplyButton, boolean enableSelectAllButton, boolean enableSelectNoneButton) {
boolean notAllSelected = checkBoxList.getModel().getSize() == 0 ||
checkBoxList.getCheckBoxListSelectedIndices().length <
checkBoxList.getModel().getSize();
boolean someSelected = checkBoxList.getCheckBoxListSelectedIndices().length > 0;
boolean filtersAvailable = checkBoxList.getModel().getSize() > 0;
selectAllButton.setEnabled(filterCheckBox.isSelected() && enableSelectAllButton && notAllSelected);
selectNoneButton.setEnabled(filterCheckBox.isSelected() && enableSelectNoneButton && someSelected);
applyButton.setEnabled(filterCheckBox.isSelected() && enableApplyButton && filtersAvailable);
checkBoxList.setEnabled(filterCheckBox.isSelected());
filterField.setEnabled(filterCheckBox.isSelected());
}
@Override
public void addFilterChangeListener(FilterChangeListener listener) {
listeners.add(listener);
}
private void fireFilterChanged() {
for (FilterChangeListener listener : listeners) {
listener.filterChanged();
}
}
public void addVariable(DAPVariable dapVariable) {
listModel.allVariables.add(dapVariable);
}
public void setVariableSelected(DAPVariable dapVariable, boolean selected) {
listModel.variableToSelected.put(dapVariable, selected);
}
public void stopFiltering() {
filterPreparators.clear();
filterPreparatorsInWait.clear();
listModel.allVariables.clear();
listModel.variableToSelected.clear();
}
private static class VariableListModel implements ListModel<DAPVariable> {
private SortedSet<DAPVariable> allVariables = new TreeSet<>();
private Map<DAPVariable, Boolean> variableToSelected = new HashMap<>();
private Set<ListDataListener> listeners = new HashSet<>();
void addVariables(DAPVariable[] dapVariables) {
allVariables.addAll(Arrays.asList(dapVariables));
for (ListDataListener listener : listeners) {
listener.contentsChanged(new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, 0, getSize() - 1));
}
}
@Override
public int getSize() {
return allVariables.size();
}
@Override
public DAPVariable getElementAt(int index) {
return (DAPVariable) allVariables.toArray()[index];
}
@Override
public void addListDataListener(ListDataListener l) {
listeners.add(l);
}
@Override
public void removeListDataListener(ListDataListener l) {
listeners.remove(l);
}
}
@Override
public void leafAdded(OpendapLeaf leaf, boolean hasNestedDatasets) {
VariableFilterPreparator filterPreparator = new VariableFilterPreparator(leaf);
if (filterPreparators.size() <= MAX_THREAD_COUNT) {
filterPreparators.add(filterPreparator);
filterPreparator.execute();
filterCheckBox.setEnabled(false);
filterCheckBox.setSelected(false);
updateUI(false, false, false);
} else {
filterPreparatorsInWait.add(filterPreparator);
}
totalWork++;
}
@Override
public void catalogElementsInsertionFinished() {
pm.setPreMessage("Scanning variables... ");
pm.setPostMessage("");
pm.beginTask("", (int)totalWork);
pm.worked((int)worked);
}
private class VariableFilterPreparator extends SwingWorker<DAPVariable[], Void> {
private OpendapLeaf leaf;
private VariableFilterPreparator(OpendapLeaf leaf) {
this.leaf = leaf;
}
@Override
protected DAPVariable[] doInBackground() throws Exception {
DAPVariable[] leafVariables = collector.collectDAPVariables(leaf);
leaf.addDAPVariables(leafVariables);
return leafVariables;
}
@Override
protected void done() {
try {
DAPVariable[] dapVariables = get();
listModel.addVariables(dapVariables);
} catch (Exception e) {
BeamLogManager.getSystemLogger().warning(
"Stopping to scan for variables due to exception: " + e.getMessage());
} finally {
filterPreparators.remove(this);
pm.worked(1);
worked++;
if (!filterPreparatorsInWait.isEmpty()) {
VariableFilterPreparator nextFilterPreparator = filterPreparatorsInWait.remove(0);
filterPreparators.add(nextFilterPreparator);
nextFilterPreparator.execute();
}
int percentage = (int) ((worked / totalWork) * 100);
pm.setTaskName(percentage + " %");
if (filterPreparators.isEmpty()) {
updateUI(true, true, true);
filterCheckBox.setEnabled(true);
pm.done();
worked = 0;
totalWork = 0;
}
}
}
}
private static class VariableFilterProgressBarProgressMonitor extends ProgressBarProgressMonitor implements LabelledProgressBarPM {
private final JProgressBar progressBar;
private final JLabel preMessageLabel;
private final JLabel postMessageLabel;
public VariableFilterProgressBarProgressMonitor(JProgressBar progressBar, JLabel preMessageLabel, JLabel postMessageLabel) {
super(progressBar, postMessageLabel);
this.progressBar = progressBar;
this.preMessageLabel = preMessageLabel;
this.postMessageLabel = postMessageLabel;
}
@Override
protected void setDescription(String description) {
}
@Override
public void setVisibility(boolean visible) {
progressBar.setVisible(visible);
preMessageLabel.setVisible(visible);
postMessageLabel.setVisible(visible);
}
@Override
protected void setRunning() {
}
@Override
protected void finish() {
}
@Override
public void setPreMessage(String preMessageText) {
preMessageLabel.setText(preMessageText);
}
@Override
public void setPostMessage(String postMessageText) {
setTaskName(postMessageText);
}
@Override
public int getTotalWork() {
throw new IllegalStateException("not implemented");
}
@Override
public int getCurrentWork() {
throw new IllegalStateException("not implemented");
}
@Override
public void setTooltip(String tooltip) {
}
}
private class ToolTippedCheckBoxList extends CheckBoxList {
public ToolTippedCheckBoxList(ListModel displayListModel) {
super(displayListModel);
}
@Override
public String getToolTipText(MouseEvent event) {
int index = locationToIndex(event.getPoint());
DAPVariable item = (DAPVariable) getModel().getElementAt(index);
return item.getInfotext();
}
}
private class FilterDocumentListener implements DocumentListener {
@Override
public void insertUpdate(DocumentEvent e) {
updateFilter(getFilterText(e));
}
@Override
public void removeUpdate(DocumentEvent e) {
updateFilter(getFilterText(e));
}
@Override
public void changedUpdate(DocumentEvent e) {
}
private void updateFilter(String text) {
filteredListModel.setFilter(element -> element.getName().contains(text.trim()));
}
private String getFilterText(DocumentEvent e) {
Document document = e.getDocument();
String text = null;
try {
text = document.getText(0, document.getLength());
} catch (BadLocationException e1) {
LOG.severe(e1.getMessage());
}
return text;
}
}
}