package org.autoplot.pngwalk; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferedImage; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.net.URL; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.ImageIcon; import javax.swing.JComponent; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import org.autoplot.datasource.DataSetURI; /** * This is the abstract superclass for views in the PNGWalk tool. Concrete * subclasses will do the work of actually laying out a particular view and * handling events on it. * * @author Ed Jackson */ public abstract class PngWalkView extends JPanel implements PropertyChangeListener { static final Logger logger= org.das2.util.LoggerManager.getLogger("autoplot.pngwalk"); protected WalkImageSequence seq; protected boolean showCaptions = false; //protected boolean showMissing = false; //Should view show placeholder for missing files? protected static final BufferedImage loadingImage = initLoadingImage(); protected static final BufferedImage tinyLoadingImage= initTinyLoadingImage(); protected static final ImageIcon okBadge; protected static final ImageIcon problemBadge; protected static final ImageIcon ignoreBadge; static { URL u = PngWalkView.class.getResource("/resources/badge_problem.png"); if (u != null) problemBadge = new ImageIcon(u); else problemBadge = null; u = PngWalkView.class.getResource("/resources/badge_ok.png"); if (u != null) okBadge = new ImageIcon(u); else okBadge = null; u = PngWalkView.class.getResource("/resources/badge_ignore.png"); if (u != null) ignoreBadge = new ImageIcon(u); else ignoreBadge = null; } protected PngWalkView(WalkImageSequence sequence) { setSequence(sequence); setFocusable(true); } public final void setSequence(WalkImageSequence sequence) { if (seq != null) seq.removePropertyChangeListener(this); seq = sequence; if (seq != null) { seq.addPropertyChangeListener(this); } sequenceChanged(); } public WalkImageSequence getSequence() { return seq; } /** * Subclasses should override this method if they need to do anything special * when the view gets a new image sequence. The default implementation just * calls repaint(). * @param seq */ protected void sequenceChanged() { repaint(); } /** * subclasses should override this if they need to take action when the * thumbnail size changes. */ protected void thumbnailSizeChanged() { repaint(); } /** Respond to property changes on the {@list WalkImageSequence} this view * represents. The default implementation just calls repaint(), * (or sequencChanged() if appropriate) but subclasses may override. * @param e */ public void propertyChange(PropertyChangeEvent e) { if (e.getPropertyName().equals(WalkImageSequence.PROP_SEQUENCE_CHANGED)) { sequenceChanged(); } else { repaint(); } } public boolean isShowCaptions() { return showCaptions; } public void setShowCaptions(boolean showCaptions) { this.showCaptions = showCaptions; } protected int thumbnailSize = 100; public static final String PROP_THUMBNAILSIZE = "thumbnailSize"; public int getThumbnailSize() { return thumbnailSize; } public void setThumbnailSize(int thumbnailSize) { int oldThumbnailSize = this.thumbnailSize; this.thumbnailSize = thumbnailSize; thumbnailSizeChanged(); firePropertyChange(PROP_THUMBNAILSIZE, oldThumbnailSize, thumbnailSize); } /** * return the component that will generate mouse events. Some * components have a JScrollPane, so simply adding a listener to the * PngWalkView doesn't work. The base class implementation of this * simply returns the PngWalkView, but such components should override * this method. * @return */ public JComponent getMouseTarget() { return this; } /** * return the mouse wheel listener, which should be added to each panel so the sequence position is easily adjusted. * @return */ public MouseWheelListener getMouseWheelListener() { return new MouseWheelListener() { public void mouseWheelMoved(MouseWheelEvent e) { if ( e.getModifiersEx()!=0 ) { //disable ctrl+wheel because we'll use it for zoom soon. } else { if ( seq!=null && seq.size()!=0 ) seq.skipBy(e.getWheelRotation()); } } }; } public JPopupMenu getPopup() { JPopupMenu m= new JPopupMenu(); m.add( new JMenuItem( new AbstractAction( "Save local copy..." ) { public void actionPerformed(ActionEvent e) { if ( PngWalkView.this.seq==null ) return; String file= DataSetURI.fromUri( seq.currentImage().getUri() ); PngWalkTool.saveLocalCopy(PngWalkView.this,file); } } ) ); m.add( new JMenuItem( new AbstractAction( "Copy to clipboard..." ) { public void actionPerformed(ActionEvent e) { if ( PngWalkView.this.seq==null ) return; String file= DataSetURI.fromUri( seq.currentImage().getUri() ); PngWalkTool.copyToClipboard(PngWalkView.this,file); } } ) ); return m; } // Error message painting (e.g. "No matching images") protected void drawCenteredString(Graphics2D g, String msg) { if (msg == null) { return; } System.err.println("draw string"); Rectangle bounds = g.getClipBounds(); if (bounds != null) { FontMetrics fm = this.getFontMetrics(this.getFont()); g.drawString(msg, (bounds.width - fm.stringWidth(msg)) / 2, bounds.height / 2); } else { System.err.println("bad clipping"); } } protected Rectangle paintImageCentered(BufferedImage i, Graphics2D g2) { return paintImageCentered(i, g2, null); } /** * paint the image centered within the this component. The image may be * scaled to fit within the View's (component's) boundardies. * @param i the image to paint. * @param g2 the graphics context. * @param caption the caption to paint below the image. * @return the rectangle containing the coordinates where the image was drawn. */ protected Rectangle paintImageCentered(BufferedImage i, Graphics2D g2, String caption) { FontMetrics fm = this.getFontMetrics(this.getFont()); double captionHeight= ( showCaptions && caption!=null ) ? ( fm.getHeight() + fm.getDescent() ) : 0 ; double imageHeight= i.getHeight(); double xfactor = (double) getWidth() / (double) i.getWidth(null); double yfactor = (double) ( getHeight()-captionHeight ) / (double) imageHeight; double s = Math.min(xfactor, yfactor); if ( s<=0 ) return null; s = Math.min(1.0, s); int xpos = (int) (this.getWidth() - i.getWidth(null) * s) / 2; int ypos = (int) ((this.getHeight()-captionHeight) - imageHeight * s) / 2; int xs = (int) (i.getWidth(null) * s); int ys = (int) (i.getHeight(null) * s); if ( i==loadingImage ) { g2.drawImage(i, xpos, ypos, this ); } else { //BufferedImageOp resizeOp = new ScalePerspectiveImageOp(i.getWidth(), i.getHeight(), 0, 0, xs, ys, 0, -1, -1, 0, false); //BufferedImageOp resizeOp = new RescaleOp( 1/(float)xfactor, 0, // new RenderingHints( Collections.singletonMap( RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY ) ) ); BufferedImage resize= ImageResize.getScaledInstance( i, xs, ys, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true ); g2.drawImage( resize, xpos, ypos, this ); } if ( showCaptions && caption != null) { int cx = xpos; int cy = ypos + ys + fm.getHeight(); g2.drawString(caption, cx, cy); } if (PngWalkTool.isQualityControlEnabled() && seq.getQualityControlSequence()!=null ) { paintQualityControlIcon( seq.getIndex(), g2, xpos, ypos, true ); QualityControlRecord rec = seq.getQualityControlSequence().getQualityControlRecord(seq.getIndex()); if ( rec!=null ) { int cx = xpos; int cy = ypos + ys + 2 *fm.getHeight(); g2.drawString( rec.getLastComment(), cx, cy ); } } return new Rectangle( xpos, ypos, xs, ys ); } private static BufferedImage initLoadingImage() { BufferedImage li; //e.printStackTrace(System.err); // Construct a backup image to use li = new BufferedImage(48, 48, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = li.createGraphics(); g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g2.setColor(new java.awt.Color(0.0F, 0.0F, 0.0F, 0.5F)); g2.fillRoundRect(0, 0, 48, 48, 6, 6); //TODO: Add text or hourglass or something? g2.setColor(java.awt.Color.WHITE); g2.fillOval(12, 32, 4, 4); g2.fillOval(24, 32, 4, 4); g2.fillOval(36, 32, 4, 4); return li; } private static BufferedImage initTinyLoadingImage() { BufferedImage li; //e.printStackTrace(System.err); // Construct a backup image to use li = new BufferedImage(8,8, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = li.createGraphics(); g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)); g2.setColor(new java.awt.Color(0.0F, 0.0F, 0.0F, 0.5F)); g2.fillRoundRect(0, 0, 8, 8, 4, 4); //TODO: Add text or hourglass or something? g2.setColor(java.awt.Color.WHITE); g2.fillOval( 2, 2, 4, 4 ); return li; } protected void paintQualityControlIcon(int i, Graphics2D g2, int imgX, int imgY, boolean icon) { QualityControlRecord rec = seq.getQualityControlSequence().getQualityControlRecord(i); if ( okBadge==null ) { throw new RuntimeException("unable to locate all badges for quality control"); } if (rec != null) { // "missing" images if ( icon ) { switch (rec.getStatus()) { case OK: okBadge.paintIcon(this, g2, imgX + 5, imgY + 5); break; case PROBLEM: problemBadge.paintIcon(this, g2, imgX + 5, imgY + 5); break; case IGNORE: ignoreBadge.paintIcon(this, g2, imgX + 5, imgY + 5); break; default: // Don't do anything. } } else { Color color0= g2.getColor(); switch (rec.getStatus()) { case OK: g2.setColor( Color.GREEN ); break; case PROBLEM: g2.setColor( Color.RED ); break; case IGNORE: g2.setColor( Color.GRAY ); break; default: // Don't do anything. return; } g2.fillOval( imgX, imgY, 6, 6 ); g2.setColor( Color.GRAY ); g2.drawOval( imgX, imgY, 6, 6 ); g2.setColor(color0); } } } int getNextInterval(int index) { return index+7; } int getNextPage(int index) { return index+28; } int getPrevInterval(int index) { return index-7; } int getPrevPage(int index) { return index-28; } }