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.BasicStroke; 025import java.awt.Color; 026import java.awt.Graphics2D; 027import java.awt.Paint; 028import java.awt.Stroke; 029import java.awt.event.MouseEvent; 030import java.io.Serializable; 031import java.util.Collection; 032import java.util.HashMap; 033import java.util.Iterator; 034import java.util.Map; 035import java.util.Set; 036 037import org.biojava.bio.seq.Feature; 038import org.biojava.bio.seq.FeatureFilter; 039import org.biojava.bio.seq.FeatureHolder; 040import org.biojava.bio.seq.OptimizableFilter; 041import org.biojava.utils.AbstractChangeable; 042import org.biojava.utils.ChangeEvent; 043import org.biojava.utils.ChangeSupport; 044import org.biojava.utils.ChangeType; 045import org.biojava.utils.ChangeVetoException; 046 047/** 048 * <p><code>AbstractBeadRenderer</code> is a an abstract base class 049 * for the creation of <code>FeatureRenderer</code>s which use a 050 * 'string of beads' metaphor for displaying features. Each subclass 051 * of <code>AbstractBeadRenderer</code> should override the abstract 052 * method <code>renderBead()</code> and provide the drawing routine 053 * for its particular bead type.</p> 054 * 055 * <p>A concrete <code>BeadFeatureRenderer</code> may render a series 056 * of features in more than one style by delegating to other 057 * <code>BeadFeatureRenderer</code>s for the additional style(s). This 058 * is achieved using the <code>setDelegateRenderer()</code> method 059 * which associates an <code>OptimizableFilter</code> with another 060 * <code>BeadFeatureRenderer</code>. Any feature accepted by the 061 * filter is rendered with that renderer, while the remainder are 062 * rendered by the current renderer.</p> 063 * 064 * @author Keith James 065 * @author Paul Seed 066 * @since 1.2 067 */ 068public abstract class AbstractBeadRenderer extends AbstractChangeable 069 implements BeadFeatureRenderer, Serializable 070{ 071 /** 072 * Constant <code>DISPLACEMENT</code> indicating a change to the 073 * Y-axis displacement of the features. 074 */ 075 public static final ChangeType DISPLACEMENT = 076 new ChangeType("The displacement of the features has changed", 077 "org.biojava.bio.gui.sequence.AbstractBeadRenderer", 078 "DISPLACEMENT", SequenceRenderContext.LAYOUT); 079 080 /** 081 * Constant <code>DEPTH</code> indicating a change to the depth of 082 * the renderer. 083 */ 084 public static final ChangeType DEPTH = 085 new ChangeType("The depth of the renderer has changed", 086 "org.biojava.bio.gui.sequence.AbstractBeadRenderer", 087 "DEPTH", SequenceRenderContext.LAYOUT); 088 089 /** 090 * Constant <code>OUTLINE</code> indicating a change to the 091 * outline paint of the features. 092 */ 093 public static final ChangeType OUTLINE = 094 new ChangeType("The outline of the features has changed", 095 "org.biojava.bio.gui.sequence.AbstractBeadRenderer", 096 "OUTLINE", SequenceRenderContext.REPAINT); 097 098 /** 099 * Constant <code>STROKE</code> indicating a change to the outline 100 * stroke of the features. 101 */ 102 public static final ChangeType STROKE = 103 new ChangeType("The stroke of the features has changed", 104 "org.biojava.bio.gui.sequence.AbstractBeadRenderer", 105 "STROKE", SequenceRenderContext.REPAINT); 106 107 /** 108 * Constant <code>FILL</code> indicating a change to the fill of 109 * the features. 110 */ 111 public static final ChangeType FILL = 112 new ChangeType("The fill of the features has changed", 113 "org.biojava.bio.gui.sequence.AbstractBeadRenderer", 114 "FILL", SequenceRenderContext.REPAINT); 115 116 protected double beadDepth; 117 protected double beadDisplacement; 118 protected Paint beadOutline; 119 protected Paint beadFill; 120 protected Stroke beadStroke; 121 122 // Map of FeatureFilter -> FeatureRenderer 123 protected Map delegates; 124 // Map of Feature -> FeatureRenderer 125 protected Map delegationCache; 126 127 /** 128 * Creates a new <code>AbstractBeadRenderer</code> with no 129 * delegates. It will render all features itself, using its own 130 * style settings. 131 */ 132 public AbstractBeadRenderer() 133 { 134 this(10.0f, 0.0f, Color.black, Color.black, new BasicStroke()); 135 } 136 137 /** 138 * Creates a new <code>AbstractBeadRenderer</code> object. 139 * 140 * @param beadDepth a <code>double</code>. 141 * @param beadDisplacement a <code>double</code>. 142 * @param beadOutline a <code>Paint</code>. 143 * @param beadFill a <code>Paint</code>. 144 * @param beadStroke a <code>Stroke</code>. 145 */ 146 public AbstractBeadRenderer(double beadDepth, 147 double beadDisplacement, 148 Paint beadOutline, 149 Paint beadFill, 150 Stroke beadStroke) 151 { 152 this.beadDepth = beadDepth; 153 this.beadDisplacement = beadDisplacement; 154 this.beadOutline = beadOutline; 155 this.beadFill = beadFill; 156 this.beadStroke = beadStroke; 157 158 delegates = new HashMap(); 159 delegationCache = new HashMap(); 160 } 161 162 /** 163 * <code>processMouseEvent</code> defines the behaviour on 164 * revieving a mouse event. 165 * 166 * @param holder a <code>FeatureHolder</code>. 167 * @param context a <code>SequenceRenderContext</code>. 168 * @param mEvent a <code>MouseEvent</code>. 169 * 170 * @return a <code>FeatureHolder</code>. 171 */ 172 public FeatureHolder processMouseEvent(FeatureHolder holder, 173 SequenceRenderContext context, 174 MouseEvent mEvent) 175 { 176 return holder; 177 } 178 179 /** 180 * <code>renderFeature</code> draws a feature using the supplied 181 * graphics context. The rendering may be delegated to another 182 * <code>FeatureRenderer</code> instance. 183 * 184 * @param g2 a <code>Graphics2D</code> context. 185 * @param f a <code>Feature</code> to render. 186 * @param context a <code>SequenceRenderContext</code> context. 187 */ 188 public void renderFeature(Graphics2D g2, 189 Feature f, 190 SequenceRenderContext context) 191 { 192 // Check the cache first 193 if (delegationCache.containsKey(f)) 194 { 195 // System.err.println("Used cache for: " + f); 196 197 BeadFeatureRenderer cachedRenderer = 198 (BeadFeatureRenderer) delegationCache.get(f); 199 200 cachedRenderer.renderBead(g2, f, context); 201 return; 202 } 203 204 for (Iterator di = delegates.keySet().iterator(); di.hasNext();) 205 { 206 FeatureFilter filter = (FeatureFilter) di.next(); 207 208 if (filter.accept(f)) 209 { 210 // System.err.println(filter + " accepted " + f); 211 212 FeatureRenderer delegate = 213 (FeatureRenderer) delegates.get(filter); 214 215 delegate.renderFeature(g2, f, context); 216 return; 217 } 218 } 219 220 delegationCache.put(f, this); 221 // System.err.println("Rendering: " + f); 222 renderBead(g2, f, context); 223 } 224 225 /** 226 * <code>setDelegateRenderer</code> associates an 227 * <code>OptimizableFilter</code> with a 228 * <code>BeadFeatureRenderer</code>. Any feature accepted by the 229 * filter will be passed to the associated renderer for 230 * drawing. The <code>OptimizableFilter</code>s should be disjoint 231 * with respect to each other (a feature may not be rendered more 232 * than once). 233 * 234 * @param filter an <code>OptimizableFilter</code>. 235 * @param renderer a <code>BeadFeatureRenderer</code>. 236 * 237 * @exception IllegalArgumentException if the filter is not 238 * disjoint with existing delegate filters. 239 */ 240 public void setDelegateRenderer(OptimizableFilter filter, 241 BeadFeatureRenderer renderer) 242 throws IllegalArgumentException 243 { 244 // Ensure the cache doesn't hide the new delegate 245 delegationCache.clear(); 246 247 Set delegateFilters = delegates.keySet(); 248 249 if (delegateFilters.size() == 0) 250 { 251 delegates.put(filter, renderer); 252 } 253 else 254 { 255 for (Iterator fi = delegateFilters.iterator(); fi.hasNext();) 256 { 257 OptimizableFilter thisFilter = (OptimizableFilter) fi.next(); 258 259 if (! thisFilter.isDisjoint(filter)) 260 { 261 throw new IllegalArgumentException("Failed to apply filter as it clashes with existing filter " 262 + thisFilter 263 + " (filters must be disjoint)"); 264 } 265 else 266 { 267 delegates.put(filter, renderer); 268 break; 269 } 270 } 271 } 272 } 273 274 /** 275 * <code>removeDelegateRenderer</code> removes any association 276 * of the given <code>OptimizableFilter</code> with a 277 * <code>BeadFeatureRenderer</code>. 278 * 279 * @param filter an <code>OptimizableFilter</code>. 280 */ 281 public void removeDelegateRenderer(OptimizableFilter filter) 282 { 283 // Ensure the cache doesn't hide the change of delegate 284 delegationCache.clear(); 285 delegates.remove(filter); 286 } 287 288 /** 289 * <code>getDepth</code> calculates the depth required by this 290 * renderer to display its beads. It recurses through its delegate 291 * renderers and returns the highest value. Concrete renderers 292 * should override this method and supply code to calculate their 293 * own depth. If a subclass needs to know the depth of its 294 * delegates (as is likely if it has any) they can call this 295 * method using <code>super.getDepth()</code>. 296 * 297 * @param context a <code>SequenceRenderContext</code>. 298 * 299 * @return a <code>double</code>. 300 */ 301 public double getDepth(SequenceRenderContext context) 302 { 303 Collection delegateRenderers = delegates.values(); 304 double maxDepth = 0.0; 305 306 if (delegateRenderers.size() == 0) 307 { 308 return maxDepth + 1.0; 309 } 310 else 311 { 312 for (Iterator ri = delegateRenderers.iterator(); ri.hasNext();) 313 { 314 maxDepth = Math.max(maxDepth, ((FeatureRenderer) ri.next()).getDepth(context)); 315 } 316 317 return maxDepth + 1.0; 318 } 319 } 320 321 /** 322 * <code>getBeadDepth</code> returns the depth of a single bead 323 * produced by this renderer. 324 * 325 * @return a <code>double</code>. 326 */ 327 public double getBeadDepth() 328 { 329 return beadDepth; 330 } 331 332 /** 333 * <code>setBeadDepth</code> sets the depth of a single bead 334 * produced by this renderer. 335 * 336 * @param depth a <code>double</code>. 337 * 338 * @exception ChangeVetoException if an error occurs. 339 */ 340 public void setBeadDepth(double depth) throws ChangeVetoException 341 { 342 if (hasListeners()) 343 { 344 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 345 synchronized(cs) 346 { 347 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT, 348 null, null, 349 new ChangeEvent(this, DEPTH, 350 new Double(beadDepth), 351 new Double(depth))); 352 cs.firePreChangeEvent(ce); 353 beadDepth = depth; 354 cs.firePostChangeEvent(ce); 355 } 356 } 357 else 358 { 359 beadDepth = depth; 360 } 361 } 362 363 /** 364 * <code>getBeadDisplacement</code> returns the displacement of 365 * beads from the centre line of the renderer. A positive value 366 * indicates displacment downwards (for horizontal renderers) or 367 * to the right (for vertical renderers). 368 * 369 * @return a <code>double</code>. 370 */ 371 public double getBeadDisplacement() 372 { 373 return beadDisplacement; 374 } 375 376 /** 377 * <code>setBeadDisplacement</code> sets the displacement of 378 * beads from the centre line of the renderer. A positive value 379 * indicates displacment downwards (for horizontal renderers) or 380 * to the right (for vertical renderers). 381 * 382 * @param displacement a <code>double</code>. 383 * 384 * @exception ChangeVetoException if an error occurs. 385 */ 386 public void setBeadDisplacement(double displacement) 387 throws ChangeVetoException 388 { 389 if (hasListeners()) 390 { 391 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 392 synchronized(cs) 393 { 394 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT, 395 null, null, 396 new ChangeEvent(this, DISPLACEMENT, 397 new Double(beadDisplacement), 398 new Double(displacement))); 399 cs.firePreChangeEvent(ce); 400 beadDisplacement = displacement; 401 cs.firePostChangeEvent(ce); 402 } 403 } 404 else 405 { 406 beadDisplacement = displacement; 407 } 408 } 409 410 /** 411 * <code>getBeadOutline</code> returns the bead outline paint. 412 * 413 * @return a <code>Paint</code>. 414 */ 415 public Paint getBeadOutline() 416 { 417 return beadOutline; 418 } 419 420 /** 421 * <code>setBeadOutline</code> sets the bead outline paint. 422 * 423 * @param outline a <code>Paint</code>. 424 * 425 * @exception ChangeVetoException if an error occurs. 426 */ 427 public void setBeadOutline(Paint outline) throws ChangeVetoException 428 { 429 if (hasListeners()) 430 { 431 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 432 synchronized(cs) 433 { 434 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT, 435 null, null, 436 new ChangeEvent(this, OUTLINE, 437 outline, 438 beadOutline)); 439 cs.firePreChangeEvent(ce); 440 beadOutline = outline; 441 cs.firePostChangeEvent(ce); 442 } 443 } 444 else 445 { 446 beadOutline = outline; 447 } 448 } 449 450 /** 451 * <code>getBeadStroke</code> returns the bead outline stroke. 452 * 453 * @return a <code>Stroke</code>. 454 */ 455 public Stroke getBeadStroke() 456 { 457 return beadStroke; 458 } 459 460 /** 461 * <code>setBeadStroke</code> sets the bead outline stroke. 462 * 463 * @param stroke a <code>Stroke</code>. 464 * 465 * @exception ChangeVetoException if an error occurs. 466 */ 467 public void setBeadStroke(Stroke stroke) throws ChangeVetoException 468 { 469 if (hasListeners()) 470 { 471 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 472 synchronized(cs) 473 { 474 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT, 475 null, null, 476 new ChangeEvent(this, STROKE, 477 stroke, 478 beadStroke)); 479 cs.firePreChangeEvent(ce); 480 beadStroke = stroke; 481 cs.firePostChangeEvent(ce); 482 } 483 } 484 else 485 { 486 beadStroke = stroke; 487 } 488 } 489 490 /** 491 * <code>getBeadFill</code> returns the bead fill paint. 492 * 493 * @return a <code>Paint</code>. 494 */ 495 public Paint getBeadFill() 496 { 497 return beadFill; 498 } 499 500 /** 501 * <code>setBeadFill</code> sets the bead fill paint. 502 * 503 * @param fill a <code>Paint</code>. 504 * 505 * @exception ChangeVetoException if an error occurs. 506 */ 507 public void setBeadFill(Paint fill) throws ChangeVetoException 508 { 509 if (hasListeners()) 510 { 511 ChangeSupport cs = getChangeSupport(SequenceRenderContext.LAYOUT); 512 synchronized(cs) 513 { 514 ChangeEvent ce = new ChangeEvent(this, SequenceRenderContext.LAYOUT, 515 null, null, 516 new ChangeEvent(this, FILL, 517 fill, 518 beadFill)); 519 cs.firePreChangeEvent(ce); 520 beadFill = fill; 521 cs.firePostChangeEvent(ce); 522 } 523 } 524 else 525 { 526 beadFill = fill; 527 } 528 } 529 530 /** 531 * <code>renderBead</code> should be overridden by the concrete 532 * <code>BeadRenderer</code>. 533 * 534 * @param g2 a <code>Graphics2D</code>. 535 * @param f a <code>Feature</code> to render. 536 * @param context a <code>SequenceRenderContext</code> context. 537 */ 538 public abstract void renderBead(Graphics2D g2, 539 Feature f, 540 SequenceRenderContext context); 541}