package org.esa.snap.ui.product; import com.jidesoft.swing.CheckBoxTree; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.datamodel.TiePointGrid; import org.esa.snap.ui.GridBagUtils; import javax.swing.JCheckBox; import javax.swing.JPanel; import javax.swing.event.TreeSelectionEvent; import javax.swing.event.TreeSelectionListener; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultTreeCellRenderer; import javax.swing.tree.DefaultTreeModel; import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; import java.awt.Color; import java.awt.Font; import java.awt.GridBagConstraints; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; public class GroupedBandChoosingStrategy implements BandChoosingStrategy { // @todo 3 nf/se - see ProductSubsetDialog for a similar declarations (code smell!) private static final Font SMALL_PLAIN_FONT = new Font("SansSerif", Font.PLAIN, 10); private static final Font SMALL_ITALIC_FONT = SMALL_PLAIN_FONT.deriveFont(Font.ITALIC); private JCheckBox selectAllCheckBox; private JCheckBox selectNoneCheckBox; private boolean multipleProducts; private Product.AutoGrouping autoGrouping; private CheckBoxTree checkBoxTree; private final Map<Band, String> allBandsMap; private final Map<Band, String> selectedBandsMap; private final Map<TiePointGrid, String> allGridsMap; private final Map<TiePointGrid, String> selectedGridsMap; public GroupedBandChoosingStrategy(Band[] allBands, Band[] selectedBands, TiePointGrid[] allTiePointGrids, TiePointGrid[] selectedTiePointGrids, Product.AutoGrouping autoGrouping, boolean multipleProducts) { allBandsMap = createBandMap(allBands); selectedBandsMap = createBandMap(selectedBands); allGridsMap = createTiepointGridMap(allTiePointGrids); selectedGridsMap = createTiepointGridMap(selectedTiePointGrids); this.autoGrouping = autoGrouping; this.multipleProducts = multipleProducts; } private Map<Band, String> createBandMap(Band[] bands) { final Map<Band, String> bandMap = new TreeMap<>(BandSorter.createComparator()); if (bands != null) { for (Band band : bands) { bandMap.put(band, getDisplayDescription(band)); } } return bandMap; } private Map<TiePointGrid, String> createTiepointGridMap(TiePointGrid[] grids) { final Map<TiePointGrid, String> gridMap = new TreeMap<>(new Comparator<TiePointGrid>() { @Override public int compare(TiePointGrid grid1, TiePointGrid grid2) { return grid1.getName().compareTo(grid2.getName()); } }); if (grids != null) { for (TiePointGrid grid : grids) { gridMap.put(grid, getDisplayDescription(grid)); } } return gridMap; } private String getDisplayDescription(RasterDataNode rasterDataNode) { final String fullName = multipleProducts ? rasterDataNode.getDisplayName() : rasterDataNode.getName(); final StringBuilder description = new StringBuilder(); description.setLength(0); description.append(fullName); description.append(rasterDataNode.getDescription() == null ? "" : " (" + rasterDataNode.getDescription()); if (rasterDataNode instanceof Band) { if (((Band) rasterDataNode).getSpectralWavelength() > 0.0) { description.append(" ("); description.append(((Band) rasterDataNode).getSpectralWavelength()); description.append(" nm)"); } } description.append(")"); return description.toString(); } @Override public Band[] getSelectedBands() { List<Band> selectedBandList = new ArrayList<>(); final TreePath[] selectionPaths = checkBoxTree.getCheckBoxTreeSelectionModel().getSelectionPaths(); TreePath rootPath = new TreePath(checkBoxTree.getModel().getRoot()); for (TreePath selectionPath : selectionPaths) { if (selectionPath.equals(rootPath)) { return allBandsMap.keySet().toArray(new Band[allBandsMap.size()]); } final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); if (selectedNode.isLeaf()) { RasterDataNode key = getKey(selectedNode.getUserObject().toString(), allBandsMap); if (key != null) { selectedBandList.add((Band) key); } } else { for (int i = 0; i < selectedNode.getChildCount(); i++) { final DefaultMutableTreeNode child = (DefaultMutableTreeNode) selectedNode.getChildAt(i); RasterDataNode key = getKey(child.getUserObject().toString(), allBandsMap); if (key != null) { selectedBandList.add((Band) key); } } } } return selectedBandList.toArray(new Band[selectedBandList.size()]); } private RasterDataNode getKey(String value, Map<? extends RasterDataNode, String> map) { for (Map.Entry<? extends RasterDataNode, String> entry : map.entrySet()) { if (entry.getValue().equals(value)) { return entry.getKey(); } } return null; } @Override public TiePointGrid[] getSelectedTiePointGrids() { List<TiePointGrid> selectedGridList = new ArrayList<>(); final TreePath[] selectionPaths = checkBoxTree.getCheckBoxTreeSelectionModel().getSelectionPaths(); TreePath rootPath = new TreePath(checkBoxTree.getModel().getRoot()); for (TreePath selectionPath : selectionPaths) { if (selectionPath.equals(rootPath)) { return allGridsMap.keySet().toArray(new TiePointGrid[allGridsMap.size()]); } final DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) selectionPath.getLastPathComponent(); if (selectedNode.isLeaf()) { RasterDataNode key = getKey(selectedNode.getUserObject().toString(), allGridsMap); if (key != null) { selectedGridList.add((TiePointGrid) key); } } else { for (int i = 0; i < selectedNode.getChildCount(); i++) { final DefaultMutableTreeNode child = (DefaultMutableTreeNode) selectedNode.getChildAt(i); RasterDataNode key = getKey(child.getUserObject().toString(), allGridsMap); if (key != null) { selectedGridList.add((TiePointGrid) key); } selectedGridList.add((TiePointGrid) key); } } } return selectedGridList.toArray(new TiePointGrid[selectedGridList.size()]); } public JPanel createCheckersPane() { DefaultMutableTreeNode root = new DefaultMutableTreeNode(); Map<String, Integer> groupNodeMap = initGrouping(root); List<TreePath> selectedPaths = new ArrayList<>(); addBandCheckBoxes(root, selectedPaths, groupNodeMap); addTiePointGridCheckBoxes(root, selectedPaths, groupNodeMap); removeEmptyGroups(root, groupNodeMap); TreeModel treeModel = new DefaultTreeModel(root); checkBoxTree = new CheckBoxTree(treeModel); checkBoxTree.getCheckBoxTreeSelectionModel().setSelectionPaths(selectedPaths.toArray(new TreePath[selectedPaths.size()])); checkBoxTree.setRootVisible(false); checkBoxTree.setShowsRootHandles(true); checkBoxTree.getCheckBoxTreeSelectionModel().addTreeSelectionListener(new TreeSelectionListener() { @Override public void valueChanged(TreeSelectionEvent e) { updateCheckBoxStates(); } }); final DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer) checkBoxTree.getActualCellRenderer(); renderer.setFont(SMALL_ITALIC_FONT); renderer.setLeafIcon(null); renderer.setOpenIcon(null); renderer.setClosedIcon(null); Color color = new Color(240, 240, 240); checkBoxTree.setBackground(color); renderer.setBackgroundSelectionColor(color); renderer.setBackgroundNonSelectionColor(color); renderer.setBorderSelectionColor(color); renderer.setTextSelectionColor(Color.BLACK); GridBagConstraints gbc2 = GridBagUtils.createConstraints("insets.left=4,anchor=WEST,fill=BOTH"); final JPanel checkersPane = GridBagUtils.createPanel(); GridBagUtils.addToPanel(checkersPane, checkBoxTree, gbc2, "weightx=1.0,weighty=1.0"); return checkersPane; } private Map<String, Integer> initGrouping(DefaultMutableTreeNode root) { Map<String, Integer> groupNodeMap = new HashMap<>(); if (autoGrouping != null) { for (String[] groupNames : autoGrouping) { final String groupName = groupNames[0]; if (!hasChild(root, groupName)) { DefaultMutableTreeNode groupNode = new DefaultMutableTreeNode(groupName); groupNodeMap.put(groupNode.getUserObject().toString(), root.getChildCount()); root.add(groupNode); } } } return groupNodeMap; } private void removeEmptyGroups(DefaultMutableTreeNode root, Map<String, Integer> groupNodeMap) { DefaultMutableTreeNode rootChild = (DefaultMutableTreeNode) root.getFirstChild(); while (rootChild != null) { DefaultMutableTreeNode nextChild = rootChild.getNextSibling(); if (rootChild.getChildCount() == 0 && groupNodeMap.containsKey(rootChild.getUserObject().toString())) { root.remove(rootChild); } rootChild = nextChild; } } private void addBandCheckBoxes(DefaultMutableTreeNode root, List<TreePath> selectedPaths, Map<String, Integer> groupNodeMap) { final Set<Map.Entry<Band, String>> allBands = allBandsMap.entrySet(); for (Map.Entry<Band, String> bandEntry : allBands) { final Band band = bandEntry.getKey(); if (autoGrouping != null) { final int bandIndex = autoGrouping.indexOf(band.getName()); if (bandIndex >= 0) { final String groupName = autoGrouping.get(bandIndex)[0]; final Integer index = groupNodeMap.get(groupName); final DefaultMutableTreeNode groupNode = (DefaultMutableTreeNode) root.getChildAt(index); final DefaultMutableTreeNode groupChild = new DefaultMutableTreeNode(bandEntry.getValue()); if (selectedBandsMap.containsValue(bandEntry.getValue())) { selectedPaths.add(new TreePath(new Object[]{root, groupNode, groupChild})); } groupNode.add(groupChild); } else { addToRoot(root, selectedPaths, bandEntry, band); } } else { addToRoot(root, selectedPaths, bandEntry, band); } } } private void addTiePointGridCheckBoxes(DefaultMutableTreeNode root, List<TreePath> selectedPaths, Map<String, Integer> groupNodeMap) { final Set<Map.Entry<TiePointGrid, String>> allGrids = allGridsMap.entrySet(); for (Map.Entry<TiePointGrid, String> gridEntry : allGrids) { final TiePointGrid grid = gridEntry.getKey(); if (autoGrouping != null) { final int gridIndex = autoGrouping.indexOf(grid.getName()); if (gridIndex >= 0) { final String groupName = autoGrouping.get(gridIndex)[0]; final Integer index = groupNodeMap.get(groupName); final DefaultMutableTreeNode groupNode = (DefaultMutableTreeNode) root.getChildAt(index); final DefaultMutableTreeNode groupChild = new DefaultMutableTreeNode(gridEntry.getValue()); if (selectedGridsMap.containsKey(grid)) { selectedPaths.add(new TreePath(new Object[]{root, groupNode, groupChild})); } groupNode.add(groupChild); } else { addToRoot(root, selectedPaths, gridEntry, grid); } } else { addToRoot(root, selectedPaths, gridEntry, grid); } } } private void addToRoot(DefaultMutableTreeNode root, List<TreePath> selectedPaths, Map.Entry<Band, String> bandEntry, Band band) { final DefaultMutableTreeNode rootChild = new DefaultMutableTreeNode(bandEntry.getValue()); if (selectedBandsMap.containsKey(band)) { selectedPaths.add(new TreePath(new Object[]{root, rootChild})); } root.add(rootChild); } private void addToRoot(DefaultMutableTreeNode root, List<TreePath> selectedPaths, Map.Entry<TiePointGrid, String> gridEntry, TiePointGrid grid) { final DefaultMutableTreeNode rootChild = new DefaultMutableTreeNode(gridEntry.getValue()); if (selectedGridsMap.containsKey(grid)) { selectedPaths.add(new TreePath(new Object[]{root, rootChild})); } root.add(rootChild); } public void updateCheckBoxStates() { final TreePath[] selectionPaths = checkBoxTree.getCheckBoxTreeSelectionModel().getSelectionPaths(); if (selectionPaths == null || selectionPaths.length == 0) { selectAllCheckBox.setSelected(false); selectAllCheckBox.setEnabled(true); selectAllCheckBox.updateUI(); selectNoneCheckBox.setSelected(true); selectNoneCheckBox.setEnabled(false); selectNoneCheckBox.updateUI(); } else { final TreePath rootPath = new TreePath(checkBoxTree.getModel().getRoot()); boolean allSelected = false; for (TreePath selectionPath : selectionPaths) { if (selectionPath.equals(rootPath)) { allSelected = true; } selectAllCheckBox.setSelected(allSelected); selectAllCheckBox.setEnabled(!allSelected); selectAllCheckBox.updateUI(); selectNoneCheckBox.setSelected(false); selectNoneCheckBox.setEnabled(true); selectNoneCheckBox.updateUI(); } } } private boolean hasChild(DefaultMutableTreeNode node, String groupName) { for (int i = 0; i < node.getChildCount(); i++) { if (((DefaultMutableTreeNode) node.getChildAt(i)).getUserObject().equals(groupName)) { return true; } } return false; } @Override public void setCheckBoxes(JCheckBox selectAllCheckBox, JCheckBox selectNoneCheckBox) { this.selectAllCheckBox = selectAllCheckBox; this.selectNoneCheckBox = selectNoneCheckBox; updateCheckBoxStates(); } @Override public void selectAll() { checkBoxTree.getCheckBoxTreeSelectionModel().setSelectionPath(new TreePath(checkBoxTree.getModel().getRoot())); } @Override public void selectNone() { checkBoxTree.getCheckBoxTreeSelectionModel().clearSelection(); } @Override public boolean atLeastOneBandSelected() { return checkBoxTree.getCheckBoxTreeSelectionModel().getSelectionPaths() != null; } @Override public void selectRasterDataNodes(String[] nodeNames) { DefaultMutableTreeNode rootNode = (DefaultMutableTreeNode) checkBoxTree.getModel().getRoot(); selectRasterDataNodes(rootNode, nodeNames); } private void selectRasterDataNodes(DefaultMutableTreeNode node, String[] nodeNames) { int childCount = node.getChildCount(); if(childCount != 0) { for(int i = 0; i < childCount; i++) { selectRasterDataNodes((DefaultMutableTreeNode)node.getChildAt(i), nodeNames); } } else { for (String nodeName : nodeNames) { if (nodeName.equals(((String) node.getUserObject()).split(" ")[0].trim())) { List<TreeNode> pathList = new ArrayList<>(); TreeNode currentNode = node; while(currentNode != null) { pathList.add(0, currentNode); currentNode = currentNode.getParent(); } TreePath path = new TreePath(pathList.toArray(new TreeNode[pathList.size()])); checkBoxTree.getCheckBoxTreeSelectionModel().addSelectionPath(path); } } } } }