/* * Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de) * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.rcp.colormanip; import com.bc.ceres.core.Assert; import com.bc.ceres.core.ProgressMonitor; import org.esa.snap.core.datamodel.Band; import org.esa.snap.core.datamodel.ColorPaletteDef; import org.esa.snap.core.datamodel.ImageInfo; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.datamodel.ProductManager; import org.esa.snap.core.datamodel.ProductNode; import org.esa.snap.core.datamodel.ProductNodeEvent; import org.esa.snap.core.datamodel.ProductNodeListener; import org.esa.snap.core.datamodel.ProductNodeListenerAdapter; import org.esa.snap.core.datamodel.RasterDataNode; import org.esa.snap.core.datamodel.Stx; import org.esa.snap.core.util.ProductUtils; import org.esa.snap.core.util.ResourceInstaller; import org.esa.snap.core.util.SystemUtils; import org.esa.snap.core.util.io.FileUtils; import org.esa.snap.core.util.io.SnapFileFilter; import org.esa.snap.netbeans.docwin.WindowUtilities; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.util.Dialogs; import org.esa.snap.rcp.util.SelectionSupport; import org.esa.snap.rcp.windows.ProductSceneViewTopComponent; import org.esa.snap.runtime.Config; import org.esa.snap.ui.AbstractDialog; import org.esa.snap.ui.GridBagUtils; import org.esa.snap.ui.SnapFileChooser; import org.esa.snap.ui.product.BandChooser; import org.esa.snap.ui.product.ProductSceneView; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import javax.swing.*; import java.awt.BorderLayout; import java.awt.Component; import java.awt.Cursor; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.event.ActionListener; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashSet; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * The GUI for the colour manipulation tool window. */ @NbBundle.Messages({ "CTL_ColorManipulationForm_TitlePrefix=Colour Manipulation" }) class ColorManipulationForm implements SelectionSupport.Handler<ProductSceneView> { private final static String PREFERENCES_KEY_IO_DIR = "snap.color_palettes.dir"; private final static String FILE_EXTENSION = ".cpd"; private AbstractButton resetButton; private AbstractButton multiApplyButton; private AbstractButton importButton; private AbstractButton exportButton; private final TopComponent toolView; private final FormModel formModel; private Band[] bandsToBeModified; private SnapFileFilter snapFileFilter; private final ProductNodeListener productNodeListener; private boolean defaultColorPalettesInstalled; private JPanel contentPanel; private ColorManipulationChildForm childForm; private ColorManipulationChildForm continuous1BandSwitcherForm; private ColorManipulationChildForm discrete1BandTabularForm; private ColorManipulationChildForm continuous3BandGraphicalForm; private JPanel toolButtonsPanel; private AbstractButton helpButton; private Path ioDir; private JPanel editorPanel; private MoreOptionsPane moreOptionsPane; private SceneViewImageInfoChangeListener sceneViewChangeListener; private String titlePrefix; private ColorManipulationChildForm emptyForm; private BrightnessContrastPanel brightnessContrastPanel; private JTabbedPane tabbedPane; public ColorManipulationForm(TopComponent colorManipulationToolView, FormModel formModel) { Assert.notNull(colorManipulationToolView); Assert.notNull(formModel); this.toolView = colorManipulationToolView; this.formModel = formModel; productNodeListener = new ColorManipulationPNL(); sceneViewChangeListener = new SceneViewImageInfoChangeListener(); titlePrefix = this.formModel.getTitlePrefix(); emptyForm = new EmptyImageInfoForm(this); } public FormModel getFormModel() { return formModel; } public JPanel getContentPanel() { if (contentPanel == null) { initContentPanel(); } if (!defaultColorPalettesInstalled) { ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(new InstallDefaultColorPalettes()); } return contentPanel; } public void revalidateToolViewPaneControl() { getToolViewPaneControl().invalidate(); getToolViewPaneControl().validate(); getToolViewPaneControl().repaint(); updateToolButtons(); } private static AbstractButton createButton(final String iconPath) { return ImageInfoEditorSupport.createButton(iconPath); } private Component getToolViewPaneControl() { return toolView; } private void setProductSceneView(final ProductSceneView productSceneView) { ProductSceneView productSceneViewOld = getFormModel().getProductSceneView(); if (productSceneViewOld != null) { Product product = productSceneViewOld.getProduct(); if (product != null) { // Product may already been gone. product.removeProductNodeListener(productNodeListener); } productSceneViewOld.removePropertyChangeListener(sceneViewChangeListener); } getFormModel().setProductSceneView(productSceneView); if (getFormModel().isValid()) { getFormModel().getProductSceneView().getProduct().addProductNodeListener(productNodeListener); getFormModel().getProductSceneView().addPropertyChangeListener(sceneViewChangeListener); } if (getFormModel().isValid()) { getFormModel().setModifiedImageInfo(getFormModel().getOriginalImageInfo()); } installChildForm(); updateTitle(); updateToolButtons(); updateMultiApplyState(); } private void installChildForm() { final ColorManipulationChildForm oldForm = childForm; ColorManipulationChildForm newForm = emptyForm; if (getFormModel().isValid()) { if (getFormModel().isContinuous3BandImage()) { if (oldForm instanceof Continuous3BandGraphicalForm) { newForm = oldForm; } else { newForm = getContinuous3BandGraphicalForm(); } } else if (getFormModel().isContinuous1BandImage()) { if (oldForm instanceof Continuous1BandSwitcherForm) { newForm = oldForm; } else { newForm = getContinuous1BandSwitcherForm(); } } else if (getFormModel().isDiscrete1BandImage()) { if (oldForm instanceof Discrete1BandTabularForm) { newForm = oldForm; } else { newForm = getDiscrete1BandTabularForm(); } } else { if (oldForm instanceof Continuous1BandSwitcherForm) { newForm = oldForm; } else { newForm = getContinuous1BandSwitcherForm(); } } } if (newForm != oldForm) { childForm = newForm; installToolButtons(); installMoreOptions(); editorPanel.removeAll(); editorPanel.add(childForm.getContentPanel(), BorderLayout.CENTER); if (!(childForm instanceof EmptyImageInfoForm)) { editorPanel.add(moreOptionsPane.getContentPanel(), BorderLayout.SOUTH); } revalidateToolViewPaneControl(); if (oldForm != null) { oldForm.handleFormHidden(getFormModel()); } childForm.handleFormShown(getFormModel()); } else { childForm.updateFormModel(getFormModel()); } } private void updateTitle() { String titlePostfix = ""; if (getFormModel().isValid()) { titlePostfix = " - " + getFormModel().getModelName(); } toolView.setDisplayName(titlePrefix + titlePostfix); } private void updateToolButtons() { resetButton.setEnabled(getFormModel().isValid()); importButton.setEnabled(getFormModel().isValid() && !getFormModel().isContinuous3BandImage()); exportButton.setEnabled(getFormModel().isValid() && !getFormModel().isContinuous3BandImage()); } private ColorManipulationChildForm getContinuous3BandGraphicalForm() { if (continuous3BandGraphicalForm == null) { continuous3BandGraphicalForm = new Continuous3BandGraphicalForm(this); } return continuous3BandGraphicalForm; } private ColorManipulationChildForm getContinuous1BandSwitcherForm() { if (continuous1BandSwitcherForm == null) { continuous1BandSwitcherForm = new Continuous1BandSwitcherForm(this); } return continuous1BandSwitcherForm; } private ColorManipulationChildForm getDiscrete1BandTabularForm() { if (discrete1BandTabularForm == null) { discrete1BandTabularForm = new Discrete1BandTabularForm(this); } return discrete1BandTabularForm; } public ActionListener wrapWithAutoApplyActionListener(final ActionListener actionListener) { return e -> { actionListener.actionPerformed(e); applyChanges(); }; } private void initContentPanel() { this.moreOptionsPane = new MoreOptionsPane(this, formModel.isMoreOptionsFormCollapsedOnInit()); this.brightnessContrastPanel = new BrightnessContrastPanel(this); resetButton = createButton("org/esa/snap/rcp/icons/Undo24.gif"); resetButton.setName("ResetButton"); resetButton.setToolTipText("Reset to defaults"); /*I18N*/ resetButton.addActionListener(wrapWithAutoApplyActionListener(e -> resetToDefaults())); multiApplyButton = createButton("org/esa/snap/rcp/icons/MultiAssignBands24.gif"); multiApplyButton.setName("MultiApplyButton"); multiApplyButton.setToolTipText("Apply to other bands"); /*I18N*/ multiApplyButton.addActionListener(e -> applyMultipleColorPaletteDef()); importButton = createButton("tango/22x22/actions/document-open.png"); importButton.setName("ImportButton"); importButton.setToolTipText("Import colour palette from text file."); /*I18N*/ importButton.addActionListener(e -> { importColorPaletteDef(); applyChanges(); }); importButton.setEnabled(true); exportButton = createButton("tango/22x22/actions/document-save-as.png"); exportButton.setName("ExportButton"); exportButton.setToolTipText("Save colour palette to text file."); /*I18N*/ exportButton.addActionListener(e -> { exportColorPaletteDef(); childForm.updateFormModel(getFormModel()); }); exportButton.setEnabled(true); helpButton = createButton("tango/22x22/apps/help-browser.png"); helpButton.setToolTipText("Help."); /*I18N*/ helpButton.setName("helpButton"); helpButton.addActionListener(e -> toolView.getHelpCtx().display()); editorPanel = new JPanel(new BorderLayout(4, 4)); toolButtonsPanel = GridBagUtils.createPanel(); contentPanel = new JPanel(new BorderLayout(4, 4)); contentPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4)); contentPanel.setPreferredSize(new Dimension(320, 200)); contentPanel.add(editorPanel, BorderLayout.CENTER); contentPanel.add(toolButtonsPanel, BorderLayout.EAST); setProductSceneView(SnapApp.getDefault().getSelectedProductSceneView()); SnapApp.getDefault().getSelectionSupport(ProductSceneView.class).addHandler(this); } public void updateMultiApplyState() { multiApplyButton.setEnabled(getFormModel().isValid() && !getFormModel().isContinuous3BandImage()); } void installToolButtons() { toolButtonsPanel.removeAll(); GridBagConstraints gbc = new GridBagConstraints(); gbc.anchor = GridBagConstraints.CENTER; gbc.fill = GridBagConstraints.NONE; gbc.weightx = 1.0; gbc.gridy = 0; gbc.insets.bottom = 0; gbc.gridwidth = 1; gbc.gridy++; toolButtonsPanel.add(resetButton, gbc); toolButtonsPanel.add(multiApplyButton, gbc); gbc.gridy++; toolButtonsPanel.add(importButton, gbc); toolButtonsPanel.add(exportButton, gbc); gbc.gridy++; AbstractButton[] additionalButtons = childForm.getToolButtons(); for (int i = 0; i < additionalButtons.length; i++) { AbstractButton button = additionalButtons[i]; toolButtonsPanel.add(button, gbc); if (i % 2 == 1) { gbc.gridy++; } } gbc.gridy++; gbc.fill = GridBagConstraints.VERTICAL; gbc.weighty = 1.0; gbc.gridwidth = 2; toolButtonsPanel.add(new JLabel(" "), gbc); // filler gbc.fill = GridBagConstraints.NONE; gbc.weighty = 0.0; gbc.gridwidth = 1; gbc.gridy++; gbc.gridx = 1; toolButtonsPanel.add(helpButton, gbc); } void installMoreOptions() { final MoreOptionsForm moreOptionsForm = childForm.getMoreOptionsForm(); if (moreOptionsForm != null) { moreOptionsForm.updateForm(); if (this.tabbedPane == null) { this.tabbedPane = new JTabbedPane(); this.tabbedPane.addTab("Histogram", moreOptionsForm.getContentPanel()); this.tabbedPane.addTab("Brightness/Contrast", this.brightnessContrastPanel); } this.moreOptionsPane.setComponent(this.tabbedPane); } } void applyChanges() { updateMultiApplyState(); if (getFormModel().isValid()) { try { getToolViewPaneControl().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); if (getFormModel().isContinuous3BandImage()) { getFormModel().setRasters(childForm.getRasters()); } else { getFormModel().getRaster().setImageInfo(getFormModel().getModifiedImageInfo()); } getFormModel().applyModifiedImageInfo(); } finally { getToolViewPaneControl().setCursor(Cursor.getDefaultCursor()); } } updateMultiApplyState(); } private void resetToDefaults() { if (getFormModel().isValid()) { getFormModel().setModifiedImageInfo(createDefaultImageInfo()); childForm.resetFormModel(getFormModel()); } } private void applyMultipleColorPaletteDef() { if (!getFormModel().isValid()) { return; } final Product selectedProduct = getFormModel().getProduct(); final ProductManager productManager = selectedProduct.getProductManager(); final RasterDataNode[] protectedRasters = getFormModel().getRasters(); final ArrayList<Band> availableBandList = new ArrayList<>(); for (int i = 0; i < productManager.getProductCount(); i++) { final Product product = productManager.getProduct(i); final Band[] bands = product.getBands(); for (final Band band : bands) { boolean validBand = false; if (band.getImageInfo() != null) { validBand = true; for (RasterDataNode protectedRaster : protectedRasters) { if (band == protectedRaster) { validBand = false; } } } if (validBand) { availableBandList.add(band); } } } final Band[] availableBands = new Band[availableBandList.size()]; availableBandList.toArray(availableBands); availableBandList.clear(); if (availableBands.length == 0) { AbstractDialog.showWarningDialog(getToolViewPaneControl(), "No other bands available.", titlePrefix); return; } final BandChooser bandChooser = new BandChooser(SwingUtilities.getWindowAncestor(toolView), "Apply to other bands", toolView.getHelpCtx().getHelpID(), availableBands, bandsToBeModified, false); final Set<RasterDataNode> modifiedRasters = new HashSet<>(availableBands.length); if (bandChooser.show() == BandChooser.ID_OK) { bandsToBeModified = bandChooser.getSelectedBands(); for (final Band band : bandsToBeModified) { applyColorPaletteDef(getFormModel().getModifiedImageInfo().getColorPaletteDef(), band, band.getImageInfo()); modifiedRasters.add(band); } } // This code replaces visatApp.updateImages(rasters) from BEAM code. WindowUtilities.getOpened(ProductSceneViewTopComponent.class).forEach(tc -> { final ProductSceneView view = tc.getView(); for (RasterDataNode raster : view.getRasters()) { if (modifiedRasters.contains(raster)) { view.updateImage(); return; } } }); } private void setIODir(final File dir) { ioDir = dir.toPath(); Config.instance().preferences().put(PREFERENCES_KEY_IO_DIR, ioDir.toString()); } protected Path getIODir() { if (ioDir == null) { ioDir = Paths.get(Config.instance().preferences().get(PREFERENCES_KEY_IO_DIR, getColorPalettesDir().toString())); } return ioDir; } private SnapFileFilter getOrCreateColorPaletteDefinitionFileFilter() { if (snapFileFilter == null) { final String formatName = "COLOR_PALETTE_DEFINITION_FILE"; final String description = "Colour palette files (*" + FILE_EXTENSION + ")"; /*I18N*/ snapFileFilter = new SnapFileFilter(formatName, FILE_EXTENSION, description); } return snapFileFilter; } private void importColorPaletteDef() { final ImageInfo targetImageInfo = getFormModel().getModifiedImageInfo(); if (targetImageInfo == null) { // Normally this code is unreachable because, the export Button // is disabled if the _contrastStretchPane has no ImageInfo. return; } final SnapFileChooser fileChooser = new SnapFileChooser(); fileChooser.setDialogTitle("Import Colour Palette"); /*I18N*/ fileChooser.setFileFilter(getOrCreateColorPaletteDefinitionFileFilter()); fileChooser.setCurrentDirectory(getIODir().toFile()); final int result = fileChooser.showOpenDialog(getToolViewPaneControl()); final File file = fileChooser.getSelectedFile(); if (file != null && file.getParentFile() != null) { setIODir(file.getParentFile()); } if (result == JFileChooser.APPROVE_OPTION) { if (file != null && file.canRead()) { try { final ColorPaletteDef colorPaletteDef = ColorPaletteDef.loadColorPaletteDef(file); colorPaletteDef.getFirstPoint().setLabel(file.getName()); applyColorPaletteDef(colorPaletteDef, getFormModel().getRaster(), targetImageInfo); getFormModel().setModifiedImageInfo(targetImageInfo); childForm.updateFormModel(getFormModel()); updateMultiApplyState(); } catch (IOException e) { showErrorDialog("Failed to import colour palette:\n" + e.getMessage()); } } } } private void applyColorPaletteDef(ColorPaletteDef colorPaletteDef, RasterDataNode targetRaster, ImageInfo targetImageInfo) { if (isIndexCoded(targetRaster)) { targetImageInfo.setColors(colorPaletteDef.getColors()); } else { Stx stx = targetRaster.getStx(false, ProgressMonitor.NULL); Boolean autoDistribute = getAutoDistribute(colorPaletteDef); if (autoDistribute == null) { return; } targetImageInfo.setColorPaletteDef(colorPaletteDef, stx.getMinimum(), stx.getMaximum(), autoDistribute); } } private Boolean getAutoDistribute(ColorPaletteDef colorPaletteDef) { if (colorPaletteDef.isAutoDistribute()) { return Boolean.TRUE; } int answer = JOptionPane.showConfirmDialog(getToolViewPaneControl(), "Automatically distribute points of\n" + "colour palette between min/max?", "Import Colour Palette", JOptionPane.YES_NO_CANCEL_OPTION ); if (answer == JOptionPane.YES_OPTION) { return Boolean.TRUE; } else if (answer == JOptionPane.NO_OPTION) { return Boolean.FALSE; } else { return null; } } private boolean isIndexCoded(RasterDataNode targetRaster) { return targetRaster instanceof Band && ((Band) targetRaster).getIndexCoding() != null; } private void exportColorPaletteDef() { final ImageInfo imageInfo = getFormModel().getModifiedImageInfo(); if (imageInfo == null) { // Normally this code is unreachable because, the export Button should be // disabled if the color manipulation form has no ImageInfo. return; } final SnapFileChooser fileChooser = new SnapFileChooser(); fileChooser.setDialogTitle("Export Colour Palette"); /*I18N*/ fileChooser.setFileFilter(getOrCreateColorPaletteDefinitionFileFilter()); fileChooser.setCurrentDirectory(getIODir().toFile()); final int result = fileChooser.showSaveDialog(getToolViewPaneControl()); File file = fileChooser.getSelectedFile(); if (file != null && file.getParentFile() != null) { setIODir(file.getParentFile()); } if (result == JFileChooser.APPROVE_OPTION) { if (file != null) { if (Boolean.TRUE.equals(Dialogs.requestOverwriteDecision(titlePrefix, file))) { file = FileUtils.ensureExtension(file, FILE_EXTENSION); try { final ColorPaletteDef colorPaletteDef = imageInfo.getColorPaletteDef(); ColorPaletteDef.storeColorPaletteDef(colorPaletteDef, file); } catch (IOException e) { showErrorDialog("Failed to export colour palette:\n" + e.getMessage()); /*I18N*/ } } } } } private void showErrorDialog(final String message) { if (message != null && message.trim().length() > 0) { if (SnapApp.getDefault() != null) { Dialogs.showError(message); } else { Dialogs.showError("Error", message); } } } private class InstallDefaultColorPalettes implements Runnable { private InstallDefaultColorPalettes() { } @Override public void run() { try { Path sourceBasePath = ResourceInstaller.findModuleCodeBasePath(GridBagUtils.class); Path auxdataDir = getColorPalettesDir(); Path sourceDirPath = sourceBasePath.resolve("auxdata/color_palettes"); final ResourceInstaller resourceInstaller = new ResourceInstaller(sourceDirPath, auxdataDir); resourceInstaller.install(".*.cpd", ProgressMonitor.NULL); defaultColorPalettesInstalled = true; } catch (IOException e) { SnapApp.getDefault().handleError("Unable to install colour palettes", e); } } } private Path getColorPalettesDir() { return SystemUtils.getAuxDataPath().resolve("color_palettes"); } private ImageInfo createDefaultImageInfo() { try { return ProductUtils.createImageInfo(getFormModel().getRasters(), false, ProgressMonitor.NULL); } catch (IOException e) { JOptionPane.showMessageDialog(getContentPanel(), "Failed to create default image settings:\n" + e.getMessage(), "I/O Error", JOptionPane.ERROR_MESSAGE); return null; } } Stx getStx(RasterDataNode raster) { return raster.getStx(false, ProgressMonitor.NULL); } private class ColorManipulationPNL extends ProductNodeListenerAdapter { @Override public void nodeChanged(final ProductNodeEvent event) { final RasterDataNode[] rasters = childForm.getRasters(); RasterDataNode raster = null; for (RasterDataNode dataNode : rasters) { if (event.getSourceNode() == dataNode) { raster = (RasterDataNode) event.getSourceNode(); } } if (raster != null) { final String propertyName = event.getPropertyName(); if (ProductNode.PROPERTY_NAME_NAME.equalsIgnoreCase(propertyName)) { updateTitle(); childForm.handleRasterPropertyChange(event, raster); } else if (RasterDataNode.PROPERTY_NAME_ANCILLARY_VARIABLES.equalsIgnoreCase(propertyName)) { updateTitle(); childForm.handleRasterPropertyChange(event, raster); } else if (RasterDataNode.PROPERTY_NAME_UNIT.equalsIgnoreCase(propertyName)) { childForm.handleRasterPropertyChange(event, raster); } else if (RasterDataNode.PROPERTY_NAME_STX.equalsIgnoreCase(propertyName)) { childForm.handleRasterPropertyChange(event, raster); } else if (RasterDataNode.isValidMaskProperty(propertyName)) { getStx(raster); } } } } @Override public void selectionChange(ProductSceneView oldValue, ProductSceneView newValue) { if (getFormModel().getProductSceneView() == oldValue) { setProductSceneView(null); } setProductSceneView(newValue); if (this.brightnessContrastPanel != null) { if (oldValue != null) { this.brightnessContrastPanel.productSceneViewDeselected(oldValue); } if (newValue != null) { this.brightnessContrastPanel.productSceneViewSelected(newValue); } } } private class SceneViewImageInfoChangeListener implements PropertyChangeListener { public SceneViewImageInfoChangeListener() { } @Override public void propertyChange(PropertyChangeEvent evt) { if (ProductSceneView.PROPERTY_NAME_IMAGE_INFO.equals(evt.getPropertyName())) { boolean correctFormForRaster = (getFormModel().getRaster() == getFormModel().getProductSceneView().getRaster()); if (correctFormForRaster) { ImageInfo modifiedImageInfo = (ImageInfo) evt.getNewValue(); getFormModel().setModifiedImageInfo(modifiedImageInfo); childForm.updateFormModel(getFormModel()); } } } } }