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.Font; 026import java.awt.Graphics2D; 027import java.awt.Paint; 028import java.awt.Rectangle; 029import java.awt.Shape; 030import java.awt.font.FontRenderContext; 031import java.awt.font.TextLayout; 032import java.awt.geom.AffineTransform; 033import java.awt.geom.Rectangle2D; 034import java.util.ArrayList; 035import java.util.Iterator; 036import java.util.List; 037 038import org.biojava.utils.AbstractChangeable; 039import org.biojava.utils.AssertionFailure; 040import org.biojava.utils.ChangeEvent; 041import org.biojava.utils.ChangeForwarder; 042import org.biojava.utils.ChangeSupport; 043import org.biojava.utils.ChangeType; 044import org.biojava.utils.ChangeVetoException; 045import org.biojava.utils.Changeable; 046 047 048/** 049 * Renderer which draws a track of sequence with a textual label. 050 * 051 * <p> 052 * <strong>Experimental:</strong> This should probably delegate the label-drawing to a little 053 * LabelRenderer interface, and have the option of rendering trailing as well as leading labels. 054 * </p> 055 * 056 * @author Kalle Naslund 057 * @author Thomas Down 058 * @author Matthew Pocock 059 * @author Andreas Prlic 060 * @since 1.3 061 */ 062 063 064public class LabelledSequenceRenderer extends AbstractChangeable implements SequenceRenderer { 065 // keeps track if this label renderer is selected or not 066 boolean selected = true; 067 // the sequence we are rendering the label for 068 SequenceRenderer seqRend = null; 069 // the area the label occupies 070 double width = 200; 071 double depth = 100; 072 // list that contains the text our lable should display 073 List labels = new ArrayList(); 074 // the area that mouse clicks should be caught on, set it to no area in the beginning 075 Rectangle mouseClickArea = new Rectangle( 0, 0, -1, -1 ); 076 // the render forwarder needed to handle ChangeEvents from the encapsuled renderer correctly 077 private transient ChangeForwarder rendererForwarder = null; 078 // the ChangeEvents this renderer can emit, due to certain things 079 public static final ChangeType RENDERER = new ChangeType( "The SequenceRenderer has been added or removed", 080 LabelledSequenceRenderer.class, 081 "RENDERER", 082 SequenceRenderContext.LAYOUT ); 083 084 public static final ChangeType VALUES = new ChangeType( "The label value has changed", 085 LabelledSequenceRenderer.class, 086 "VALUES", 087 SequenceRenderContext.REPAINT ); 088 089 090 Color fillColor = Color.white ; 091 Color textColor = Color.black ; 092 093 protected ChangeSupport generateChangeSupport() { 094 ChangeSupport cs = super.generateChangeSupport(); 095 rendererForwarder = new SequenceRenderer.RendererForwarder( this, cs ); 096 if( ( seqRend != null ) && ( seqRend instanceof Changeable ) ) { 097 Changeable c = ( Changeable ) seqRend; 098 c.addChangeListener( rendererForwarder, SequenceRenderContext.REPAINT ); 099 c.addChangeListener( rendererForwarder, SequenceRenderContext.LAYOUT ); // do i need this ? 100 } 101 102 return cs; 103 } 104 105 /** 106 * Creates new LabelledSequenceRenderer with default width and depth; 107 */ 108 109 public LabelledSequenceRenderer() { 110 } 111 112 public LabelledSequenceRenderer(String label, SequenceRenderer renderer) { 113 114 try { 115 addLabelString(label); 116 setRenderer(renderer); 117 } catch (ChangeVetoException e) { 118 throw new AssertionFailure("We should not have listeners yet", e); 119 } 120 } 121 122 /** 123 * Creates new LabelledSequenceRenderer with the specified width and depth. 124 */ 125 public LabelledSequenceRenderer( double minWidth, double minDepth ) { 126 width = minWidth; 127 depth = minDepth; 128 } 129 130 /** 131 * Set the child renderer responsible for drawing the contents of this track 132 */ 133 134 public void setRenderer( SequenceRenderer sR ) throws ChangeVetoException { // should transmit LAYOUT event 135 if( hasListeners() ) { 136 ChangeEvent ce = new ChangeEvent( this, RENDERER, sR, seqRend ); 137 ChangeSupport cs = getChangeSupport( RENDERER ); 138 139 synchronized( cs ) { 140 cs.firePreChangeEvent( ce ); 141 142 if( ( seqRend != null ) && ( seqRend instanceof Changeable ) ) { 143 Changeable c = ( Changeable )seqRend; 144 c.removeChangeListener( rendererForwarder ); 145 } 146 147 seqRend = sR; 148 149 if( seqRend instanceof Changeable ) { 150 Changeable c = ( Changeable )seqRend; 151 c.addChangeListener( rendererForwarder ); 152 } 153 154 cs.firePostChangeEvent( ce ); 155 } 156 } 157 else { 158 seqRend = sR; 159 } 160 161 162 } 163 164 /** 165 * Add a piece of text to this renderer's label 166 */ 167 168 public void addLabelString( String text ) throws ChangeVetoException { // should trigger REPAINT, as currently no layout change can be triggered by more text 169 if( hasListeners() ) { // LAYOUT if i later let text affect the size of the renderer etc 170 ChangeSupport cs = getChangeSupport( VALUES ); 171 ChangeEvent ce = new ChangeEvent( this, VALUES, text ); 172 173 synchronized( cs ) { 174 cs.firePreChangeEvent( ce ); 175 labels.add( text ); 176 cs.firePostChangeEvent( ce ); 177 } 178 179 } 180 else { 181 labels.add( text ); 182 } 183 } 184 185 /** 186 * Remove a piece of text from the label 187 */ 188 189 public void removeLabelString( String text ) throws ChangeVetoException { 190 if( hasListeners() ) { 191 ChangeSupport cs = getChangeSupport( VALUES ); 192 ChangeEvent ce = new ChangeEvent( this, VALUES, null, text ); 193 194 synchronized ( cs ) { 195 cs.firePreChangeEvent( ce ); 196 labels.remove( text ); 197 cs.firePostChangeEvent( ce ); 198 } 199 } 200 else { 201 labels.remove( text ); // should handle exceptions i guess ? 202 } 203 } 204 205 public void toggleSelectionStatus() throws ChangeVetoException { // should throw repaint i guess 206 boolean newStatus; 207 208 if( selected ) { 209 newStatus = false; 210 } 211 else { 212 newStatus = true; 213 } 214 215 if( hasListeners() ) { 216 ChangeSupport cs = getChangeSupport( VALUES ); 217 ChangeEvent ce = new ChangeEvent( this, VALUES ); // i cant just pass the bools here, i need to convert to objects 218 219 synchronized( cs ) { 220 cs.firePreChangeEvent( ce ); 221 selected = newStatus; 222 cs.firePostChangeEvent( ce ); 223 } 224 } 225 else { 226 selected = newStatus; 227 } 228 } 229 230 // SequenceRenderer interface implemented here 231 public double getMinimumLeader( SequenceRenderContext sRC ) { 232 return width + seqRend.getMinimumLeader( sRC ); 233 } 234 235 236 public double getMinimumTrailer( SequenceRenderContext sRC ) { 237 return seqRend.getMinimumTrailer( sRC ); 238 } 239 240 241 public double getDepth( SequenceRenderContext sRC ) { 242 return Math.max( depth , seqRend.getDepth( sRC ) ); 243 } 244 245 246 public SequenceViewerEvent processMouseEvent( org.biojava.bio.gui.sequence.SequenceRenderContext sRC, java.awt.event.MouseEvent mE, java.util.List path ) { 247 248 SequenceViewerEvent sVE; 249 250 path.add( this ); 251 if( mouseClickArea.contains( mE.getX(), mE.getY() )) { 252 sVE = new SequenceViewerEvent( this, this, 1, mE, path ); 253 } else { 254 if (sRC.getDirection() == SequenceRenderContext.VERTICAL) { 255 mE.translatePoint(0, -(int)width); 256 sVE = seqRend.processMouseEvent( sRC, mE, path ); 257 } else { 258 mE.translatePoint(-(int)width, 0); 259 sVE = seqRend.processMouseEvent( sRC, mE, path ); 260 } 261 } 262 263 return sVE; 264 } 265 266 267 /** Set the background color of the label area. 268 * */ 269 public void setFillColor(Color c){ 270 fillColor = c; 271 } 272 /** Get the background color of the label area. 273 * */ 274 public Color getFillColor(){ 275 return fillColor; 276 } 277 278 /** Set the color of the label text 279 * */ 280 public void setTextColor (Color c) { 281 textColor = c; 282 } 283 284 /** Get the color of the label text 285 * */ 286 public Color getTextColor() { 287 return textColor; 288 } 289 290 291 public void paint( Graphics2D g, SequenceRenderContext sRC ) { 292 Paint selectedFill = fillColor; 293 Paint textCol = textColor; 294 Paint notSelectedFill = g.getBackground(); 295 296 Shape originalClip = g.getClip(); 297 AffineTransform originalTransform = g.getTransform(); 298 299 Rectangle2D labelArea = new Rectangle2D.Double(); 300 Font font = sRC.getFont(); 301 FontRenderContext fRC = g.getFontRenderContext(); 302 303 304 if( sRC.getDirection() == SequenceRenderContext.VERTICAL ) { 305 // rotate the drawing 306 g.rotate( - java.lang.Math.PI / 2 ); 307 g.translate( -width, 0 ); 308 // set the area we should grab mouseclicks in, this could most 309 // likely be cached in som way 310 mouseClickArea.setRect( 0.0 , -width , depth, width ); 311 } 312 else { 313 // set the area that we grab mouseclicks in 314 mouseClickArea.setRect( -width, 0.0, width, depth ); 315 } 316 317 // all below this is the same for vertical or horizontal drawing, as i am 318 // just using the built in transformation stuff to get text and stuff 319 // at the right possition, as its the simple way of drawing text 320 321 // set cliping zone so we only can draw in our label area 322 labelArea.setRect( -width , 0.0, width, depth ); 323 g.clip( labelArea ); 324 325 326 // draw the label area 327 if( selected ) { 328 g.setPaint( selectedFill ); 329 } else { 330 g.setPaint( notSelectedFill ); 331 } 332 g.fill( labelArea ); 333 g.setPaint( textCol ); 334 335 // draw text 336 float drawPosY = 3; // initial positions for text placement 337 float drawPosX = (float) (5 - width); 338 339 // iterate over each string of the label 340 for( Iterator labelIterator = labels.iterator() ; labelIterator.hasNext() ; ) { 341 342 // get the current string, and setup the drawign crap for it 343 String currentString = ( String )labelIterator.next(); 344 TextLayout tLayout = new TextLayout( currentString, font, fRC ); 345 346 drawPosY += tLayout.getAscent(); 347 tLayout.draw( g, drawPosX, drawPosY ); 348 drawPosY += ( tLayout.getDescent() + tLayout.getLeading() ); 349 } 350 351 //g.setTransform( originalTransform ); 352 // reset the clip and transformation so the next renderer can draw its things 353 354 g.setTransform( originalTransform ); 355 g.setClip( originalClip ); 356 357 // have the renderer render 358 seqRend.paint( g, sRC ); 359 } 360} 361 362 363 364