001/* 002 * BioJava development code 003 * 004 * This code may be freely distributed and modified under the 005 * terms of the GNU Lesser General Public Licence. This should 006 * be distributed with the code. If you do not have a copy, 007 * see: 008 * 009 * http://www.gnu.org/copyleft/lesser.html 010 * 011 * Copyright for this code is held jointly by the individual 012 * authors. These should be listed in @author doc comments. 013 * 014 * For more information on the BioJava project and its aims, 015 * or to join the biojava-l mailing list, visit the home page 016 * at: 017 * 018 * http://www.biojava.org/ 019 * 020 */ 021 022package org.biojava.bio.gui.sequence; 023 024import java.awt.Color; 025import java.awt.Graphics2D; 026import java.awt.Paint; 027import java.awt.Point; 028import java.awt.Rectangle; 029import java.awt.event.MouseEvent; 030import java.awt.geom.Line2D; 031import java.util.List; 032 033import javax.swing.SwingUtilities; 034 035import org.biojava.utils.AbstractChangeable; 036import org.biojava.utils.ChangeEvent; 037import org.biojava.utils.ChangeSupport; 038import org.biojava.utils.ChangeType; 039import org.biojava.utils.ChangeVetoException; 040 041/** 042 * <p> 043 * <code>CrosshairRenderer</code> draws a crosshair, optionally 044 * with coordinates. The crosshair is set to a sequence position by a 045 * click and then stays there through scrolls/rescales until the next 046 * click. See the <code>processMouseEvent</code> documentation for 047 * details of responses to various mouse actions. 048 * </p> 049 * 050 * @author Keith James 051 * @since 1.2 052 */ 053public class CrosshairRenderer extends AbstractChangeable 054 implements PairwiseSequenceRenderer 055{ 056 /** 057 * Constant <code>OUTLINE</code> indicating a change to the 058 * crosshair paint. 059 */ 060 public static final ChangeType OUTLINE = 061 new ChangeType("The outline paint has changed", 062 "org.biojava.bio.gui.sequence.CrosshairRenderer", 063 "OUTLINE", SequenceRenderContext.REPAINT); 064 065 /** 066 * <code>xHair</code> is the vertical line positioned along the 067 * X-axis. 068 */ 069 protected Line2D xHair; 070 071 /** 072 * <code>yHair</code> is the horizontal line positioned along the 073 * Y-axis. 074 */ 075 protected Line2D yHair; 076 /** 077 * <code>point</code> is the current location (in sequence 078 * coordinates) of the crosshair in the X and Y sequences. 079 */ 080 protected Point point; 081 082 // Crosshair location in sequence coordinates 083 private int sPosX, sPosY; 084 // Crosshair colour 085 private Paint outline; 086 // Display coordinates? 087 private boolean display; 088 089 /** 090 * Creates a new <code>CrosshairRenderer</code> in light grey with 091 * coordinates displayed. 092 */ 093 public CrosshairRenderer() 094 { 095 this(Color.lightGray); 096 } 097 098 /** 099 * Creates a new <code>CrosshairRenderer</code> of the specified 100 * colour, with coordinates displayed. 101 * 102 * @param outline a <code>Paint</code>. 103 */ 104 public CrosshairRenderer(Paint outline) 105 { 106 xHair = new Line2D.Double(); 107 yHair = new Line2D.Double(); 108 point = new Point(); 109 110 sPosX = 1; 111 sPosY = 1; 112 113 display = true; 114 115 this.outline = outline; 116 } 117 118 public void paint(Graphics2D g2, PairwiseRenderContext context) 119 { 120 Rectangle clip = g2.getClipBounds(); 121 122 double xMin = clip.getMinX(); 123 double xMax = clip.getMaxX(); 124 double yMin = clip.getMinY(); 125 double yMax = clip.getMaxY(); 126 127 // Offset to get the hair to line up with the centre of a 128 // residue 129 double residueCentre = context.getScale() * 0.5; 130 131 double gPosX = context.sequenceToGraphics(sPosX); 132 gPosX += residueCentre; 133 134 double gPosY = context.secondarySequenceToGraphics(sPosY); 135 gPosY += residueCentre; 136 137 if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) 138 { 139 xHair.setLine(gPosX, yMin, gPosX, yMax); 140 yHair.setLine(xMin, gPosY, xMax, gPosY); 141 } 142 else 143 { 144 xHair.setLine(xMin, gPosY, xMax, gPosY); 145 yHair.setLine(gPosX, yMin, gPosX, yMax); 146 } 147 148 g2.setPaint(outline); 149 g2.draw(xHair); 150 g2.draw(yHair); 151 152 if (display) 153 { 154 g2.setFont(context.getFont()); 155 g2.drawString(sPosX + ", " + sPosY, 156 (float) (gPosX + 5.0), 157 (float) (gPosY - 5.0)); 158 } 159 } 160 161 /** 162 * <code>coordinateDisplayOn</code> toggles the display of 163 * sequence coordinates. 164 * 165 * @param display a <code>boolean</code>. 166 */ 167 public void coordinateDisplayOn(boolean display) 168 { 169 this.display = display; 170 } 171 172 /** 173 * <code>getOutline</code> returns the colour used to draw the 174 * lines. 175 * 176 * @return a <code>Paint</code>. 177 */ 178 public Paint getOutline() 179 { 180 return outline; 181 } 182 183 /** 184 * <code>setOutline</code> sets the the colour used to draw the 185 * lines. 186 * 187 * @param outline a <code>Paint</code>. 188 * 189 * @exception ChangeVetoException if an error occurs. 190 */ 191 public void setOutline(Paint outline) throws ChangeVetoException 192 { 193 if (hasListeners()) 194 { 195 ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); 196 synchronized(cs) 197 { 198 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT, 199 null, null, 200 new ChangeEvent(this, OUTLINE, 201 outline, 202 this.outline)); 203 cs.firePreChangeEvent(ce); 204 this.outline = outline; 205 cs.firePostChangeEvent(ce); 206 } 207 } 208 else 209 { 210 this.outline = outline; 211 } 212 } 213 214 /** 215 * <p><code>processMouseEvent</code> processes any 216 * <code>MouseEvent</code>s directed to the renderer.</p> 217 * 218 * <p> 219 * Mouse actions are as follows (all are button-1 only): 220 * <ul> 221 * <li>Click sets the crosshair position and returns an event 222 * whose target object is the <code>Point</code> in sequence 223 * coordinates. The X coordinate is in the primary sequence, 224 * the Y coordinate is in the secondary sequence.</li> 225 * <li>Press same as Click</li> 226 * <li>Drag same as Click, except that the <code>Point</code> is 227 * not set</li> 228 * <li>Release same as Click, except that the <code>Point</code> 229 * is not set</li> 230 * <li>Move same as Click, except that the <code>Point</code> is 231 * not set and the target is null</li> 232 * </ul> 233 * </p> 234 * 235 * @param context a <code>PairwiseRenderContext</code>. 236 * @param me a <code>MouseEvent</code>. 237 * @param path a <code>List</code>. 238 * 239 * @return a <code>SequenceViewerEvent</code>. 240 */ 241 public SequenceViewerEvent processMouseEvent(PairwiseRenderContext context, 242 MouseEvent me, 243 List path) 244 { 245 path.add(this); 246 247 // Only hit the point with left button 248 if (! SwingUtilities.isLeftMouseButton(me)) 249 return new SequenceViewerEvent(this, null, sPosX, me, path); 250 251 // Only hit the point with click/release/drag 252 int id = me.getID(); 253 if (! (id == MouseEvent.MOUSE_CLICKED || 254 id == MouseEvent.MOUSE_PRESSED || 255 id == MouseEvent.MOUSE_DRAGGED || 256 id == MouseEvent.MOUSE_RELEASED)) 257 return new SequenceViewerEvent(this, null, sPosX, me, path); 258 259 // Only move the point on clicks or presses 260 if (id == MouseEvent.MOUSE_CLICKED || 261 id == MouseEvent.MOUSE_PRESSED) 262 { 263 double gPosX, gPosY; 264 265 if (context.getDirection() == PairwiseRenderContext.HORIZONTAL) 266 { 267 gPosX = me.getPoint().getX(); 268 gPosY = me.getPoint().getY(); 269 } 270 else 271 { 272 gPosX = me.getPoint().getY(); 273 gPosY = me.getPoint().getX(); 274 } 275 276 sPosX = context.graphicsToSequence(gPosX); 277 sPosY = context.graphicsToSecondarySequence(gPosY); 278 279 // We were clicked, so set the point here 280 point.setLocation(sPosX, sPosY); 281 282 // Inform any REPAINT listeners that they need to repaint this 283 if (hasListeners()) 284 { 285 ChangeSupport cs = getChangeSupport(SequenceRenderContext.REPAINT); 286 synchronized(cs) 287 { 288 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.REPAINT); 289 cs.firePostChangeEvent(ce); 290 } 291 } 292 } 293 294 return new SequenceViewerEvent(this, point, sPosX, me, path); 295 } 296}