/* * Copyright (C) 2016 by Array Systems Computing Inc. http://www.array.ca * * 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.quicklooks; import com.bc.ceres.core.ProgressMonitor; import com.bc.ceres.core.SubProgressMonitor; import com.bc.ceres.swing.progress.ProgressMonitorSwingWorker; import net.coobird.thumbnailator.makers.FixedSizeThumbnailMaker; 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.quicklooks.Quicklook; import org.esa.snap.core.datamodel.quicklooks.Thumbnail; import org.esa.snap.core.dataop.downloadable.StatusProgressMonitor; import org.esa.snap.core.util.SystemUtils; import org.esa.snap.engine_utilities.gpf.ThreadManager; import org.esa.snap.engine_utilities.util.MemUtils; import org.esa.snap.rcp.SnapApp; import org.esa.snap.rcp.actions.window.OpenRGBImageViewAction; import org.esa.snap.rcp.util.SelectionSupport; import org.esa.snap.tango.TangoIcons; import org.esa.snap.ui.UIUtils; import org.esa.snap.ui.tool.ToolButtonFactory; import org.netbeans.api.annotations.common.NullAllowed; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; import org.openide.awt.ActionReferences; import org.openide.util.NbBundle; import org.openide.windows.TopComponent; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; @TopComponent.Description( preferredID = "QuicklookToolView", iconBase = "org/esa/snap/rcp/icons/quicklook.png", persistenceType = TopComponent.PERSISTENCE_ALWAYS ) @TopComponent.Registration( mode = "navigator", openAtStartup = false, position = 1 ) @ActionID(category = "Window", id = "org.esa.snap.rcp.quicklooks.QuicklookToolView") @ActionReferences({ @ActionReference(path = "Menu/View/Tool Windows"), @ActionReference(path = "Toolbars/Tool Windows") }) @TopComponent.OpenActionRegistration( displayName = "#CTL_QuicklookToolView_Name", preferredID = "QuicklookToolView" ) @NbBundle.Messages({ "CTL_QuicklookToolView_Name=Quicklooks", "CTL_QuicklookToolView_Description=Quicklooks of all bands", }) /** * Tool window to display quicklooks */ public class QuicklookToolView extends TopComponent implements Thumbnail.ThumbnailListener { private Product currentProduct; private final SortedSet<Product> productSet; private final SortedSet<String> quicklookNameSet = new TreeSet<>(); private final JComboBox<String> quicklookNameCombo = new JComboBox<>(); private final JLabel nameLabel = new JLabel(); private final ImagePanel imgPanel = new ImagePanel(); private final BufferedImage noDataImage; private JScrollPane imgScrollPanel; private JButton nextBtn, prevBtn, startBtn, endBtn, openBtn, closeBtn, refreshBtn, viewBtn; private ButtonActionListener actionListener = new ButtonActionListener(); private boolean updateQuicklooks = false; private ProductNode oldNode = null; private int zoom = 1; private static final String DEFAULT_QUICKLOOK = "Default"; private static final ImageIcon openIcon = TangoIcons.actions_document_open(TangoIcons.Res.R22); private static final ImageIcon closeIcon = TangoIcons.actions_list_remove(TangoIcons.Res.R22); private static final ImageIcon firstIcon = TangoIcons.actions_go_first(TangoIcons.Res.R22); private static final ImageIcon lastIcon = TangoIcons.actions_go_last(TangoIcons.Res.R22); private static final ImageIcon nextIcon = TangoIcons.actions_go_next(TangoIcons.Res.R22); private static final ImageIcon previousIcon = TangoIcons.actions_go_previous(TangoIcons.Res.R22); private static final ImageIcon refreshIcon = TangoIcons.actions_view_refresh(TangoIcons.Res.R22); private static final ImageIcon singleViewIcon = UIUtils.loadImageIcon("/org/esa/snap/rcp/icons/view_single24.png", ThumbnailPanel.class); private static final ImageIcon thumbnailViewIcon = UIUtils.loadImageIcon("/org/esa/snap/rcp/icons/view_thumbnails24.png", ThumbnailPanel.class); public QuicklookToolView() { setLayout(new BorderLayout()); setDisplayName(Bundle.CTL_QuicklookToolView_Name()); setToolTipText(Bundle.CTL_QuicklookToolView_Description()); add(createPanel(), BorderLayout.CENTER); noDataImage = createNoDataImage(); final SnapApp snapApp = SnapApp.getDefault(); snapApp.getProductManager().addListener(new ProductManagerListener()); productSet = new TreeSet<>(new Comparator<Product>() { public int compare(Product p1, Product p2) { int ref1 = p1.getRefNo(); int ref2 = p2.getRefNo(); return ref1 < ref2 ? -1 : ref1 == ref2 ? 0 : 1; } }); quicklookNameSet.add(DEFAULT_QUICKLOOK); addProducts(); updateButtons(); quicklookNameCombo.setSelectedItem(DEFAULT_QUICKLOOK); quicklookNameCombo.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { showProduct(currentProduct); } }); snapApp.getSelectionSupport(ProductNode.class).addHandler(new SelectionSupport.Handler<ProductNode>() { @Override public void selectionChange(@NullAllowed ProductNode oldValue, @NullAllowed ProductNode newValue) { if (newValue != null && newValue != oldNode) { showProduct(newValue.getProduct()); oldNode = newValue; } } }); } public JComponent createPanel() { final JPanel panel = new JPanel(new BorderLayout()); panel.add(createTopPanel(), BorderLayout.NORTH); panel.add(createSidePanel(), BorderLayout.EAST); panel.add(createImagePanel(), BorderLayout.CENTER); panel.add(createButtonPanel(), BorderLayout.SOUTH); return panel; } private JPanel createTopPanel() { final JPanel topPanel = new JPanel(new BorderLayout()); topPanel.add(nameLabel, BorderLayout.CENTER); topPanel.add(quicklookNameCombo, BorderLayout.EAST); return topPanel; } private JPanel createSidePanel() { final JPanel sidePanel = new JPanel(); sidePanel.setLayout(new BoxLayout(sidePanel, BoxLayout.Y_AXIS)); // viewBtn = createButton("viewButton", "Change View", sidePanel, actionListener, thumbnailViewIcon); // viewBtn.addActionListener(new ActionListener() { // public synchronized void actionPerformed(final ActionEvent e) { // // } // }); // sidePanel.add(viewBtn); openBtn = createButton("Open", "Open RGB", sidePanel, actionListener, openIcon); sidePanel.add(openBtn); closeBtn = createButton("Close", "Close Product", sidePanel, actionListener, closeIcon); sidePanel.add(closeBtn); return sidePanel; } private JScrollPane createImagePanel() { imgScrollPanel = new JScrollPane(imgPanel); imgPanel.setComponentPopupMenu(createImagePopup()); return imgScrollPanel; } private JPanel createButtonPanel() { final JPanel buttonPanel = new JPanel(); startBtn = createButton("Start", "Go to first product", buttonPanel, actionListener, firstIcon); prevBtn = createButton("Prev", "Previous product", buttonPanel, actionListener, previousIcon); nextBtn = createButton("Next", "Next product", buttonPanel, actionListener, nextIcon); endBtn = createButton("End", "Go to last product", buttonPanel, actionListener, lastIcon); refreshBtn = createButton("Refresh", "Update products", buttonPanel, actionListener, refreshIcon); buttonPanel.add(startBtn); buttonPanel.add(prevBtn); buttonPanel.add(nextBtn); buttonPanel.add(endBtn); buttonPanel.add(refreshBtn); return buttonPanel; } private static JButton createButton(final String name, final String text, final JPanel panel, final ButtonActionListener actionListener, final ImageIcon icon) { final JButton btn = (JButton) ToolButtonFactory.createButton(icon, false); btn.setName(name); btn.setIcon(icon); if (panel != null) { btn.setBackground(panel.getBackground()); } btn.setToolTipText(text); btn.setActionCommand(name); btn.addActionListener(actionListener); return btn; } private void updateButtons() { boolean hasProducts = !productSet.isEmpty(); boolean hasPrevProd = getPreviousProduct() != null; boolean hasNextProd = getNextProduct() != null; startBtn.setEnabled(hasPrevProd); prevBtn.setEnabled(hasPrevProd); nextBtn.setEnabled(hasNextProd); endBtn.setEnabled(hasNextProd); openBtn.setEnabled(hasProducts); closeBtn.setEnabled(hasProducts); refreshBtn.setEnabled(hasProducts); if(!hasProducts) { imgPanel.setImage(null); nameLabel.setText(""); currentProduct = null; } } private static BufferedImage createNoDataImage() { final int w = 100, h = 100; final BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); final Graphics2D g = image.createGraphics(); g.addRenderingHints(new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g.addRenderingHints(new RenderingHints( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); g.addRenderingHints(new RenderingHints( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)); g.setColor(Color.DARK_GRAY); g.setStroke(new BasicStroke(1)); g.drawLine(0, 0, 0 + w, h); g.drawLine(0 + w, 0, 0, h); g.drawRect(0, 0, w - 1, h - 1); return image; } private synchronized void loadProducts(final String qlName) { try { final ThreadManager threadManager = new ThreadManager(); threadManager.setNumConsecutiveThreads(Math.min(threadManager.getNumConsecutiveThreads(), 4)); // collect quicklooks that need to load final List<Quicklook> quicklooksToLoad = new ArrayList<>(); for (final Product product : productSet) { if(product.getFileLocation() != null) { final Quicklook quicklook; if (qlName.equals(DEFAULT_QUICKLOOK)) { quicklook = product.getDefaultQuicklook(); } else { quicklook = product.getQuicklook(qlName); } if (quicklook != null && !quicklook.hasImage() && !quicklook.hasCachedImage()) { quicklooksToLoad.add(quicklook); } } } if(quicklooksToLoad.isEmpty()) { updateQuicklooks = true; showProduct(currentProduct); return; } ProgressMonitorSwingWorker<Boolean, Object> worker = new ProgressMonitorSwingWorker<Boolean, Object> (SnapApp.getDefault().getMainFrame(), "Loading quicklooks") { @Override protected Boolean doInBackground(com.bc.ceres.core.ProgressMonitor pm) throws Exception { final int total = productSet.size(); pm.beginTask("Generating quicklooks", total); int cnt = 1; for (final Quicklook quicklook : quicklooksToLoad) { if(pm.isCanceled()) break; final Thread worker = new Thread() { @Override public void run() { try { quicklook.getImage(SubProgressMonitor.create(pm, 1)); } catch (Throwable e) { SystemUtils.LOG.warning("Unable to create quicklook for " + quicklook.getProductFile() + '\n' + e.getMessage()); } } }; threadManager.add(worker); MemUtils.freeAllMemory(); pm.setTaskName("Generating quicklooks " + cnt + " of " + total); ++cnt; //pm.worked(1); } pm.done(); threadManager.finish(); return true; } @Override protected void done() { super.done(); updateQuicklooks = true; showProduct(currentProduct); } }; worker.execute(); } catch (Exception e) { SnapApp.getDefault().handleError("Unable to load quicklooks", e); } } public void setSelectedQuicklook(final Quicklook ql) { updateQuicklooks = true; showProduct(ql.getProduct()); quicklookNameCombo.setSelectedItem(ql.getName()); } private synchronized void showProduct(final Product product) { if (product == null) { return; } final String qlName = (String)quicklookNameCombo.getSelectedItem(); if(qlName != null) { final Quicklook quicklook; if (qlName.equals(DEFAULT_QUICKLOOK)) { quicklook = product.getDefaultQuicklook(); } else { quicklook = product.getQuicklook(qlName); } if(quicklook != null) { quicklook.addListener(this); if ((quicklook.hasImage() || quicklook.hasCachedImage())) { setImage(product, quicklook); } else if (quicklook != null && updateQuicklooks) { if (product.getFileLocation() != null) { loadImage(product, quicklook); } } else { setImage(product, null); } } else { setImage(product, null); } } } private static void loadImage(final Product product, final Quicklook quicklook) { final StatusProgressMonitor qlPM = new StatusProgressMonitor(StatusProgressMonitor.TYPE.SUBTASK); qlPM.beginTask("Creating quicklook " + product.getName() + "... ", 100); ProgressMonitorSwingWorker<BufferedImage, Object> loader = new ProgressMonitorSwingWorker<BufferedImage, Object> (SnapApp.getDefault().getMainFrame(), "Loading quicklook image...") { @Override protected BufferedImage doInBackground(com.bc.ceres.core.ProgressMonitor pm) throws Exception { return quicklook.getImage(qlPM); } @Override protected void done() { qlPM.done(); } }; loader.execute(); } private void setImage(final Product product, final Quicklook quicklook) { final BufferedImage img; if(quicklook != null && (quicklook.hasImage() || quicklook.hasCachedImage())) { img = quicklook.getImage(ProgressMonitor.NULL); } else { img = noDataImage; } if(currentProduct == product && imgPanel.getImage() == img) { return; } currentProduct = product; nameLabel.setText(product.getDisplayName()); imgPanel.setImage(img); updateButtons(); } private void addProducts() { final Product[] products = SnapApp.getDefault().getProductManager().getProducts(); for(Product product : products) { addProduct(product); } } private synchronized void addProduct(final Product product) { productSet.add(product); for(int i=0; i < product.getQuicklookGroup().getNodeCount(); ++i) { quicklookNameSet.add(product.getQuicklookGroup().get(i).getName()); } updateQuicklookNameCombo(); } private synchronized void removeProduct(final Product product) { productSet.remove(product); cleanUpQuicklookNameSet(); updateQuicklookNameCombo(); } private void cleanUpQuicklookNameSet() { Set<String> toRemove = new HashSet<>(); for(String name : quicklookNameSet) { if(name.equals(DEFAULT_QUICKLOOK)) continue; boolean exists = false; for(Product product : productSet) { for(int i=0; i < product.getQuicklookGroup().getNodeCount(); ++i) { if(name.equals(product.getQuicklookGroup().get(i).getName())) { exists = true; break; } } } if(!exists) { toRemove.add(name); } } for(String name : toRemove) { quicklookNameSet.remove(name); } } private void updateQuicklookNameCombo() { String selected = (String)quicklookNameCombo.getSelectedItem(); quicklookNameCombo.removeAllItems(); for(String name : quicklookNameSet) { quicklookNameCombo.addItem(name); } // restore selection if(selected != null && ((DefaultComboBoxModel)quicklookNameCombo.getModel()).getIndexOf(selected) != -1 ) { quicklookNameCombo.setSelectedItem(selected); } else { quicklookNameCombo.setSelectedItem(DEFAULT_QUICKLOOK); } } private Product getFirstProduct() { return productSet.isEmpty() ? null : productSet.first(); } private Product getLastProduct() { return productSet.isEmpty() ? null : productSet.last(); } private Product getNextProduct() { final Iterator<Product> itr = productSet.iterator(); while (itr.hasNext()) { Product p = itr.next(); if (p == currentProduct) { if (itr.hasNext()) { return itr.next(); } else { return null; } } } return null; } private Product getPreviousProduct() { final Iterator<Product> itr = productSet.iterator(); Product prev = null; while (itr.hasNext()) { Product p = itr.next(); if (p == currentProduct) { return prev; } prev = p; } return null; } private void openProduct() { final OpenRGBImageViewAction rgbAction = new OpenRGBImageViewAction(currentProduct); rgbAction.openProductSceneViewRGB(currentProduct, ""); } private void closeProduct() { if (currentProduct != null) { Product productToClose = currentProduct; if(productToClose == getLastProduct()) { showProduct(getPreviousProduct()); } else { showProduct(getNextProduct()); } SnapApp.getDefault().getProductManager().removeProduct(productToClose); } } private class ImagePanel extends JLabel implements MouseListener { private BufferedImage img = null; public ImagePanel() { setHorizontalAlignment(JLabel.CENTER); setVerticalAlignment(JLabel.CENTER); addMouseListener(this); } public BufferedImage getImage() { return img; } public void setImage(final BufferedImage img) { this.img = img; if (img == null) { setIcon(null); } repaint(); } @Override public void paintComponent(Graphics g) { if (img != null) { Graphics2D g2 = (Graphics2D)g; g2.addRenderingHints(new RenderingHints( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g2.addRenderingHints(new RenderingHints( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY)); g2.addRenderingHints(new RenderingHints( RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC)); int w = imgScrollPanel.getWidth()*zoom - 10; int h = imgScrollPanel.getHeight()*zoom - 10; setIcon(new ImageIcon(new FixedSizeThumbnailMaker() .size(w, h) .keepAspectRatio(true) .fitWithinDimensions(true) .make(img))); } super.paintComponent(g); } public void mouseClicked(MouseEvent e) { } public void mousePressed(MouseEvent e) { if (e.getClickCount() == 2 && currentProduct != null) { openProduct(); } } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } } private class ButtonActionListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { switch (e.getActionCommand()) { case "Start": showProduct(getFirstProduct()); break; case "Prev": showProduct(getPreviousProduct()); break; case "Next": showProduct(getNextProduct()); break; case "End": showProduct(getLastProduct()); break; case "Refresh": if(!productSet.isEmpty()) { loadProducts((String)quicklookNameCombo.getSelectedItem()); } break; case "Open": openProduct(); break; case "Close": closeProduct(); break; } } } public class ProductManagerListener implements ProductManager.Listener { @Override public void productAdded(ProductManager.Event event) { addProduct(event.getProduct()); updateButtons(); } @Override public void productRemoved(ProductManager.Event event) { if (event.getProduct() == currentProduct) { getNextProduct(); } removeProduct(event.getProduct()); updateButtons(); } } public JPopupMenu createImagePopup() { final JPopupMenu popup = new JPopupMenu(); final JMenuItem zoomInItem = new JMenuItem("Zoom in"); zoomInItem.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if(zoom < 10) { zoom += 2; } } }); popup.add(zoomInItem); final JMenuItem zoomOutItem = new JMenuItem("Zoom out"); zoomOutItem.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { if(zoom > 2) { zoom -= 2; } } }); popup.add(zoomOutItem); popup.addSeparator(); final JMenuItem closeItem = new JMenuItem("Close Product"); closeItem.addActionListener(new ActionListener() { public void actionPerformed(final ActionEvent e) { closeProduct(); } }); popup.add(closeItem); return popup; } public void notifyImageUpdated(Thumbnail thumbnail) { showProduct(thumbnail.getProduct()); } }