/* File: DasCanvas.java * Copyright (C) 2002-2003 The University of Iowa * Created by: Jeremy Faden * Jessica Swanner * Edward E. West * * This file is part of the das2 library. * * das2 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 2 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, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.das2.graph; import java.util.logging.Level; import org.das2.DasApplication; import org.das2.DasProperties; import org.das2.event.DragRenderer; import org.das2.graph.dnd.TransferableCanvasComponent; import org.das2.system.DasLogger; import org.das2.system.RequestProcessor; import org.das2.util.AboutUtil; import org.das2.util.DasExceptionHandler; import org.das2.util.DasPNGConstants; import org.das2.util.DasPNGEncoder; import org.das2.util.awt.GraphicsOutput; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.LayoutManager; import java.awt.Paint; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.Stroke; import java.awt.Toolkit; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.dnd.DnDConstants; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.lang.reflect.InvocationTargetException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TimeZone; import java.util.concurrent.locks.Lock; import java.util.logging.Logger; import java.util.prefs.Preferences; import javax.imageio.ImageIO; import javax.swing.AbstractAction; import javax.swing.Action; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JLayeredPane; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.LookAndFeel; import javax.swing.Scrollable; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.event.MouseInputAdapter; import javax.swing.event.MouseInputListener; import javax.swing.filechooser.FileNameExtensionFilter; import org.das2.components.propertyeditor.Editable; import org.das2.components.propertyeditor.PropertyEditor; import org.das2.datum.Datum; import org.das2.datum.TimeParser; import org.das2.datum.TimeUtil; import org.das2.datum.Units; import org.das2.datum.UnitsUtil; import org.das2.event.DasUpdateEvent; import org.das2.system.ChangesSupport; import org.das2.system.DefaultMonitorFactory; import org.das2.system.DefaultMonitorFactory.MonitorEntry; import org.das2.system.EventQueueBlocker; import org.das2.system.MonitorFactory; import org.das2.util.LoggerManager; import org.das2.util.monitor.ProgressMonitor; /** Canvas for das2 graphics. The DasCanvas contains any number of DasCanvasComponents such as axes, plots, colorbars, etc. * @author eew */ public class DasCanvas extends JLayeredPane implements Printable, Editable, Scrollable { /** Default drawing layer of the JLayeredPane */ public static final Integer DEFAULT_LAYER = JLayeredPane.DEFAULT_LAYER; /** Z-Layer for drawing the plot. */ public static final Integer PLOT_LAYER = 300; /** Z-Layer for vertical axis. Presently lower than the horizontal axis, presumably to remove ambiguity */ public static final Integer VERTICAL_AXIS_LAYER = 400; /** Z-Layer */ public static final Integer HORIZONTAL_AXIS_LAYER = 500; /** Z-Layer */ public static final Integer AXIS_LAYER = VERTICAL_AXIS_LAYER; /** Z-Layer */ public static final Integer ANNOTATION_LAYER = 1000; /** Z-Layer */ public static final Integer GLASS_PANE_LAYER = 30000; private static final Paint PAINT_ROW = new Color(0xff, 0xb2, 0xb2, 0x92); private static final Paint PAINT_COLUMN = new Color(0xb2, 0xb2, 0xff, 0x92); private static final Paint PAINT_SELECTION = Color.GRAY; private static final Stroke STROKE_DASHED; static { float thick = 3.0f; int cap = BasicStroke.CAP_SQUARE; int join = BasicStroke.JOIN_MITER; float[] dash = new float[]{thick * 4.0f, thick * 4.0f}; STROKE_DASHED = new BasicStroke(thick, cap, join, thick, dash, 0.0f); } /** * return the canvas that has the focus. * @return */ public static DasCanvas getFocusCanvas() { return currentCanvas; } private final List topDecorators= Collections.synchronizedList(new LinkedList<>()); private final List bottomDecorators= Collections.synchronizedList(new LinkedList<>()); private final Painter[] emptyPainterArray= new Painter[0]; // so we can call atomic copy. /** * Java6 has paintingForPrint, use this for now. */ boolean lpaintingForPrint= false; private static DasCanvas currentCanvas; /** * return a plot which shares this row and column, or null. * @param aThis * @return a plot which shares this row and column, or null. */ public DasPlot otherPlotOnTop( DasAxis aThis ) { DasPlot myPlot=null; for ( DasCanvasComponent cc: this.getCanvasComponents() ) { if ( cc instanceof DasPlot ) { DasPlot p= (DasPlot)cc; if ( p.getXAxis()==aThis || p.getYAxis()==aThis ) { myPlot= p; } } } if ( myPlot==null ) return null; for ( DasCanvasComponent cc: this.getCanvasComponents() ) { if ( cc instanceof DasPlot ) { DasPlot p= (DasPlot)cc; if ( p!=myPlot && p.getRow()==myPlot.getRow() && p.getColumn()==myPlot.getColumn() ) { return p; } } } return null; } /** * paint the top decorators above the data. * @param g2 the graphics context, which is set for the canvas with 0,0 in the upper-left corner. */ private void paintTopDecorators(Graphics2D g2) { Painter[] decor; decor= topDecorators.toArray(emptyPainterArray); for ( Painter p : decor ) { try { long t0= System.currentTimeMillis(); Graphics2D g22= (Graphics2D) g2.create(); // create a graphics object in case they reset colors, etc. //See https://sourceforge.net/p/autoplot/bugs/2140/ g22.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); g22.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); p.paint(g22); long dt= System.currentTimeMillis()-t0; if ( dt > 120 ) { // warn if painters are taking more than 120 ms to paint. System.err.println("painter is taking too long to paint ("+dt+" ms): "+p ); } } catch ( Exception ex ) { g2.drawString( "topDecorator causes exception: "+ex.toString(), 20, 20 ); ex.printStackTrace(); } } } /* Canvas actions */ protected static abstract class CanvasAction extends AbstractAction { CanvasAction(String label) { super(label); } } private static File currentFile; public static final Action SAVE_AS_PNG_ACTION = new CanvasAction("Save as PNG") { @Override public void actionPerformed(ActionEvent e) { final JFileChooser fileChooser = new JFileChooser(); fileChooser.setDialogTitle("Write to PNG"); fileChooser.setFileFilter( new FileNameExtensionFilter("png files", "png")); Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); String savedir = prefs.get("savedir", null); if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); if (currentFile != null) fileChooser.setSelectedFile(currentFile); int choice = fileChooser.showSaveDialog(currentCanvas); if (choice == JFileChooser.APPROVE_OPTION) { final DasCanvas canvas = currentCanvas; String fname = fileChooser.getSelectedFile().toString(); if (!fname.toLowerCase().endsWith(".png")) fname += ".png"; final String ffname = fname; prefs.put("savedir", new File(ffname).getParent()); currentFile = new File(ffname.substring(0, ffname.length() - 4)); Runnable run = new Runnable() { public void run() { try { canvas.writeToPng(ffname); } catch (java.io.IOException ioe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(ioe); } } }; new Thread(run, "writePng").start(); } } }; public static final Action SAVE_AS_SVG_ACTION = new CanvasAction("Save as SVG") { @Override public void actionPerformed(ActionEvent e) { final JFileChooser fileChooser = new JFileChooser(); fileChooser.setApproveButtonText("Select File"); fileChooser.setDialogTitle("Write to SVG"); fileChooser.setFileFilter( new FileNameExtensionFilter("svg files", "svg") ); Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); String savedir = prefs.get("savedir", null); if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); if (currentFile != null) fileChooser.setSelectedFile(currentFile); int choice = fileChooser.showSaveDialog(currentCanvas); if (choice == JFileChooser.APPROVE_OPTION) { final DasCanvas canvas = currentCanvas; String fname = fileChooser.getSelectedFile().toString(); if (!fname.toLowerCase().endsWith(".svg")) fname += ".svg"; final String ffname = fname; prefs.put("savedir", new File(ffname).getParent()); currentFile = new File(ffname.substring(0, ffname.length() - 4)); Runnable run = new Runnable() { @Override public void run() { try { canvas.writeToSVG(ffname); } catch (java.io.IOException ioe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(ioe); } } }; new Thread(run, "writeSvg").start(); } } }; public static final Action SAVE_AS_PDF_ACTION = new CanvasAction("Save as PDF") { @Override public void actionPerformed(ActionEvent e) { final JFileChooser fileChooser = new JFileChooser(); fileChooser.setApproveButtonText("Select File"); fileChooser.setDialogTitle("Write to PDF"); fileChooser.setFileFilter( new FileNameExtensionFilter("pdf files", "pdf")); Preferences prefs = Preferences.userNodeForPackage(DasCanvas.class); String savedir = prefs.get("savedir", null); if (savedir != null) fileChooser.setCurrentDirectory(new File(savedir)); if (currentFile != null) fileChooser.setSelectedFile(currentFile); int choice = fileChooser.showDialog(currentCanvas, "Select File"); if (choice == JFileChooser.APPROVE_OPTION) { final DasCanvas canvas = currentCanvas; String fname = fileChooser.getSelectedFile().toString(); if (!fname.toLowerCase().endsWith(".pdf")) fname += ".pdf"; final String ffname = fname; prefs.put("savedir", new File(ffname).getParent()); currentFile = new File(ffname.substring(0, ffname.length() - 4)); Runnable run = new Runnable() { public void run() { try { canvas.writeToPDF(ffname); } catch (java.io.IOException ioe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(ioe); } } }; new Thread(run, "writePdf").start(); } } }; public static final Action EDIT_DAS_PROPERTIES_ACTION = new AbstractAction("DAS Properties") { @Override public void actionPerformed(ActionEvent e) { org.das2.DasProperties.showEditor(); } }; private static boolean printBusy= false; private static void doPrintImmediately( Component me ) { Printable p = currentCanvas.getPrintable(); PrinterJob pj = PrinterJob.getPrinterJob(); pj.setPrintable(p); // PrintRequestAttributeSet aset = new HashPrintRequestAttributeSet(); // This doesn't work for me on Linux, and http://stackoverflow.com/questions/4554452/java-native-print-dialog-change-icon says there's a access restriction. // if ( me!=null ) { // Window w= SwingUtilities.getWindowAncestor(me); // Frame owner= w instanceof Frame ? (Frame)w : null; // if ( owner!=null ) aset.add(new sun.print.DialogOwner(owner)); // } if (pj.printDialog()) { try { pj.print(); } catch (PrinterException pe) { Object[] message = {"Error printing", pe.getMessage()}; JOptionPane.showMessageDialog(null, message, "ERROR", JOptionPane.ERROR_MESSAGE); } } } public static final Action PRINT_ACTION = new CanvasAction("Print...") { @Override public void actionPerformed(ActionEvent e) { if ( printBusy ) { JOptionPane.showMessageDialog( currentCanvas, "Another task is trying to print, please wait.", "Please Wait", JOptionPane.INFORMATION_MESSAGE ); } else { printBusy= true; Runnable run= new Runnable() { @Override public void run() { doPrintImmediately(currentCanvas); printBusy= false; } }; new Thread(run,"printThread").start(); } } }; public static final Action REFRESH_ACTION = new CanvasAction("Refresh") { public void actionPerformed(ActionEvent e) { DasCanvasComponent[] comps = currentCanvas.getCanvasComponents(); for (int i = 0; i < comps.length; i++) { comps[i].update(); } } }; /** * returns a list of all the rows and columns on the canvas. * @return */ public List devicePositionList() { return Collections.unmodifiableList(devicePositionList); } /** * Override Component.setBounds for debugging. */ //@Override //public void setBounds(int x, int y, int width, int height) { // System.err.println(">>> "+width + ","+ height ); // super.setBounds(x, y, width, height); //} //@Override //public void invalidate() { // super.invalidate(); //} public static final Action ABOUT_ACTION = new CanvasAction("About") { public void actionPerformed(ActionEvent e) { String aboutContent = AboutUtil.getAboutHtml(); JOptionPane.showConfirmDialog(currentCanvas, aboutContent, "about das2", JOptionPane.PLAIN_MESSAGE); } }; public final Action PROPERTIES_ACTION = new CanvasAction("properties") { public void actionPerformed(ActionEvent e) { PropertyEditor editor = new PropertyEditor(DasCanvas.this); editor.showDialog(DasCanvas.this); } }; private static boolean disableActions = false; public static void setDisableActions(boolean val) { disableActions = val; } public static Action[] getActions() { if ( disableActions ) { return new Action[0]; } else { return new Action[]{ ABOUT_ACTION, REFRESH_ACTION, EDIT_DAS_PROPERTIES_ACTION, PRINT_ACTION, SAVE_AS_PNG_ACTION, SAVE_AS_SVG_ACTION, SAVE_AS_PDF_ACTION,}; } } private DasApplication application; private static final Logger logger = DasLogger.getLogger(DasLogger.GRAPHICS_LOG); private final GlassPane glassPane; private String dasName; private JPopupMenu popup; private boolean editable; List devicePositionList = new ArrayList(); transient org.das2.util.DnDSupport dndSupport; ChangesSupport stateSupport; /** The set of Threads that are currently printing this canvas. * This set is used to determine of certain operations that are only * appropriate in printing situations should occur. */ private Set printingThreads; /** * Creates a new instance of DasCanvas. * */ public DasCanvas() { LookAndFeel.installColorsAndFont(this, "Panel.background", "Panel.foreground", "Panel.font"); application = DasApplication.getDefaultApplication(); String name = application.suggestNameFor(this); setName(name); setOpaque(true); setLayout(new RowColumnLayout()); addComponentListener(createResizeListener()); setBackground(Color.white); setPreferredSize(new Dimension(400, 300)); this.setDoubleBuffered(true); glassPane = new GlassPane(); add(glassPane, GLASS_PANE_LAYER); // this is needed because code elsewhere (getCanvasComponents) assumes the zeroth component is the glassPane. if (!application.isHeadless()) { popup = createPopupMenu(); this.addMouseListener(createMouseInputAdapter()); try { dndSupport = new CanvasDnDSupport(); } catch (SecurityException ex) { dndSupport = new CanvasDnDSupport(); } } makeCurrent(); stateSupport = new ChangesSupport(null, this); stateSupport.addPropertyChangeListener(new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent e) { if (Boolean.FALSE.equals(e.getNewValue())) { setOpaque(true); repaint(); } else { setOpaque(false); } } }); incrementPaintCountTimer= new Timer( 100, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { setPaintCount( paintCount + 1 ); } } ); incrementPaintCountTimer.setRepeats(false); } /** * add a decorator that will be painted on top of all other objects. * Each decorator object should complete painting within 100 milliseconds, and the * total for all decorators should not exceed 300 milliseconds. * This should be done on the event thread. * @param painter */ public void addTopDecorator(Painter painter) { this.topDecorators.add( painter ); repaint(); } /** * remove the decorator. This should be done on the event thread. * @param painter */ public void removeTopDecorator(Painter painter) { this.topDecorators.remove( painter ); repaint(); } /** * remove all top decorators. This should be done on the event thread. */ public void removeTopDecorators() { this.topDecorators.clear( ); repaint(); } /** * returns true if there are any top decorators. * @return true if there are any decorators. */ public boolean hasTopDecorators() { return ! this.topDecorators.isEmpty(); } /** * add a decorator that will be painted on below all other objects. * Each decorator object should complete painting within 100 milliseconds, and the * total for all decorators should not exceed 300 milliseconds. * This should be done on the event thread. * @param painter */ public void addBottomDecorator(Painter painter) { this.bottomDecorators.add( painter ); repaint(); } /** * remove the decorator. This should be done on the event thread. * @param painter */ public void removeBottomDecorator(Painter painter) { this.bottomDecorators.remove( painter ); repaint(); } /** * remove all bottom decorators. This should be done on the event thread. */ public void removeBottomDecorators() { this.bottomDecorators.clear( ); repaint(); } /** * returns true if there are any bottom decorators. * @return true if there are any decorators. */ public boolean hasBottomDecorators() { return ! this.bottomDecorators.isEmpty(); } private MouseInputAdapter createMouseInputAdapter() { return new MouseInputAdapter() { @Override public void mousePressed(MouseEvent e) { makeCurrent(); if ( e.isPopupTrigger() && e.getX()<10 && e.getY()<10 ) popup.show(DasCanvas.this, e.getX(), e.getY()); } @Override public void mouseReleased(MouseEvent e) { makeCurrent(); if ( e.isPopupTrigger() && e.getX()<10 && e.getY()<10 ) popup.show(DasCanvas.this, e.getX(), e.getY()); } }; } private JPopupMenu createPopupMenu() { JPopupMenu popup = new JPopupMenu(); JMenuItem props = new JMenuItem(PROPERTIES_ACTION); props.setText("DasCanvas Properties"); popup.add(props); popup.addSeparator(); Action[] actions = getActions(); if ( !disableActions ) { for (Action action : actions) { JMenuItem item = new JMenuItem(); item.setAction(action); popup.add(item); } } return popup; } /** returns the GlassPane above all other components. This is used for drawing dragRenderers, etc. * @return */ public Component getGlassPane() { return glassPane; } /** * return a list of all the rows and columns. * @return a list of all the rows and columns. */ public List getDevicePositionList() { return Collections.unmodifiableList(devicePositionList); } private int displayLockCount = 0; private final Object displayLockObject = new Object(); /** * Lock the display for this canvas. All Mouse and Key events are captured * and swallowed by the glass pane. * * @param o the Object requesting the lock. * @see #freeDisplay(Object); */ void lockDisplay(Object o) { synchronized (displayLockObject) { displayLockCount++; //if (displayLockCount == 1) { // glassPane.setBlocking(true); //} } } /** * Frees the lock the specified object has requested on the display. * The display will not be freed until all locks have been freed. * * @param o the object releasing its lock on the display * @see #lockDisplay(Object) */ void freeDisplay(Object o) { synchronized (displayLockObject) { displayLockCount--; if (displayLockCount == 0) { // glassPane.setBlocking(false); displayLockObject.notifyAll(); } } } /** * Creates a new instance of DasCanvas with the specified width and height * @param width The width of the DasCanvas * @param height The height of the DasCanvas */ public DasCanvas(int width, int height) { this(); setPreferredSize(new Dimension(width, height)); } public DasApplication getApplication() { return application; } public void setApplication(DasApplication application) { this.application = application; } /** * simply returns getPreferredSize() * @return getPreferredSize() */ @Override public Dimension getMaximumSize() { return getPreferredSize(); } /** * paints the canvas itself. If printing, stamps the date on it as well. * @param g1 the Graphics object */ @Override protected void paintComponent(Graphics g1) { logger.finest("entering DasCanvas.paintComponent"); if (stateSupport.isValueAdjusting()) { logger.finest("value is adjusting, returning"); return; } Graphics2D g = (Graphics2D) g1; g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, textAntiAlias ? RenderingHints.VALUE_TEXT_ANTIALIAS_ON : RenderingHints.VALUE_TEXT_ANTIALIAS_OFF); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, antiAlias ? RenderingHints.VALUE_ANTIALIAS_ON : RenderingHints.VALUE_ANTIALIAS_OFF); if (!(isPrintingThread() && getBackground().equals(Color.WHITE))) { g.setColor(getBackground()); //g.fillRect(0, 0, getWidth(), getHeight()); Graphics2D g2 = g; g2.fill(g2.getClipBounds()); } g.setColor(getForeground()); Painter[] decor; decor= bottomDecorators.toArray(emptyPainterArray); for ( Painter p : decor ) { try { Graphics2D g2= (Graphics2D) g.create(); // create a graphics object in case they reset colors, etc. // See https://sourceforge.net/p/autoplot/bugs/2140/ g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); p.paint(g2); } catch ( Exception ex ) { g.drawString( "bottomDecorator causes exception: "+ex.toString(), 20, 20 ); ex.printStackTrace(); } } if (isPrintingThread()) { int width, height; Date now; SimpleDateFormat dateFormat; Font font, oldFont; FontMetrics metrics; String s; if (!printingTag.equals("")) { now = new Date(); if ( printingTag.contains("$Y") || printingTag.contains("$y") ) { TimeParser tp= TimeParser.create(printingTag); Datum nowTZ= TimeUtil.now().add( Units.hours.createDatum( TimeZone.getDefault().getRawOffset()/3600000 ) ); s= tp.format( nowTZ, nowTZ ); } else if ( printingTag.contains("'yy") ) { dateFormat = new SimpleDateFormat(printingTag); s = dateFormat.format(now); } else { s = printingTag; } oldFont = g.getFont(); font = oldFont.deriveFont((float) oldFont.getSize() / 2); metrics = g.getFontMetrics(font); width = metrics.stringWidth(s); height = metrics.getHeight(); g.setFont(font); g.drawString(s, getWidth() - width - 2 * height, getHeight() - metrics.getAscent() ); g.setFont(oldFont); } //paintTopDecorators(g); } if ( doIncrementPaintCountTimer ) { logger.log(Level.FINE, "incrementPaintCountTimer.restart() {0}", paintCount); incrementPaintCountTimer.restart(); } else { doIncrementPaintCountTimer= true; } } // See https://sourceforge.net/p/autoplot/bugs/1672/#0a1a, where the following code was used to identify who was triggering the repaint. // public void repaint() { // super.repaint(); // } // // @Override // public void repaint(Rectangle r) { // super.repaint(r); //To change body of generated methods, choose Tools | Templates. // } // // @Override // public void repaint(long tm, int x, int y, int width, int height) { // super.repaint(tm, x, y, width, height); //To change body of generated methods, choose Tools | Templates. // } // /** * Prints the canvas, scaling and possibly rotating it to improve fit. * @param printGraphics the Graphics object. * @param format the PageFormat object. * @param pageIndex should be 0, since the image will be on one page. * @return Printable.PAGE_EXISTS or Printable.NO_SUCH_PAGE */ @Override public int print(Graphics printGraphics, PageFormat format, int pageIndex) { if (pageIndex != 0) return NO_SUCH_PAGE; Graphics2D g2 = (Graphics2D) printGraphics; double canvasWidth = (double) getWidth(); double canvasHeight = (double) getHeight(); double printableWidth = format.getImageableWidth(); double printableHeight = format.getImageableHeight(); g2.translate(format.getImageableX(), format.getImageableY()); double canvasMax = Math.max(canvasWidth, canvasHeight); double canvasMin = Math.min(canvasWidth, canvasHeight); double printableMax = Math.max(printableWidth, printableHeight); double printableMin = Math.min(printableWidth, printableHeight); double maxScaleFactor = printableMax / canvasMax; double minScaleFactor = printableMin / canvasMin; double scaleFactor = Math.min(maxScaleFactor, minScaleFactor); g2.scale(scaleFactor, scaleFactor); if ((canvasWidth == canvasMax) ^ (printableWidth == printableMax)) { g2.rotate(Math.PI / 2.0); g2.translate(0.0, -canvasHeight); } print(g2); return PAGE_EXISTS; } /** * Layout manager for managing the Row, Column layout implemented by swing. * This will probably change in the future when we move away from using * swing to handle the DasCanvasComponents. */ protected static class RowColumnLayout implements LayoutManager { @Override public void layoutContainer(Container target) { synchronized (target.getTreeLock()) { int count = target.getComponentCount(); for (int i = 0; i < count; i++) { Component c = target.getComponent(i); if (c instanceof DasCanvasComponent) { ((DasCanvasComponent) c).update(); } else if (c == ((DasCanvas) target).glassPane) { Dimension size = target.getSize(); c.setBounds(0, 0, size.width, size.height); } } } } @Override public Dimension minimumLayoutSize(Container target) { return new Dimension(0, 0); } @Override public Dimension preferredLayoutSize(Container target) { return new Dimension(400, 300); } @Override public void addLayoutComponent(String name, Component comp) { } @Override public void removeLayoutComponent(Component comp) { } } /** * return true if the current thread is registered as the one printing this component. * @return true if the current thread is registered as the one printing this component. */ protected final boolean isPrintingThread() { synchronized (this) { return printingThreads == null ? false : printingThreads.contains(Thread.currentThread()); } } /** * Java1.6 has this function native * @return */ public boolean lisPaintingForPrint() { return this.lpaintingForPrint; } /** * print the canvas, filling the background and the painting. * @param g1 the graphics context. */ @Override public void print(Graphics g1) { synchronized (this) { if (printingThreads == null) { printingThreads = new HashSet(); } printingThreads.add(Thread.currentThread()); } try { setOpaque(false); Graphics2D g= (Graphics2D)g1; // if svg g.setColor( this.getBackground() ); g.fillRect(0,0,getWidth(),getHeight()); g.setColor( this.getForeground() ); g.setBackground( this.getBackground() ); //logger.fine("*** print graphics: " + g); //logger.fine("*** print graphics clip: " + g.getClip()); logger.log(Level.FINE, "*** print graphics clip: {0}", g.getClip() ); if ( logger.isLoggable(Level.FINER) ) { for (int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof DasCanvasComponent) { DasCanvasComponent p = (DasCanvasComponent) c; logger.log(Level.FINER, "-- {0} --", p); logger.log(Level.FINER, " DasPlot.isDirty()={0}", p.isDirty()); logger.log(Level.FINER, " DasPlot.getBounds()={0}", p.getBounds()); } } } lpaintingForPrint= true; super.print(g); lpaintingForPrint= false; } finally { setOpaque(true); synchronized (this) { printingThreads.remove(Thread.currentThread()); } } } /** * Returns an instance of java.awt.print.Printable that can * be used to render this canvas to a printer. The current implementation * returns a reference to this canvas. This method is provided so that in * the future, the canvas can delegate it's printing to another object. * @return a Printable instance for rendering this component. */ public Printable getPrintable() { return this; } /** * return the colorbar or null if there is not a single, unique colorbar. * @param plot a das plot * @return null or the single colorbar. */ private DasColorBar findOneColorBar( DasPlot plot ) { Renderer[] rr= plot.getRenderers(); DasColorBar result= null; int count=0; for ( Renderer r: rr ) { if ( r.getColorBar()!=null ) { result= r.getColorBar(); count++; } } return count==1 ? result : null; } /** * encode the plot in a JSON fragment. See http://autoplot.org/richPng * @param plot the plot to describe. * @param indent indent new lines this amount * @param isInList is in list, so append a comma after the y_axis. * @return the JSON code */ private String getJSONForPlot( DasPlot plot, String indent, boolean isInList ) { DasColorBar cb= findOneColorBar(plot); boolean inclColorbar= ( cb!=null && cb.isVisible() ) ; StringBuilder json= new StringBuilder(); json.append( String.format( "%s\"title\":\"%s\", \n", indent, plot.getTitle().replaceAll("\"", "\\\"") ) ); DasAxis axis= plot.getXAxis(); String minstr= UnitsUtil.isTimeLocation( axis.getDataMinimum().getUnits() ) ? String.format( "\"%s\"", axis.getDataMinimum().toString() ) : String.valueOf( axis.getDataMinimum( axis.getUnits() ) ); String maxstr= UnitsUtil.isTimeLocation( axis.getDataMaximum().getUnits() ) ? String.format( "\"%s\"", axis.getDataMaximum().toString() ) : String.valueOf( axis.getDataMaximum( axis.getUnits() ) ); String unitsstr= UnitsUtil.isTimeLocation( axis.getDataMinimum().getUnits() ) ? "UTC" : axis.getDataMinimum().getUnits().toString(); String dpos; dpos= String.format( "\"left\":%d, \"right\":%d", (int)plot.getColumn().getDMinimum(), (int)plot.getColumn().getDMaximum() ); json.append( String.format( "%s\"xaxis\": { \"label\":\"%s\", \"min\":%s, \"max\":%s, %s, \"type\":\"%s\", \"units\":\"%s\" },\n", indent, axis.getLabel().replaceAll("\"", "\\\"") , minstr, maxstr, dpos, axis.isLog() ? "log" : "lin", unitsstr ) ); axis= plot.getYAxis(); minstr= UnitsUtil.isTimeLocation( axis.getDataMinimum().getUnits() ) ? String.format( "'%s'", axis.getDataMinimum().toString() ) : String.valueOf( axis.getDataMinimum( axis.getUnits() ) ); maxstr= UnitsUtil.isTimeLocation( axis.getDataMaximum().getUnits() ) ? String.format( "'%s'", axis.getDataMaximum().toString() ) : String.valueOf( axis.getDataMaximum( axis.getUnits() ) ); unitsstr= UnitsUtil.isTimeLocation( axis.getDataMinimum().getUnits() ) ? "UTC" : axis.getDataMinimum().getUnits().toString(); dpos= String.format( "\"top\":%d, \"bottom\":%d", (int)plot.getRow().getDMinimum(), (int)plot.getRow().getDMaximum() ); if ( inclColorbar ) { json.append( String.format( "%s\"yaxis\": { \"label\":\"%s\", \"min\":%s, \"max\":%s, %s, \"type\":\"%s\", \"units\":\"%s\" },\n", indent, axis.getLabel().replaceAll("\"", "\\\"") , minstr, maxstr, dpos, axis.isLog() ? "log" : "lin", unitsstr ) ); } else { json.append( String.format( "%s\"yaxis\": { \"label\":\"%s\", \"min\":%s, \"max\":%s, %s, \"type\":\"%s\", \"units\":\"%s\" }%s\n", indent, axis.getLabel().replaceAll("\"", "\\\"") , minstr, maxstr, dpos, axis.isLog() ? "log" : "lin", unitsstr, isInList ? "," : "" ) ); } // if we can identify a colorbar for the plot, include it as well, with coordinates for the min and max colors. if ( inclColorbar ) { assert cb!=null; minstr= UnitsUtil.isTimeLocation( cb.getDataMinimum().getUnits() ) ? String.format( "'%s'", cb.getDataMinimum().toString() ) : String.valueOf( cb.getDataMinimum( cb.getUnits() ) ); maxstr= UnitsUtil.isTimeLocation( cb.getDataMaximum().getUnits() ) ? String.format( "'%s'", cb.getDataMaximum().toString() ) : String.valueOf( cb.getDataMaximum( cb.getUnits() ) ); unitsstr= UnitsUtil.isTimeLocation( cb.getDataMinimum().getUnits() ) ? "UTC" : cb.getDataMinimum().getUnits().toString(); // locate the painted colorbar so that it could be used to lookup colors. int[] pos= new int[4]; if ( cb.isHorizontal() ) { pos[1]=pos[3]=cb.getRow().getDMiddle(); pos[0]= cb.getColumn().getDMinimum(); pos[2]= cb.getColumn().getDMaximum(); } else { pos[0]=pos[2]=cb.getColumn().getDMiddle(); pos[1]= cb.getRow().getDMaximum()-2; // flip over because 0,0 is upper-left. pos[3]= cb.getRow().getDMinimum()+1; // tweak to get inside the axis. This is all just to get ballpark Z values anyway... } json.append( String.format( "%s\"zaxis\": { \"label\":\"%s\", \"min\":%s, \"max\":%s, \"minpixel\":[%d,%d], \"maxpixel\":[%d,%d], \"type\":\"%s\", \"units\":\"%s\" }%s\n", indent, cb.getLabel().replaceAll("\"", "\\\"") , minstr, maxstr, pos[0], pos[1], pos[2], pos[3], cb.isLog() ? "log" : "lin", unitsstr, isInList ? "," : "" ) ); } return json.toString(); } /** * returns JSON code that can be used to get plot positions and axes. * @return */ public String getImageMetadata() { List plots= new ArrayList(); for ( Component c: this.getCanvasComponents() ) { if ( c instanceof DasPlot ) { if ( ((DasPlot)c).isPlotVisible() ) { plots.add( (DasPlot)c ); } } } StringBuilder json= new StringBuilder(); json.append( String.format("{ \"size\":[%d,%d],\n", this.getWidth(),this.getHeight() ) ); json.append( String.format(" \"numberOfPlots\":%d,\n",plots.size() ) ); if ( plots.size()>0 ) { json.append(" \"plots\": [\n"); DasPlot lastPlot= plots.get( plots.size()-1 ); for ( DasPlot p: plots ) { json.append( " {\n"); json.append( getJSONForPlot( p, " ", false ) ); json.append( " }"); if ( p!=lastPlot ) json.append(","); json.append( "\n" ); } json.append(" ]"); } json.append("}" ); return json.toString(); } public String broken= null; /** * uses getImage to get an image of the canvas and encodes it * as a png. * * Note this now puts in a JSON representation of plot locations in the "plotInfo" tag. The plotInfo * tag will contain: *
{@code
     *   "size:[640,480]"
     *   "numberOfPlots:0"   
     *   "plots: { ... }"  where each plot contains:
     *   "title" "xaxis" "yaxis"
     *}
* See http://autoplot.org/richPng. * It is the responsibility of the caller to close the stream. * @param out the outputStream. This is left open, so the opener code must close it! * @param w width in pixels * @param h height in pixels * @throws IOException if there is an error opening the file for writing */ public void writeToPng( OutputStream out, int w, int h ) throws IOException { logger.fine("Enter writeToPng"); BufferedImage image = getImage(w,h); boolean doCheckAPBug1129= false; if ( doCheckAPBug1129 ) { // see DemoAPBug1129.java. List peaks= new ArrayList(); int z0= ( image.getRGB( 150,150 ) & 0xFF ); double dz= 0; for ( int i=0; i<50; i++ ) { //System.err.println( String.format( "%d %d %d %d", i, ( c & 0xFF0000 ) >> 16, ( c & 0x00FF00 ) >> 8, c & 0x0000FF ) ); int z1= ( image.getRGB( 150,150-i ) & 0xFF ); double dz1= z1-z0; if ( dz>0 && dz1< 0 ) { peaks.add(i); } dz= dz1; z0= z1; } System.err.println("peaks: "+peaks); if ( peaks.size()>1 ) { logger.warning("double peak detected"); broken= "doublePeakDetected"; } else { broken= null; } } if ( getBackground().getAlpha()>0 ) { logger.fine("Encoding image into png"); DasPNGEncoder encoder = new DasPNGEncoder(); encoder.addText(DasPNGConstants.KEYWORD_CREATION_TIME, new Date().toString()); javax.swing.JFrame f= DasApplication.getDefaultApplication().getMainFrame(); if ( f!=null ) { String title= f.getTitle(); int i= title.indexOf(" - Autoplot"); if ( i>-1 ) { title= title.substring(i+3) + " > " + title.substring(0,i); // Autoplot > foo.vap } encoder.addText(DasPNGConstants.KEYWORD_SOFTWARE, title ); } encoder.addText(DasPNGConstants.KEYWORD_PLOT_INFO, getImageMetadata() ); encoder.write(image, out); } else { logger.fine("ImageIO used to create image with transparent background, no metadata will be put in image."); ImageIO.write(image, "png", out ); } } /** * uses getImage to get an image of the canvas and encodes it * as a png. * * Note this now puts in a JSON representation of plot locations in the "plotInfo" tag. * See http://autoplot.org/richPng * @param filename the specified filename * @throws IOException if there is an error opening the file for writing */ public void writeToPng( String filename) throws IOException { logger.log(Level.CONFIG, "writeToPng({0})", filename); int w= getWidth(); int h= getHeight(); if (h==0 || w==0) { Dimension p = getPreferredSize(); w=(int) p.getWidth(); h=(int) p.getHeight(); } logger.log( Level.FINE, "Write to png {0} **************************************", filename); final FileOutputStream out = new FileOutputStream(filename); try { writeToPng( out, w, h ); } finally { out.close(); } logger.log(Level.FINE, "Wrote png file {0} **************************************", filename); } /** * write the canvas to a PDF file. * @param filename the PDF file name. * @throws IOException */ public void writeToPDF(String filename) throws IOException { try { writeToGraphicsOutput(filename, "org.das2.util.awt.PdfGraphicsOutput"); DasLogger.getLogger(DasLogger.GRAPHICS_LOG).log(Level.FINE, "write pdf file {0}", filename); } catch (NoClassDefFoundError cnfe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(new RuntimeException("PDF output is not available", cnfe)); } catch (ClassNotFoundException cnfe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(new RuntimeException("PDF output is not available", cnfe)); } catch (InstantiationException ie) { DasApplication.getDefaultApplication().getExceptionHandler().handleUncaught(ie); } catch (IllegalAccessException iae) { DasApplication.getDefaultApplication().getExceptionHandler().handleUncaught(iae); } } /** * write to various graphics devices such as png, pdf and svg. This handles the synchronization and * parameter settings. * @param out OutputStream to receive the data * @param go GraphicsOutput object. * @throws java.io.IOException */ public void writeToGraphicsOutput(OutputStream out, GraphicsOutput go) throws IOException { go.setOutputStream(out); go.setSize(getWidth(), getHeight()); go.start(); print(go.getGraphics()); go.finish(); } /** * write to future implementations of graphicsOutput. * @param filename * @param graphicsOutput * @throws IOException * @throws ClassNotFoundException * @throws InstantiationException * @throws IllegalAccessException * @see org.das2.util.awt.GraphicsOutput */ public void writeToGraphicsOutput(String filename, String graphicsOutput) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException { FileOutputStream out=null; try { out= new FileOutputStream(filename); Class goClass = Class.forName(graphicsOutput); GraphicsOutput go = (GraphicsOutput) goClass.newInstance(); writeToGraphicsOutput(out, go); } finally { if ( out!=null ) out.close(); } } /** * @param filename the specified filename * @throws IOException if there is an error opening the file for writing */ public void writeToSVG(String filename) throws IOException { try { writeToGraphicsOutput(filename, "org.das2.util.awt.SvgGraphicsOutput"); DasLogger.getLogger(DasLogger.GRAPHICS_LOG).log(Level.FINE, "write svg file {0}", filename); } catch (ClassNotFoundException cnfe) { DasApplication.getDefaultApplication().getExceptionHandler().handle(new RuntimeException("SVG output is not available", cnfe)); } catch (InstantiationException ie) { DasExceptionHandler.handleUncaught(ie); } catch (IllegalAccessException iae) { DasExceptionHandler.handleUncaught(iae); } } /** * returns true if work needs to be done to make the canvas clean. * This checks each component's isDirty. * @return true if work needs to be done to make the canvas clean */ public boolean isDirty() { DasCanvasComponent[] cc = this.getCanvasComponents(); boolean result = false; for (DasCanvasComponent cc1 : cc) { boolean dirty1 = cc1.isDirty(); if (dirty1) { logger.log(Level.FINE, "component is marked as dirty: {0}", cc[1]); //cc1.isDirty(); } result = result | dirty1 ; } return result; } /** * return a progress monitor if there is one that is active, and we are * using progress monitors. * @return progress monitor */ private ProgressMonitor getActiveMonitor() { MonitorFactory mf= getApplication().getMonitorFactory(); if ( mf instanceof DefaultMonitorFactory ) { DefaultMonitorFactory dmf= (DefaultMonitorFactory)mf; MonitorEntry[] mfs= dmf.getMonitors(); for (MonitorEntry mf1 : mfs) { ProgressMonitor mon = mf1.getMonitor(); if ( mon.isStarted() && !( mon.isFinished() && mon.isCancelled() ) ) { return mon; } } } return null; } /** * scans through all the canvas components and returns true if any are in a "dirty" state and need * repainting. * @param c * @return true if any canvas component is dirty. */ public static boolean childIsDirty( DasCanvas c ) { for ( DasCanvasComponent cc: c.getCanvasComponents() ) { if ( cc.isDirty() ) { logger.log(Level.FINE, "Still Dirty: {0}", cc); cc.isDirty(); return true; } } if ( c.isDirty() ) { logger.log(Level.FINE,"Canvas is still dirty"); return true; } return false; } /** * blocks until everything is idle, including no active monitors. * PRESENTLY THIS DOES NOT CHECK MONITORS! * @param monitors * @throws InterruptedException */ public void waitUntilIdle( boolean monitors ) throws InterruptedException { if ( false && monitors ) { while ( true ) { ProgressMonitor mon= getActiveMonitor(); if ( mon==null || ( mon.getTaskProgress()==0 && mon.getTaskSize()==-1 ) ) { break; } else { Thread.sleep(200); } } } waitUntilIdle(); } /** * Blocks the caller's thread until all events have been dispatched from the awt event thread, and * then waits for the RequestProcessor to finish all tasks with this canvas as the lock object. */ public void waitUntilIdle() { String msg = "dasCanvas.waitUntilIdle"; logger.fine(msg); final Object lockObject = new Object(); // see https://sourceforge.net/p/autoplot/bugs/1129/ if ( ! this.isShowing() || "true".equals(DasApplication.getProperty("java.awt.headless", "false")) ) { this.addNotify(); logger.log(Level.FINER, "setSize({0})", getPreferredSize()); this.setSize(getPreferredSize()); logger.finer("validate()"); this.validate(); resizeAllComponents(); } else { resizeAllComponents(); } /* wait for all the events on the awt event thread to process */ EventQueueBlocker.clearEventQueue(); logger.finer("pending events processed"); for ( DasCanvasComponent cc: getCanvasComponents() ) { if ( cc.isDirty() ) { logger.log(Level.FINE, "Still Dirty: {0}", cc); } } if ( this.isDirty() ) { logger.log(Level.FINE,"Canvas is still dirty"); } final String[] ss= new String[1]; ss[0]= null; /* wait for all the RequestProcessor to complete */ Runnable request = new Runnable() { @Override public void run() { synchronized (lockObject) { lockObject.notifyAll(); ss[0]= "okayStopWaiting"; } } }; try { synchronized (lockObject) { logger.finer("submitting invokeAfter to RequestProcessor to block until all tasks are complete"); RequestProcessor.invokeAfter(request, this); while ( ss[0]==null ) { lockObject.wait(); } logger.finer("requestProcessor.invokeAfter task complete"); } } catch (InterruptedException ex) { throw new RuntimeException(ex); } /* wait for all the post data-load stuff to clear */ EventQueueBlocker.clearEventQueue(); logger.finer("post data-load pending events processed"); int count=0; /** wait for registered pending changes. TODO: this is cheesy */ if (stateSupport.isPendingChanges() || childIsDirty(this) ) { logger.finer("waiting for pending changes"); while (stateSupport.isPendingChanges() || childIsDirty(this) ) { count++; if ( count%5==0 && childIsDirty(this) ) { for ( DasCanvasComponent cc: getCanvasComponents() ) { if ( cc.isDirty() ) { // there's a weird bug where sometimes the dirty flags aren't cleared. logger.info("strange bug where update event didn't clear dirty flags, reposting."); cc.isDirty(); cc.update(); } } } if ( count==100 ) { logger.info("stateSupport.pendingChanges:"); for ( Object o: stateSupport.getChangesPending().entrySet() ) { logger.log(Level.INFO, " {0}", o); } } try { Thread.sleep(100); } catch (InterruptedException ex) { throw new RuntimeException(ex); } } waitUntilIdle(); } // // The following is for debugging. // EventQueue eventQueue= Toolkit.getDefaultToolkit().getSystemEventQueue(); // while ( eventQueue.peekEvent( DasUpdateEvent.DAS_UPDATE_EVENT_ID )!=null ) { // try { // Thread.sleep(100); // System.err.println("==Dump Future Events...=="); // EventQueueBlocker.dumpEventQueue( System.err ); // System.err.println("========================="); // // }catch (InterruptedException ex) { // throw new RuntimeException(ex); // } // } // if ( isDirty() ) { // logger.fine("something is still dirty, not waiting."); // isDirty(); // try { // Thread.sleep(1000); // } catch (InterruptedException ex) { // throw new RuntimeException(ex); // } // } logger.fine("canvas is idle"); /* should be in static state */ } /** * return a list of pending changes. * @see waitUntilIdle * @param result */ public void pendingChanges( Map result ) { stateSupport.pendingChanges(result); if ( Toolkit.getDefaultToolkit().getSystemEventQueue().peekEvent(DasUpdateEvent.DAS_UPDATE_EVENT_ID) != null ) { result.put( "dasUpdate", "eventQueueContainsUpdateEvents"); //TODO: it would be better to check for the eventQueueBlocker object as well. } } /** * introduced as a kludgy way for clients to force the canvas to resize all of its components. * validate or revalidate should probably do this. */ public void resizeAllComponents() { for (DasDevicePosition devicePositionList1 : devicePositionList) { devicePositionList1.revalidate(); } for (int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof DasCanvasComponent) { ((DasCanvasComponent) c).resize(); } } } /** * process all pending operations and make sure we're repainted. See PlotCommand in Autoplot. */ public void waitUntilValid() { for (int i = 0; i < getComponentCount(); i++) { Component c = getComponent(i); if (c instanceof DasPlot) { ((DasPlot) c).invalidateCacheImage(); } } if ( ! this.isShowing() || "true".equals(DasApplication.getProperty("java.awt.headless", "false")) ) { this.addNotify(); logger.log(Level.FINER, "setSize({0})", getPreferredSize()); this.setSize(getPreferredSize()); logger.finer("validate()"); this.validate(); resizeAllComponents(); } else { resizeAllComponents(); } waitUntilIdle(); } /** * resets the width and height, then waits for all update * messages to be processed. In headless mode, * the GUI components are validated. * This must not be called from the event queue, because * it uses eventQueueBlocker! * * @param width the width of the output in pixels. * @param height the width of the output in pixels. * * @throws IllegalStateException if called from the event queue. */ public void prepareForOutput(int width, int height) { if (SwingUtilities.isEventDispatchThread()) throw new IllegalStateException("dasCanvas.prepareForOutput must not be called from event queue!"); setPreferredWidth(width); setPreferredHeight(height); if ( ! this.isShowing() || "true".equals(DasApplication.getProperty("java.awt.headless", "false")) ) { this.addNotify(); logger.log(Level.FINER, "setSize({0})", getPreferredSize()); this.setSize(getPreferredSize()); logger.finer("validate()"); this.validate(); resizeAllComponents(); } try { waitUntilIdle(true); // wait for monitors. } catch (InterruptedException ex) { throw new RuntimeException(ex); } } /** * Creates a BufferedImage by blocking until the image is ready. This * includes waiting for datasets to load, etc. Works by submitting * an invokeAfter request to the RequestProcessor that calls * {@link #writeToImageImmediately(Image)}. * * @param width * @param height * @return a BufferedImage */ public BufferedImage getImage(int width, int height) { long t0= System.currentTimeMillis(); logger.log(Level.FINE, "dasCanvas.getImage({0},{1})", new Object[]{width, height}); prepareForOutput(width, height); //TODO: this requires not event thread, and is inconsistent with if statement five lines down. final BufferedImage image = getBackground().getAlpha() > 0 ? new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB) : new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB ); if (SwingUtilities.isEventDispatchThread()) { writeToImageImmediately(image); } else { Runnable run = new Runnable() { @Override public void run() { logger.fine("writeToImageImmediately"); writeToImageImmediately(image); } }; try { SwingUtilities.invokeAndWait(run); } catch (InvocationTargetException | InterruptedException ex) { application.getExceptionHandler().handle(ex); } } Logger logger1= LoggerManager.getLogger( "das2.graphics.layout" ); if ( logger1.isLoggable(Level.FINER ) ) { logger1.log(Level.FINER, "All Row Positions for Canvas ({0}x{1}): ", new Object[]{width, height}); ArrayList rows= new ArrayList<>(); for ( DasCanvasComponent dcc: getCanvasComponents() ) { DasRow r= dcc.getRow(); DasRow pr= (DasRow)r.getParentDevicePosition(); if ( pr!=null && !rows.contains(pr) ) rows.add(pr); if ( !rows.contains(r) ) rows.add(r); } for ( DasRow r: rows ) { String s= ""+r.getDasName() + " " +DasDevicePosition.formatLayoutStr(r,true)+","+DasDevicePosition.formatLayoutStr(r,false); if ( r.getParentDevicePosition()!=null ) { s+= " "+r.getParentDevicePosition().getDasName(); } logger1.finer( s ); } } logger.log(Level.FINE, "time to getImage: {0}ms", (System.currentTimeMillis() - t0)); return image; } /** * Creates a BufferedImage by blocking until the image is ready. This * includes waiting for datasets to load, etc. Works by submitting * an invokeAfter request to the RequestProcessor that calls * {@link #writeToImageImmediately(Image)}. * * Note, this calls writeToImageImmediatelyNonPrint, which avoids the usual overhead of * revalidating the DasPlot elements we normally do when printing to a new device. * * @param width * @param height * @return an Image */ public Image getImageNonPrint(int width, int height) { logger.entering( "DasCanvas", "getImageNonPrint" ); long t0= System.currentTimeMillis(); String msg = "dasCanvas.getImageNonPrint(" + width + "," + height + ")"; logger.fine(msg); prepareForOutput(width, height); final Image image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); if (SwingUtilities.isEventDispatchThread()) { writeToImageImmediatelyNonPrint(image); } else { Runnable run = new Runnable() { @Override public void run() { logger.fine("writeToImageImmediately"); writeToImageImmediatelyNonPrint(image); } }; try { SwingUtilities.invokeAndWait(run); } catch (InvocationTargetException ex) { application.getExceptionHandler().handle(ex); } catch (InterruptedException ex) { application.getExceptionHandler().handle(ex); } } logger.log(Level.FINEST, "time to getImageNonPaint: {0}ms", (System.currentTimeMillis() - t0)); logger.exiting( "DasCanvas", "getImageNonPrint" ); return image; } /** * Writes on to the image without waiting, using the print method. * The graphics context is accessed with image.getGraphics. * @param image the image */ public void writeToImageImmediately(Image image) { Graphics2D graphics; try { synchronized (displayLockObject) { while (displayLockCount != 0) { displayLockObject.wait(); } } } catch (InterruptedException ex) { } graphics = (Graphics2D) image.getGraphics(); if ( this.getBackground().getAlpha()>0 ) { graphics.setColor(this.getBackground()); graphics.fillRect(0, 0, image.getWidth(this), image.getHeight(this)); graphics.setColor(this.getForeground()); } graphics.setBackground(this.getBackground()); print(graphics); } /** * silly code so that Autoplot can get an image without incrementing paintCount. * @param image */ public void writeToImageImmediatelyNoCount( Image image ) { doIncrementPaintCountTimer= false; writeToImageImmediatelyNonPrint(image); } /** * This by passes the normal print method used in writeToImageImmedately, which sets the printing flags which * tell the components, like DasPlot, to fully reset. This was introduced so that Autoplot could get thumbnails * and an image of the canvas for its layout tab without having to reset. * @param image the image */ public void writeToImageImmediatelyNonPrint( Image image ) { logger.entering( "DasCanvas", "writeToImageImmediatelyNonPrint" ); long t0= System.currentTimeMillis(); Graphics2D graphics; try { synchronized (displayLockObject) { while (displayLockCount != 0) { displayLockObject.wait(); } } } catch (InterruptedException ex) { } graphics = (Graphics2D) image.getGraphics(); // code from print method. Here we do the print stuff, but don't turn on the printing flag. setOpaque(false); Graphics2D g= (Graphics2D)graphics; // if svg g.setColor( this.getBackground() ); g.fillRect(0,0,getWidth(),getHeight()); g.setColor( this.getForeground() ); g.setBackground( this.getBackground() ); // avoid calling the overrided print method, which turns on flags for printing. super.print(g); logger.log(Level.FINE, "time to writeToImageImmediatelyNonPaint: {0}ms", (System.currentTimeMillis() - t0)); logger.exiting( "DasCanvas", "writeToImageImmediatelyNonPrint" ); } private transient PropertyChangeListener repaintListener= new PropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent evt) { repaint(); } }; /** * This methods adds the specified DasCanvasComponent to this canvas. * @param c the component to be added to this canvas * Note that the canvas will need to be revalidated after the component * is added. * @param row DasRow specifying the layout of the component. * @param column DasColumn specifying the layout of the component. */ public void add(DasCanvasComponent c, DasRow row, DasColumn column) { logger.log( Level.FINE, "adding DasCanvasComponent {0}", c); if (c.getRow() == DasRow.NULL || c.getRow().getParent() != this) { c.setRow(row); } if (c.getColumn() == DasColumn.NULL || c.getColumn().getParent() != this) { c.setColumn(column); } add(c); row.addPropertyChangeListener(repaintListener); column.addPropertyChangeListener(repaintListener); } /** * This is doing something special setting the LAYER_PROPERTY. We * check to see if the property is already set, and if it is not then * we set it to the default value. (This is to allow client code to * set it before adding the component.) * @param comp * @param constraints * @param index */ @Override protected void addImpl(Component comp, Object constraints, int index) { if (comp == null) { logger.log(Level.SEVERE,"NULL COMPONENT"); Thread.dumpStack(); return; } if (index < 0) index = 0; Integer layer = (Integer) ((JComponent) comp).getClientProperty(LAYER_PROPERTY); if (layer == null) { if (comp instanceof DasPlot) { ((DasPlot) comp).putClientProperty(LAYER_PROPERTY, PLOT_LAYER); } else if (comp instanceof DasAxis) { ((DasAxis) comp).putClientProperty(LAYER_PROPERTY, AXIS_LAYER); } else if (comp instanceof Legend) { ((Legend) comp).putClientProperty(LAYER_PROPERTY, AXIS_LAYER); } else if (comp instanceof DasAnnotation) { ((DasAnnotation) comp).putClientProperty(LAYER_PROPERTY, ANNOTATION_LAYER); } else if (comp instanceof JComponent) { ((JComponent) comp).putClientProperty(LAYER_PROPERTY, DEFAULT_LAYER); } } super.addImpl(comp, constraints, index); if (comp instanceof DasCanvasComponent) { ((DasCanvasComponent) comp).installComponent(); } } /** * Sets the preferred width of the canvas to the specified width. * * @param width the specified width. */ public void setPreferredWidth(int width) { logger.log(Level.CONFIG, "setPreferredWidth({0,number,#})", width); Dimension pref = getPreferredSize(); pref.width = width; setPreferredSize(pref); if (getParent() != null) ((JComponent) getParent()).revalidate(); } /** Sets the preferred height of the canvas to the specified height. * * @param height the specified height */ public void setPreferredHeight(int height) { logger.log(Level.CONFIG, "setPreferredHeight({0,number,#})", height); Dimension pref = getPreferredSize(); pref.height = height; setPreferredSize(pref); if (getParent() != null) ((JComponent) getParent()).revalidate(); } /** * The font used should be the base font scaled based on the canvas size. * If this is false, then the canvas font is simply the base font. */ protected boolean scaleFonts = true; /** * The font used should be the base font scaled based on the canvas size. * If this is false, then the canvas font is simply the base font. */ public static final String PROP_SCALEFONTS = "scaleFonts"; /** * true if the fonts should be rescaled as the window size is changed. * @return true if the fonts should be rescaled as the window size is changed. */ public boolean isScaleFonts() { return scaleFonts; } /** * true if the fonts should be rescaled as the window size is changed. * @param scaleFonts true if the fonts should be rescaled as the window size is changed. */ public void setScaleFonts(boolean scaleFonts) { logger.log(Level.CONFIG, "setScaleFonts({0})", scaleFonts); boolean oldScaleFonts = this.scaleFonts; this.scaleFonts = scaleFonts; setBaseFont(getBaseFont()); firePropertyChange(PROP_SCALEFONTS, oldScaleFonts, scaleFonts); } /** * Property name for the base font. */ public static final String PROP_BASEFONT= "baseFont"; /** * the base font, which is the font or the font which is scaled when scaleFont is true. */ private Font baseFont = null; /** * the base font, which is the font or the font which is scaled when scaleFont is true. * @return the base font, which is the font or the font which is scaled when scaleFont is true. */ public Font getBaseFont() { if (baseFont == null) { baseFont = getFont(); } return this.baseFont; } /** * the base font, which is the font or the font which is scaled with canvas size when scaleFont is true. * @param font the font used to derive all other fonts. */ public void setBaseFont(Font font) { logger.log(Level.CONFIG, "setBaseFont({0})", font); Font oldFont = getFont(); Font oldBaseFont= baseFont; this.baseFont = font; if ( scaleFonts ) { super.setFont(getFontForSize(getWidth(), getHeight())); } else { super.setFont( font ); } firePropertyChange("font", oldFont, getFont()); //TODO: really? firePropertyChange("baseFont", oldBaseFont, this.baseFont ); repaint(); } @Override public void setFont(Font font) { logger.log(Level.CONFIG, "setFont({0})", font); super.setFont(font); } private static final int R_1024_X_768 = 1024 * 768; private static final int R_800_X_600 = 800 * 600; private static final int R_640_X_480 = 640 * 480; private static final int R_320_X_240 = 320 * 240; private Font getFontForSize(int width, int height) { int area = width * height; Font f; float baseFontSize = getBaseFont().getSize2D(); if (area >= (R_1024_X_768 - R_800_X_600) / 2 + R_800_X_600) { f = getBaseFont().deriveFont(baseFontSize / 12f * 18f); //new Font("Serif", Font.PLAIN, 18); } else if (area >= (R_800_X_600 - R_640_X_480) / 2 + R_640_X_480) { f = getBaseFont().deriveFont(baseFontSize / 12f * 14f); //new Font("Serif", Font.PLAIN, 14); } else if (area >= (R_640_X_480 - R_320_X_240) / 2 + R_320_X_240) { f = getBaseFont().deriveFont(baseFontSize / 12f * 12f); //new Font("Serif", Font.PLAIN, 12); } else if (area >= (R_320_X_240) / 2) { f = getBaseFont().deriveFont(baseFontSize / 12f * 8f); //new Font("Serif", Font.PLAIN, 8); } else { f = getBaseFont().deriveFont(baseFontSize / 12f * 6f); //new Font("Serif", Font.PLAIN, 6); } return f; } private ComponentListener createResizeListener() { return new ComponentAdapter() { @Override public void componentResized(ComponentEvent e) { Font aFont; if ( scaleFonts ) { aFont= getFontForSize(getWidth(), getHeight()); } else { aFont= getBaseFont(); } if (!aFont.equals(getFont())) { setFont(aFont); } } }; } /** * Returns the DasCanvasComponent that contains the (x, y) location. * If there is no component at that location, this method * returns null * @param x the x coordinate * @param y the y coordinate * @return the component at the specified point, or null */ public DasCanvasComponent getCanvasComponentAt(int x, int y) { Component[] components = getComponents(); for (int index = 1; index < components.length; index++) { Component c = components[index]; if (c instanceof DasCanvasComponent) { DasCanvasComponent cc = (DasCanvasComponent) c; if (cc.getActiveRegion().contains((double) x, (double) y)) { return cc; } } } return null; } /** * Removes the component, specified by index, * from this container, calling its uninstallComponent * method if it's a DasCanvasComponent. * @param index the index of the component to be removed. */ @Override public void remove(int index) { Component comp = this.getComponent(index); super.remove(index); if (comp instanceof DasCanvasComponent) { ((DasCanvasComponent) comp).uninstallComponent(); //We can't remove the repaintListener because multiple DCCs may be using the same column or row. //((DasCanvasComponent) comp).getRow().removePropertyChangeListener(repaintListener); //((DasCanvasComponent) comp).getColumn().removePropertyChangeListener(repaintListener); } } /** * Removes the component, specified by index, * from this container, calling its uninstallComponent * method if it's a DasCanvasComponent. * @param comp the component */ @Override public void remove(Component comp) { super.remove(comp); //TODO: I wonder what component required the following code... //if (comp instanceof DasCanvasComponent) { // ((DasCanvasComponent) comp).uninstallComponent(); //} repaint(); } private class CanvasDnDSupport extends org.das2.util.DnDSupport { CanvasDnDSupport() { super(DasCanvas.this, DnDConstants.ACTION_COPY_OR_MOVE, null); } private Rectangle getAxisRectangle(Rectangle rc, Rectangle t, int x, int y) { if (t == null) { t = new Rectangle(); } int o = getAxisOrientation(rc, x, y); switch (o) { case DasAxis.TOP: t.width = rc.width; t.height = 3 * getFont().getSize(); t.x = rc.x; t.y = rc.y - t.height; break; case DasAxis.RIGHT: t.width = 3 * getFont().getSize(); t.height = rc.height; t.x = rc.x + rc.width; t.y = rc.y; break; case DasAxis.LEFT: t.width = 3 * getFont().getSize(); t.height = rc.height; t.x = rc.x - t.width; t.y = rc.y; break; case DasAxis.BOTTOM: t.width = rc.width; t.height = 3 * getFont().getSize(); t.x = rc.x; t.y = rc.y + rc.height; break; default: throw new RuntimeException("invalid orientation: " + o); } return t; } private int getAxisOrientation(Rectangle rc, int x, int y) { int nx = (x - rc.x) * rc.height; int ny = (y - rc.y) * rc.width; int a = rc.width * rc.height; boolean b = nx + ny < a; return (nx > ny ? (b ? DasAxis.TOP : DasAxis.RIGHT) : (b ? DasAxis.LEFT : DasAxis.BOTTOM)); } @Override protected int canAccept(DataFlavor[] flavors, int x, int y, int action) { glassPane.setAccepting(true); List flavorList = java.util.Arrays.asList(flavors); Cell cell = getCellAt(x, y); Rectangle cellBounds = (cell == null ? null : cell.getCellBounds()); Rectangle target = glassPane.target; if (flavorList.contains(TransferableCanvasComponent.COLORBAR_FLAVOR)) { return action; } else if (flavorList.contains(TransferableCanvasComponent.AXIS_FLAVOR)) { if (target != cellBounds && (target == null || !target.equals(cellBounds))) { if (target != null) { glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); } if (cellBounds != null) { target = glassPane.target = getAxisRectangle(cellBounds, target, x, y); glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); } else { glassPane.target = null; } } return action; } else if (flavorList.contains(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR)) { if (target != cellBounds && (target == null || !target.equals(cellBounds))) { if (target != null) { glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); } target = glassPane.target = cellBounds; if (cellBounds != null) { assert target!=null; glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); } } return action; } return -1; } @Override protected void done() { glassPane.setAccepting(false); if (glassPane.target != null) { Rectangle target = glassPane.target; glassPane.target = null; glassPane.repaint(target.x - 1, target.y - 1, target.width + 2, target.height + 2); } } @Override protected boolean importData(Transferable t, int x, int y, int action) { boolean success = false; try { if (t.isDataFlavorSupported(TransferableCanvasComponent.COLORBAR_FLAVOR)) { Cell c = getCellAt(x, y); if (c != null) { DasCanvasComponent comp = (DasCanvasComponent) t.getTransferData(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR); comp.setRow(c.getRow()); comp.setColumn(c.getColumn()); add(comp); revalidate(); success = true; } } else if (t.isDataFlavorSupported(TransferableCanvasComponent.AXIS_FLAVOR)) { Cell c = getCellAt(x, y); if (c != null) { DasAxis axis = (DasAxis) t.getTransferData(TransferableCanvasComponent.AXIS_FLAVOR); axis.setRow(c.getRow()); axis.setColumn(c.getColumn()); Rectangle cellBounds = c.getCellBounds(); int orientation = getAxisOrientation(cellBounds, x, y); axis.setOrientation(orientation); add(axis); revalidate(); success = true; } } else if (t.isDataFlavorSupported(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR)) { Cell c = getCellAt(x, y); if (c != null) { DasCanvasComponent comp = (DasCanvasComponent) t.getTransferData(TransferableCanvasComponent.CANVAS_COMPONENT_FLAVOR); comp.setRow(c.getRow()); comp.setColumn(c.getColumn()); add(comp); revalidate(); success = true; } } } catch (UnsupportedFlavorException ufe) { } catch (IOException ioe) { } return success; } @Override protected Transferable getTransferable(int x, int y, int action) { DasCanvasComponent component = DasCanvas.this.getCanvasComponentAt(x, y); if (component instanceof DasColorBar) { return new TransferableCanvasComponent((DasColorBar) component); } else if (component instanceof DasAxis) { return new TransferableCanvasComponent((DasAxis) component); } else if (component instanceof DasPlot) { return new TransferableCanvasComponent((DasPlot) component); } else { return null; } } @Override protected void exportDone(Transferable t, int action) { } } /** * JPanel that lives above all other components, and is capable of blocking keyboard and mouse input from * all components underneath. */ public static class GlassPane extends JPanel implements MouseInputListener, KeyListener { boolean blocking = false; boolean accepting = false; Rectangle target; DragRenderer dragRenderer = null; Point p1 = null, p2 = null; public GlassPane() { setOpaque(false); setLayout(null); } DasCanvas getCanvas() { return (DasCanvas) getParent(); } void setBlocking(boolean b) { if (b != blocking) { blocking = b; if (b) { addMouseListener(this); addMouseMotionListener(this); } else { removeMouseListener(this); removeMouseMotionListener(this); } repaint(); } } void setAccepting(boolean b) { if (b != accepting) { accepting = b; repaint(); } } /** * set the DragRenderer to be painted on the glasspane. * @param r DragRenderer, for example the CrossHairRenderer or BoxZoomGesturesRenderer * @param p1 the start point to render * @param p2 the current point to render */ public void setDragRenderer(DragRenderer r, Point p1, Point p2) { this.dragRenderer = r; this.p1 = p1; this.p2 = p2; } @Override protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g.create(); g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON ); g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON ); if (blocking) { paintLoading(g2); } if (((DasCanvas) getParent()).getEditingMode()) { paintRowColumn(g2); } if (accepting && target != null) { paintDnDTarget(g2); } if (dragRenderer != null ) { if ( p1!=null && p2!=null ) { dragRenderer.renderDrag(g2, p1, p2); } else { logger.info("NullPointerException avoided, why is p1 or p2 null?"); } } getCanvas().paintTopDecorators(g2); g2.dispose(); } private void paintRowColumn(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); DasCanvas canvas = getCanvas(); for (DasDevicePosition d : canvas.devicePositionList) { double minimum = d.getMinimum(); double maximum = d.getMaximum(); int cWidth = canvas.getWidth(); int cHeight = canvas.getHeight(); int x, y, width, height; Paint paint; if (d instanceof DasRow) { x = 0; width = cWidth; y = (int) Math.floor(minimum * cHeight + 0.5); height = (int) Math.floor(maximum * cHeight + 0.5) - y; paint = PAINT_ROW; } else { x = (int) Math.floor(minimum * cWidth + 0.5); width = (int) Math.floor(maximum * cWidth + 0.5) - x; y = 0; height = cHeight; paint = PAINT_COLUMN; } g2.setPaint(paint); g2.fillRect(x, y, width, height); } } private void paintDnDTarget(Graphics2D g2) { g2.setStroke(STROKE_DASHED); g2.setPaint(PAINT_SELECTION); g2.drawRect(target.x + 1, target.y + 1, target.width - 2, target.height - 2); } private void paintLoading(Graphics2D g2) { g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_SPEED); g2.setColor(new Color(0xdcFFFFFF, true)); Rectangle rect = g2.getClipBounds(); if (rect == null) { g2.fillRect(0, 0, getWidth(), getHeight()); } else { g2.fillRect(rect.x, rect.y, rect.width, rect.height); } } @Override public void mouseClicked(MouseEvent e) { } @Override public void mouseDragged(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mouseMoved(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } /** Invoked when a key has been pressed. * See the class description for {@link KeyEvent} for a definition of * a key pressed event. * @param e */ @Override public void keyPressed(KeyEvent e) { } /** Invoked when a key has been released. * See the class description for {@link KeyEvent} for a definition of * a key released event. * @param e */ @Override public void keyReleased(KeyEvent e) { } /** Invoked when a key has been typed. * See the class description for {@link KeyEvent} for a definition of * a key typed event. * @param e */ @Override public void keyTyped(KeyEvent e) { } } private HashSet horizontalLineSet = new HashSet(); private HashSet verticalLineSet = new HashSet(); private HashSet cellSet = new HashSet(); /** TODO * @param x * @param y * @return */ public HotLine getLineAt(int x, int y) { Iterator iterator = horizontalLineSet.iterator(); while (iterator.hasNext()) { HotLine line = (HotLine) iterator.next(); if (y >= line.position - 1 && y <= line.position + 1) { return line; } } iterator = verticalLineSet.iterator(); while (iterator.hasNext()) { HotLine line = (HotLine) iterator.next(); if (x >= line.position - 1 && x <= line.position + 1) { return line; } } return null; } /** TODO * @param x * @param y * @return */ public Cell getCellAt(int x, int y) { Cell best = null; Point bestCenter = null; Point boxCenter = null; Iterator iterator = cellSet.iterator(); while (iterator.hasNext()) { Cell box = (Cell) iterator.next(); Rectangle rc = box.rc; if (rc.contains(x, y)) { if (best == null) { best = box; } else { if (bestCenter == null) { bestCenter = new Point(); boxCenter = new Point(); } if (best.rc.contains(rc)) { best = box; } else { bestCenter.setLocation(best.rc.x + best.rc.width / 2, best.rc.y + best.rc.height / 2); boxCenter.setLocation(rc.x + rc.width / 2, rc.y + rc.height / 2); int bestDistance = distanceSquared(x, y, bestCenter.x, bestCenter.y); int boxDistance = distanceSquared(x, y, boxCenter.x, boxCenter.y); if (boxDistance < bestDistance) { best = box; } else if (boxDistance == bestDistance) { if (rc.width * rc.height < best.rc.width * best.rc.height) { best = box; } } } } } } return best; } private static int distanceSquared(int x1, int y1, int x2, int y2) { return (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1); } /** * This method should only be called in a constructor defined in the * DasDevicePosition class. * * @param position the DasDevicePosition object calling this method. */ void addDevicePosition(DasDevicePosition position) { devicePositionList.add(position); if (position instanceof DasRow) { addRow((DasRow) position); } else if (position instanceof DasColumn) { addColumn((DasColumn) position); } } private void addRow(DasRow row) { HotLine min = new HotLine(row, HotLine.MIN); HotLine max = new HotLine(row, HotLine.MAX); horizontalLineSet.add(min); horizontalLineSet.add(max); Iterator iterator = devicePositionList.iterator(); while (iterator.hasNext()) { DasDevicePosition position = (DasDevicePosition) iterator.next(); if (position instanceof DasColumn) { DasColumn column = (DasColumn) position; cellSet.add(new Cell(row, column)); } } } private void addColumn(DasColumn column) { HotLine min = new HotLine(column, HotLine.MIN); HotLine max = new HotLine(column, HotLine.MAX); verticalLineSet.add(min); verticalLineSet.add(max); Iterator iterator = devicePositionList.iterator(); while (iterator.hasNext()) { DasDevicePosition position = (DasDevicePosition) iterator.next(); if (position instanceof DasRow) { DasRow row = (DasRow) position; cellSet.add(new Cell(row, column)); } } } /** * remove the device position from the list we keep track of. Note those * with parent rows and columns should not be registered (or at least existing * code doesn't add it). * @param position */ public void removeDasDevicePosition(DasDevicePosition position ) { devicePositionList.remove(position); } /** TODO * @param position * @deprecated use removeDasDevicePosition instead. */ public void removepwDevicePosition(DasDevicePosition position) { devicePositionList.remove(position); if (position instanceof DasRow) { removeRow((DasRow) position); } else if (position instanceof DasColumn) { removeColumn((DasColumn) position); } } private void removeRow(DasRow row) { for (Iterator i = horizontalLineSet.iterator(); i.hasNext();) { HotLine line = (HotLine) i.next(); if (line.devicePosition == row) { i.remove(); } } for (Iterator i = cellSet.iterator(); i.hasNext();) { Cell cell = (Cell) i.next(); if (cell.row == row) { i.remove(); } } } private void removeColumn(DasColumn column) { for (Iterator i = verticalLineSet.iterator(); i.hasNext();) { HotLine line = (HotLine) i.next(); if (line.devicePosition == column) { i.remove(); } } for (Iterator i = cellSet.iterator(); i.hasNext();) { Cell cell = (Cell) i.next(); if (cell.column == column) { i.remove(); } } } /** * @param name * @param width * @param height * @return DasCanvas with a name. */ public static DasCanvas createFormCanvas(String name, int width, int height) { DasCanvas canvas = new DasCanvas(width, height); if (name == null) { name = "canvas_" + Integer.toHexString(System.identityHashCode(canvas)); } try { canvas.setDasName(name); } catch (org.das2.DasNameException dne) { DasApplication.getDefaultApplication().getExceptionHandler().handle(dne); } return canvas; } /** TODO * @return */ public boolean getEditingMode() { return editable; } /** TODO * @param b */ public void setEditingMode(boolean b) { if (editable == b) { return; } editable = b; revalidate(); } /** TODO * @return */ public org.das2.util.DnDSupport getDnDSupport() { return dndSupport; } /** TODO * @param x * @param y * @param action * @param evt * @return */ public boolean startDrag(int x, int y, int action, java.awt.event.MouseEvent evt) { for (int i = 0; i < getComponentCount(); i++) { if (getComponent(i).getBounds().contains(x, y)) { dndSupport.startDrag(x, y, action, evt); return true; } } return false; } /** * return the name identifying the component. * @return the name identifying the component. */ public String getDasName() { return dasName; } /** * set the name identifying the component. * @param name the name identifying the component. * @throws org.das2.DasNameException when the name is not a valid name ("[A-Za-z][A-Za-z0-9_]*") */ public void setDasName(String name) throws org.das2.DasNameException { if (name.equals(dasName)) { return; } String oldName = dasName; dasName = name; DasApplication app = getDasApplication(); if (app != null) { app.getNameContext().put(name, this); if (oldName != null) { app.getNameContext().remove(oldName); } } this.firePropertyChange("name", oldName, name); } Timer incrementPaintCountTimer; boolean doIncrementPaintCountTimer= true; // use to disable increment paint count private int paintCount = 0; public static final String PROP_PAINTCOUNT = "paintCount"; /** * provide a property which can be used to monitor updates. * @return arbitrary int which will change as the canvas is painted. */ public int getPaintCount() { return paintCount; } private void setPaintCount(int paintCount) { int oldPaintCount = this.paintCount; this.paintCount = paintCount; firePropertyChange(PROP_PAINTCOUNT, oldPaintCount, paintCount); } /** * return the application object for this canvas. * @return the application object for this canvas. */ public DasApplication getDasApplication() { return getApplication(); } /** * return the component at the index. * @param index the index * @return the component at the index. */ public DasCanvasComponent getCanvasComponents(int index) { return (DasCanvasComponent) getComponent(index + 1); } /** * return the components. * @return the components. */ public DasCanvasComponent[] getCanvasComponents() { Component[] cc= getComponents(); int n = cc.length - 1; DasCanvasComponent[] result = new DasCanvasComponent[n]; for (int i = 0; i < n; i++) { result[i] = (DasCanvasComponent) cc[i+1]; } return result; } @Override public String toString() { return "[DasCanvas " + this.getWidth() + "x" + this.getHeight() + " " + this.getDasName() + "]"; } /** TODO */ public static class HotLine implements PropertyChangeListener { /** TODO */ public static final int MIN = -1; /** TODO */ public static final int NONE = 0; /** TODO */ public static final int MAX = 1; int position; DasDevicePosition devicePosition; int minOrMax; HotLine(DasDevicePosition devicePosition, int minOrMax) { this.devicePosition = devicePosition; this.minOrMax = minOrMax; refresh(); devicePosition.addPropertyChangeListener((minOrMax == MIN ? "dMinimum" : "dMaximum"), this); } final void refresh() { position = (minOrMax == MIN ? (int) Math.floor(devicePosition.getDMinimum() + 0.5) : (int) Math.floor(devicePosition.getDMaximum() + 0.5)); } /** TODO * @param e */ public void propertyChange(PropertyChangeEvent e) { refresh(); } @Override public boolean equals(Object o) { if (o instanceof HotLine) { HotLine h = (HotLine) o; return h.devicePosition == devicePosition && h.minOrMax == minOrMax; } return false; } @Override public int hashCode() { return minOrMax * devicePosition.hashCode(); } @Override public String toString() { return "{" + devicePosition.getDasName() + (minOrMax == MIN ? ", MIN, " : ", MAX, ") + position + "}"; } /** TODO * @return */ public DasDevicePosition getDevicePosition() { return devicePosition; } /** TODO * @return */ public int getMinOrMax() { return minOrMax; } } public static class Cell implements PropertyChangeListener { Rectangle rc; DasRow row; DasColumn column; Cell(DasRow row, DasColumn column) { this.row = row; this.column = column; rc = new Rectangle(); row.addPropertyChangeListener("dMinimum", this); row.addPropertyChangeListener("dMaximum", this); column.addPropertyChangeListener("dMinimum", this); column.addPropertyChangeListener("dMaximum", this); rc.x = (int) Math.floor(column.getDMinimum() + 0.5); rc.y = (int) Math.floor(row.getDMinimum() + 0.5); rc.width = (int) Math.floor(column.getDMaximum() + 0.5) - rc.x; rc.height = (int) Math.floor(row.getDMaximum() + 0.5) - rc.y; } public void propertyChange(PropertyChangeEvent e) { if (e.getSource() == row) { rc.y = (int) Math.floor(row.getDMinimum() + 0.5); rc.height = (int) Math.floor(row.getDMaximum() + 0.5) - rc.y; } else { rc.x = (int) Math.floor(column.getDMinimum() + 0.5); rc.width = (int) Math.floor(column.getDMaximum() + 0.5) - rc.x; } } @Override public int hashCode() { int hash = 5; hash = 79 * hash + (this.row != null ? this.row.hashCode() : 0); hash = 79 * hash + (this.column != null ? this.column.hashCode() : 0); return hash; } @Override public boolean equals(Object o) { if (o instanceof Cell) { Cell box = (Cell) o; return box.row == row && box.column == column; } return false; } @Override public String toString() { return "{" + row.getDasName() + " x " + column.getDasName() + ": " + rc.toString() + "}"; } /** get the bounds * @return */ public Rectangle getCellBounds() { return new Rectangle(rc); } /** get the bounds * @param r * @return */ public Rectangle getCellBounds(Rectangle r) { if (r == null) { return getCellBounds(); } r.setBounds(rc); return r; } /** get the Row * @return */ public DasRow getRow() { return row; } /** get the Column * @return */ public DasColumn getColumn() { return column; } } /** * printingTag is the string to use to tag printed images. */ private String printingTag = "'UIOWA 'yyyyMMdd"; /** * printingTag is the DateFormat string to use to tag printed images. * @return Value of property printingTag. */ public String getPrintingTag() { return this.printingTag; } /** * printingTag is the string to use to tag printed images. * This can be 'yyyymmdd (SimpleDateFormat) or $Y$m$d, or just a string. * @param printingTag New value of property printingTag. */ public void setPrintingTag(String printingTag) { String old = this.printingTag; if ( printingTag.trim().length()>0 ) { if ( printingTag.contains("$Y") || printingTag.contains("$y") ) { TimeParser tp= TimeParser.create(printingTag); tp.format( TimeUtil.now(), TimeUtil.now() ); } else if ( printingTag.contains("'yy") ) { SimpleDateFormat dateFormat = new SimpleDateFormat(printingTag); dateFormat.format(new Date()); } else { // no timetag is allowed as well. } } this.printingTag = printingTag; firePropertyChange("printingTag", old, printingTag); } /** * if true if fonts will be fully rendered. */ private boolean textAntiAlias = true; /** * return true if fonts will be fully rendered. * @return true if fonts will be fully rendered. */ public boolean isTextAntiAlias() { return this.textAntiAlias; } /** * true if fonts will be fully rendered. * @param textAntiAlias true if fonts will be fully rendered. */ public void setTextAntiAlias(boolean textAntiAlias) { boolean old = this.textAntiAlias; this.textAntiAlias = textAntiAlias; firePropertyChange("textAntiAlias", old, textAntiAlias); } /** * true if data will be fully rendered with anti-aliasing. */ private boolean antiAlias = "on".equals(DasProperties.getInstance().get("antiAlias")); /** * true if data will be fully rendered with anti-aliasing. * @return true if data will be fully rendered with anti-aliasing. */ public boolean isAntiAlias() { return this.antiAlias; } /** * true if data will be fully rendered with anti-aliasing. * @param antiAlias if data will be fully rendered with anti-aliasing. */ public void setAntiAlias(boolean antiAlias) { boolean old = this.antiAlias; this.antiAlias = antiAlias; firePropertyChange("antiAlias", old, antiAlias); } private boolean fitted; /** * If true, and the canvas was added to a scrollpane, the canvas * will size itself to fit within the scrollpane. * * @return value of fitted property */ public boolean isFitted() { return fitted; } /** * If true, and the canvas was added to a scrollpane, the canvas * will size itself to fit within the scrollpane. * * @param fitted value of fitted property */ public void setFitted(boolean fitted) { logger.log(Level.CONFIG, "setFitted({0})", fitted); boolean oldValue = this.fitted; this.fitted = fitted; firePropertyChange("fitted", oldValue, fitted); revalidate(); } /** * make this the current canvas */ public final void makeCurrent() { currentCanvas= this; } @Override public Dimension getPreferredScrollableViewportSize() { return getPreferredSize(); } @Override public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.HORIZONTAL: return visibleRect.width / 10; case SwingConstants.VERTICAL: return visibleRect.height / 10; default: return 10; } } @Override public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { switch (orientation) { case SwingConstants.HORIZONTAL: return visibleRect.width; case SwingConstants.VERTICAL: return visibleRect.height; default: return 10; } } @Override public boolean getScrollableTracksViewportWidth() { return fitted; } @Override public boolean getScrollableTracksViewportHeight() { return fitted; } /** * ask the canvas if the particular change is already pending. * @return true if that particular change is pending. * @see ChangesSupport * @param lockObject an object identifying the change */ public boolean isPendingChanges( Object lockObject ) { return stateSupport.isPendingChanges(lockObject); } /** * indicate to the canvas that a change will be made soon. * For example, the canvas should wait for the change to be performed before creating an image. * @see ChangesSupport * @param client the client registering the change * @param lockObject an object identifying the change */ public void registerPendingChange(Object client, Object lockObject) { stateSupport.registerPendingChange(client, lockObject); } /** * indicate to the canvas that a change is being performed. * @see ChangesSupport * @param client the client registering the change * @param lockObject an object identifying the change */ public void performingChange(Object client, Object lockObject) { stateSupport.performingChange(client, lockObject); } /** * indicate to the canvas that a change is now complete. * @see ChangesSupport * @param client the client registering the change * @param lockObject an object identifying the change */ public void changePerformed(Object client, Object lockObject) { stateSupport.changePerformed(client, lockObject); } /** * returns true if there are changes pending. * @see ChangesSupport * @return true if there are changes pending. */ public boolean isPendingChanges() { return stateSupport.isPendingChanges(); } /** * access the lock for an atomic operation. * @see ChangesSupport * @return the lock. */ public Lock mutatorLock() { return stateSupport.mutatorLock(); } /** * returns true if an operation is being performed that should be treated as atomic. * @see ChangesSupport * @return true if an operation is being performed that should be treated as atomic. */ public boolean isValueAdjusting() { return stateSupport.isValueAdjusting(); } }