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.Dimension; 025import java.awt.Font; 026import java.awt.Graphics; 027import java.awt.Graphics2D; 028import java.awt.Insets; 029import java.awt.RenderingHints; 030import java.awt.Shape; 031import java.awt.event.MouseAdapter; 032import java.awt.event.MouseEvent; 033import java.awt.event.MouseListener; 034import java.awt.event.MouseMotionListener; 035import java.awt.geom.AffineTransform; 036import java.awt.geom.Point2D; 037import java.awt.geom.Rectangle2D; 038import java.beans.PropertyChangeEvent; 039import java.beans.PropertyChangeListener; 040import java.util.ArrayList; 041 042import javax.swing.JComponent; 043 044import org.biojava.bio.seq.FeatureHolder; 045import org.biojava.bio.symbol.RangeLocation; 046import org.biojava.bio.symbol.SymbolList; 047import org.biojava.utils.ChangeAdapter; 048import org.biojava.utils.ChangeEvent; 049import org.biojava.utils.ChangeListener; 050import org.biojava.utils.ChangeSupport; 051import org.biojava.utils.ChangeType; 052import org.biojava.utils.ChangeVetoException; 053import org.biojava.utils.Changeable; 054 055/** 056 * <p><code>TranslatedSequencePanel</code> is a panel that displays a 057 * Sequence. Its features are that it will always draw at low pixel 058 * coordinates when using Java2D to render very long sequences and it 059 * is quite fast (approximately 8x faster than 060 * <code>SequencePanel</code></p>. 061 * 062 * <p>A <code>TranslatedSequencePanel</code> can either display the 063 * sequence from left-to-right (HORIZONTAL) or from top-to-bottom 064 * (VERTICAL). It has an associated scale which is the number of 065 * pixels per symbol and a translation which is the number of 066 * <code>Symbol</code>s to skip before rendering starts. In order to 067 * produce a scrolling effect, the <code>setSymbolTranslation</code> 068 * method may be hooked up to an <code>Adjustable</code> such as 069 * <code>JScrollBar</code> or to an event listener.</p> 070 * 071 * <p>The exact number of <code>Symbol</code>s rendered depends on the 072 * width of the panel and the scale. Resizing the panel will cause the 073 * number of <code>Symbol</code>s rendered to change accordingly.</p> 074 * 075 * <p>The panel will fill its background to the <code>Color</code> 076 * defined by the <code>setBackground()</code> method provided that it 077 * has been defined as opaque using <code>setOpaque()</code>.</p> 078 * 079 * <p>The change event handling code is based on the original panel 080 * and other BioJava components by Matthew and Thomas.</p> 081 * 082 * @author Keith James 083 * @author Matthew Pocock 084 * @author Thomas Down 085 * @author Jolyon Holdstock 086 * @since 1.2 087 */ 088public class TranslatedSequencePanel extends JComponent 089 implements SequenceRenderContext, Changeable 090{ 091 /** 092 * Generated Serial Version UID 093 */ 094 private static final long serialVersionUID = 3269477379497205817L; 095 096 /** 097 * Constant <code>RENDERER</code> is a <code>ChangeType</code> 098 * which indicates a change to the renderer, requiring a layout 099 * update. 100 */ 101 public static final ChangeType RENDERER = 102 new ChangeType("The renderer for this TranslatedSequencePanel has changed", 103 "org.biojava.bio.gui.sequence.TranslatedSequencePanel", 104 "RENDERER", SequenceRenderContext.LAYOUT); 105 106 /** 107 * Constant <code>TRANSLATION</code> is a <code>ChangeType</code> 108 * which indicates a change to the translation, requiring a paint 109 * update. 110 */ 111 public static final ChangeType TRANSLATION = 112 new ChangeType("The translation for this TranslatedSequencePanel has changed", 113 "org.biojava.bio.gui.sequence.TranslatedSequencePanel", 114 "TRANSLATION", SequenceRenderContext.REPAINT); 115 116 // The sequence to be rendered 117 private SymbolList sequence; 118 // The number of residues to skip before starting to render 119 private int translation; 120 // The rendering direction (HORIZONTAL or VERTICAL) 121 private int direction; 122 // The rendering scale in pixels per residue 123 private double scale; 124 // The sequence renderer 125 private SequenceRenderer renderer; 126 // The total border size of the renderer. Cached to avoid 127 // recursive method calls between us and the renderer. 128 private double rendererBorders; 129 // RenderingHints to be used by renderers 130 private RenderingHints hints; 131 132 // The rendering context borders 133 private SequenceRenderContext.Border leadingBorder; 134 private SequenceRenderContext.Border trailingBorder; 135 136 // Listens for bound property changes which require a repaint 137 // afterwards 138 private PropertyChangeListener propertyListener = 139 new PropertyChangeListener() 140 { 141 public void propertyChange(PropertyChangeEvent pce) 142 { 143 repaint(); 144 } 145 }; 146 147 // ChangeSupport helper for BioJava ChangeListeners 148 private transient ChangeSupport changeSupport = null; 149 150 // Listens for BioJava changes which will require a repaint 151 // afterwards 152 private ChangeListener repaintListener = new ChangeAdapter() 153 { 154 public void postChange(ChangeEvent ce) 155 { 156 repaint(); 157 } 158 }; 159 160 // Listens for BioJava changes which will require a revalidation 161 // afterwards 162 private ChangeListener layoutListener = new ChangeAdapter() 163 { 164 public void postChange(ChangeEvent ce) 165 { 166 revalidate(); 167 } 168 }; 169 170 // SequenceViewerSupport helper for BioJava SequenceViewerListeners 171 private SequenceViewerSupport svSupport = new SequenceViewerSupport(); 172 173 // Listens for mouse click events 174 private MouseListener mouseListener = new MouseAdapter() 175 { 176 public void mouseClicked(MouseEvent me) 177 { 178 if (! isActive()) 179 return; 180 181 Insets insets = getInsets(); 182 me.translatePoint(-insets.left, -insets.top); 183 184 SequenceViewerEvent sve = 185 renderer.processMouseEvent(TranslatedSequencePanel.this, me, 186 new ArrayList()); 187 188 me.translatePoint(insets.left, insets.top); 189 svSupport.fireMouseClicked(sve); 190 } 191 192 public void mousePressed(MouseEvent me) 193 { 194 if (! isActive()) 195 return; 196 197 Insets insets = getInsets(); 198 me.translatePoint(-insets.left, -insets.top); 199 200 SequenceViewerEvent sve = 201 renderer.processMouseEvent(TranslatedSequencePanel.this, me, 202 new ArrayList()); 203 me.translatePoint(insets.left, insets.top); 204 svSupport.fireMousePressed(sve); 205 } 206 207 public void mouseReleased(MouseEvent me) 208 { 209 if (! isActive()) 210 return; 211 212 Insets insets = getInsets(); 213 me.translatePoint(-insets.left, -insets.top); 214 215 SequenceViewerEvent sve = 216 renderer.processMouseEvent(TranslatedSequencePanel.this, me, 217 new ArrayList()); 218 219 me.translatePoint(insets.left, insets.top); 220 svSupport.fireMouseReleased(sve); 221 } 222 }; 223 224 // SequenceViewerMotionSupport helper for BioJava 225 // SequenceViewerMotionListeners 226 private SequenceViewerMotionSupport svmSupport = 227 new SequenceViewerMotionSupport(); 228 229 // Listens for mouse movement events 230 private MouseMotionListener mouseMotionListener = new MouseMotionListener() 231 { 232 public void mouseDragged(MouseEvent me) 233 { 234 if (! isActive()) 235 return; 236 237 Insets insets = getInsets(); 238 me.translatePoint(-insets.left, -insets.top); 239 240 SequenceViewerEvent sve = 241 renderer.processMouseEvent(TranslatedSequencePanel.this, me, 242 new ArrayList()); 243 244 me.translatePoint(insets.left, insets.top); 245 svmSupport.fireMouseDragged(sve); 246 } 247 248 public void mouseMoved(MouseEvent me) 249 { 250 if (! isActive()) 251 return; 252 253 Insets insets = getInsets(); 254 me.translatePoint(-insets.left, -insets.top); 255 256 SequenceViewerEvent sve = 257 renderer.processMouseEvent(TranslatedSequencePanel.this, me, 258 new ArrayList()); 259 260 me.translatePoint(insets.left, insets.top); 261 svmSupport.fireMouseMoved(sve); 262 } 263 }; 264 265 /** 266 * Creates a new <code>TranslatedSequencePanel</code> with the 267 * default settings (direction HORIZONTAL, scale 10.0 pixels per 268 * symbol, symbol translation 0, leading border 0.0, trailing 269 * border 0.0, 12 point sanserif font). 270 */ 271 public TranslatedSequencePanel() 272 { 273 super(); 274 275 if (getFont() == null) 276 setFont(new Font("sanserif", Font.PLAIN, 12)); 277 278 direction = HORIZONTAL; 279 scale = 10.0; 280 translation = 0; 281 282 leadingBorder = new SequenceRenderContext.Border(); 283 trailingBorder = new SequenceRenderContext.Border(); 284 285 leadingBorder.setSize(0.0); 286 trailingBorder.setSize(0.0); 287 288 hints = new RenderingHints(null); 289 290 this.addPropertyChangeListener(propertyListener); 291 this.addMouseListener(mouseListener); 292 this.addMouseMotionListener(mouseMotionListener); 293 } 294 295 /** 296 * <code>getSequence</code> returns the entire 297 * <code>Sequence</code> currently being rendered. 298 * 299 * @return a <code>Sequence</code>. 300 */ 301 public SymbolList getSequence() 302 { 303 return sequence; 304 } 305 306 /** 307 * <code>setSequence</code> sets the <code>Sequence</code> to be 308 * rendered. 309 * 310 * @param sequence a <code>Sequence</code>. 311 */ 312 public void setSequence(SymbolList sequence) 313 { 314 SymbolList prevSequence = this.sequence; 315 316 // Remove out listener from the sequence, if necessary 317 if (prevSequence != null) 318 prevSequence.removeChangeListener(layoutListener); 319 320 this.sequence = sequence; 321 322 // Add our listener to the sequence, if necessary. Also update 323 // rendererBorders cache which can be affected by changes in 324 // sequence 325 if (sequence != null) 326 { 327 if (renderer != null) 328 rendererBorders = renderer.getMinimumLeader(this) 329 + renderer.getMinimumTrailer(this); 330 331 sequence.addChangeListener(layoutListener); 332 } 333 334 firePropertyChange("sequence", prevSequence, sequence); 335 resizeAndValidate(); 336 } 337 338 /** 339 * <code>getSymbols</code> returns all of the <code>Symbol</code>s 340 * belonging to the currently rendered <code>Sequence</code>. 341 * 342 * @return a <code>SymbolList</code>. 343 */ 344 public SymbolList getSymbols() 345 { 346 return sequence; 347 } 348 349 /** 350 * <code>getFeatures</code> returns all of the 351 * <code>Feature</code>s belonging to the currently rendered 352 * <code>Sequence</code>. 353 * 354 * @return a <code>FeatureHolder</code>. 355 */ 356 public FeatureHolder getFeatures() 357 { 358 if(sequence instanceof FeatureHolder) { 359 return (FeatureHolder) sequence; 360 } else { 361 return FeatureHolder.EMPTY_FEATURE_HOLDER; 362 } 363 } 364 365 /** 366 * <code>getRange</code> returns a <code>RangeLocation</code> 367 * representing the region of the sequence currently being 368 * rendered. This is calculated from the size of the 369 * <code>TranslatedSequencePanel</code>, minus its 370 * <code>SequenceRenderContext.Border</code>s and its delegate 371 * renderer borders (if any), the current rendering translation 372 * and the current scale. The value will therefore change when the 373 * <code>TranslatedSequencePanel</code> is resized or "scrolled" 374 * by changing the translation. 375 * 376 * @return a <code>RangeLocation</code>. 377 */ 378 public RangeLocation getRange() 379 { 380 int visibleSymbols = getVisibleSymbolCount(); 381 382 // This is a fudge as we have to return a RangeLocation, which 383 // can not have start == end 384 if (visibleSymbols == 0) 385 return new RangeLocation(translation + 1, 386 translation + 2); 387 else 388 return new RangeLocation(translation + 1, visibleSymbols); 389 } 390 391 /** 392 * <code>getDirection</code> returns the direction in which this 393 * context expects sequences to be rendered - HORIZONTAL or 394 * VERTICAL. 395 * 396 * @return an <code>int</code>. 397 */ 398 public int getDirection() 399 { 400 return direction; 401 } 402 403 /** 404 * <code>setDirection</code> sets the direction in which this 405 * context will render sequences - HORIZONTAL or VERTICAL. 406 * 407 * @param direction an <code>int</code>. 408 * 409 * @exception IllegalArgumentException if an error occurs. 410 */ 411 public void setDirection(int direction) throws IllegalArgumentException 412 { 413 if (direction != HORIZONTAL && direction != VERTICAL) 414 throw new IllegalArgumentException("Direction must be either HORIZONTAL or VERTICAL"); 415 416 int prevDirection = this.direction; 417 this.direction = direction; 418 419 firePropertyChange("direction", prevDirection, direction); 420 resizeAndValidate(); 421 } 422 423 /** 424 * <code>getScale</code> returns the scale in pixels per 425 * <code>Symbol</code>. 426 * 427 * @return a <code>double</code>. 428 */ 429 public double getScale() 430 { 431 return scale; 432 } 433 434 /** 435 * <code>setScale</code> sets the scale in pixels per 436 * <code>Symbol</code>. 437 * 438 * @param scale a <code>double</code>. 439 */ 440 public void setScale(double scale) 441 { 442 double prevScale = this.scale; 443 this.scale = scale; 444 445 firePropertyChange("scale", prevScale, scale); 446 resizeAndValidate(); 447 } 448 449 /** 450 * <code>getSymbolTranslation</code> returns the current 451 * translation in <code>Symbol</code>s which will be applied when 452 * rendering. The sequence will be rendered, immediately after any 453 * borders, starting at this translation. Values may be from 0 to 454 * the length of the rendered sequence. 455 * 456 * @return an <code>int</code>. 457 */ 458 public int getSymbolTranslation() 459 { 460 return translation; 461 } 462 463 /** 464 * <code>setSymbolTranslation</code> sets the translation in 465 * <code>Symbol</code>s which will be applied when rendering. The 466 * sequence will be rendered, immediately after any borders, 467 * starting at that translation. Values may be from 0 to the 468 * length of the rendered sequence. 469 * 470 * @param translation an <code>int</code>. 471 * 472 * @exception IndexOutOfBoundsException if the translation is 473 * greater than the sequence length. 474 */ 475 public void setSymbolTranslation(int translation) 476 throws IndexOutOfBoundsException 477 { 478 if (translation >= sequence.length()) 479 throw new IndexOutOfBoundsException("Tried to set symbol translation offset equal to or greater than SymbolList length"); 480 481 int prevTranslation = this.translation; 482 483 if (hasListeners()) 484 { 485 ChangeSupport cs = getChangeSupport(TRANSLATION); 486 487 ChangeEvent ce = new ChangeEvent(this, TRANSLATION); 488 489 cs.firePostChangeEvent(ce); 490 this.translation = translation; 491 cs.firePostChangeEvent(ce); 492 } 493 else 494 { 495 this.translation = translation; 496 } 497 498 firePropertyChange("translation", prevTranslation, translation); 499 resizeAndValidate(); 500 } 501 502 /** 503 * <code>getLeadingBorder</code> returns the leading border. 504 * 505 * @return a <code>SequenceRenderContext.Border</code>. 506 */ 507 public SequenceRenderContext.Border getLeadingBorder() 508 { 509 return leadingBorder; 510 } 511 512 /** 513 * <code>getTrailingBorder</code> returns the trailing border. 514 * 515 * @return a <code>SequenceRenderContext.Border</code>. 516 */ 517 public SequenceRenderContext.Border getTrailingBorder() 518 { 519 return trailingBorder; 520 } 521 522 /** 523 * <code>getRenderer</code> returns the current 524 * <code>SequenceRenderer</code>. 525 * 526 * @return a <code>SequenceRenderer</code>. 527 */ 528 public SequenceRenderer getRenderer() 529 { 530 return renderer; 531 } 532 533 /** 534 * <code>setRenderer</code> sets the current 535 * <code>SequenceRenderer</code>. 536 * 537 * @param renderer set the <code>SequenceRenderer</code> used 538 */ 539 public void setRenderer(SequenceRenderer renderer) 540 throws ChangeVetoException 541 { 542 if (! isActive()) 543 _setRenderer(renderer); 544 545 if (hasListeners()) 546 { 547 ChangeSupport cs = getChangeSupport(RENDERER); 548 549 // We are originating a change event so use this 550 // constructor 551 ChangeEvent ce = new ChangeEvent(this, RENDERER, 552 renderer, this.renderer); 553 554 synchronized(cs) 555 { 556 cs.firePreChangeEvent(ce); 557 _setRenderer(renderer); 558 cs.firePostChangeEvent(ce); 559 } 560 } 561 else 562 { 563 _setRenderer(renderer); 564 } 565 resizeAndValidate(); 566 } 567 568 /** 569 * <code>getRenderingHints</code> returns the 570 * <code>RenderingHints</code> currently being used by the 571 * <code>Graphics2D</code> instances of delegate renderers. If 572 * none is set, the constructor creates one with a null 573 * <code>Map</code>. 574 * 575 * @return a <code>RenderingHints</code>. 576 */ 577 public RenderingHints getRenderingHints() 578 { 579 return hints; 580 } 581 582 /** 583 * <code>setRenderingHints</code> sets the 584 * <code>RenderingHints</code> which will be used by the 585 * <code>Graphics2D</code> instances of delegate renderers. 586 * 587 * @param hints a <code>RenderingHints</code>. 588 */ 589 public void setRenderingHints(RenderingHints hints) 590 { 591 RenderingHints prevHints = this.hints; 592 this.hints = hints; 593 594 firePropertyChange("hints", prevHints, hints); 595 } 596 597 /** 598 * <code>sequenceToGraphics</code> converts a sequence index to a 599 * graphical position. 600 * 601 * @param sequencePos an <code>int</code>. 602 * 603 * @return a <code>double</code>. 604 */ 605 public double sequenceToGraphics(int sequencePos) 606 { 607 return (sequencePos - translation - 1) * scale; 608 } 609 610 /** 611 * <code>graphicsToSequence</code> converts a graphical position 612 * to a sequence index. 613 * 614 * @param graphicsPos a <code>double</code>. 615 * 616 * @return an <code>int</code>. 617 */ 618 public int graphicsToSequence(double graphicsPos) 619 { 620 return ((int) (graphicsPos / scale)) + translation + 1; 621 } 622 623 /** 624 * <code>graphicsToSequence</code> converts a graphical position 625 * to a sequence index. 626 * 627 * @param point the <code>Point2D</code> to transform 628 * 629 * @return an <code>int</code>. 630 */ 631 public int graphicsToSequence(Point2D point) 632 { 633 if (direction == HORIZONTAL) 634 return graphicsToSequence(point.getX()); 635 else 636 return graphicsToSequence(point.getY()); 637 } 638 639 /** 640 * <code>getVisibleSymbolCount</code> returns the 641 * <strong>maximum</strong> number of <code>Symbol</code>s which 642 * can be rendered in the visible area (excluding all borders) of 643 * the <code>TranslatedSequencePanel</code> at the current 644 * scale. Note that if the translation is greater than 0, the 645 * actual number of <code>Symbol</code>s rendered will be less. 646 * 647 * @return an <code>int</code>. 648 */ 649 public int getVisibleSymbolCount() 650 { 651 // The BioJava borders 652 double totalBorders = leadingBorder.getSize() 653 + trailingBorder.getSize() + rendererBorders; 654 655 // The Insets 656 Insets insets = getInsets(); 657 658 int visible; 659 660 if (direction == HORIZONTAL) 661 { 662 int width = getWidth() - insets.left - insets.right; 663 664 if (width <= totalBorders) 665 return 0; 666 else 667 visible = width - (int) totalBorders; 668 } 669 else 670 { 671 int height = getHeight() - insets.top - insets.bottom; 672 673 if (height <= totalBorders) 674 return 0; 675 else 676 visible = height - (int) totalBorders; 677 } 678 679 return Math.min(graphicsToSequence(visible), sequence.length()); 680 } 681 682 /** 683 * <code>paintComponent</code> paints this component. 684 * 685 * @param g a <code>Graphics</code> object. 686 */ 687 public void paintComponent(Graphics g) 688 { 689 if (! isActive()) 690 return; 691 692 super.paintComponent(g); 693 694 // Set hints 695 Graphics2D g2 = (Graphics2D) g; 696 g2.addRenderingHints(hints); 697 698 // Save current transform and clip 699 AffineTransform prevTransform = g2.getTransform(); 700 Shape prevClip = g2.getClip(); 701 Insets insets = getInsets(); 702 703 // As we subclass JComponent we have to paint our own 704 // background, but only if we are opaque 705 if (isOpaque()) 706 { 707 g2.setPaint(getBackground()); 708 g2.fillRect(0, 0, getWidth(), getHeight()); 709 } 710 711 Rectangle2D.Double clip = new Rectangle2D.Double(); 712 if (direction == HORIZONTAL) { 713 // Clip x to edge of delegate renderer's leader 714 //clip.x = renderer.getMinimumLeader(this); 715 clip.x = 0 - renderer.getMinimumLeader(this); 716 clip.y = 0.0; 717 // Set the width to visible symbols + the delegate 718 // renderer's minimum trailer (which may have something in 719 // it to render). 720 clip.width = sequenceToGraphics(getVisibleSymbolCount() + 1) + 721 renderer.getMinimumLeader(this) + renderer.getMinimumTrailer(this); 722 clip.height = renderer.getDepth(this); 723 g2.translate(leadingBorder.getSize() - clip.x + insets.left, insets.top); 724 } 725 else 726 { 727 clip.x = 0.0; 728 // Clip y to edge of delegate renderer's leader 729 clip.y = renderer.getMinimumLeader(this); 730 clip.width = renderer.getDepth(this); 731 // Set the height to visible symbols + the delegate 732 // renderer's minimum trailer (which may have something in 733 // it to render). 734 clip.height = sequenceToGraphics(getVisibleSymbolCount() + 1) 735 + renderer.getMinimumTrailer(this); 736 737 g2.translate(insets.left, leadingBorder.getSize() + insets.top); 738 } 739 740 // Clip and paint 741 g2.clip(clip); 742 renderer.paint(g2, this); 743 744 // Restore 745 g2.setTransform(prevTransform); 746 g2.setClip(prevClip); 747 } 748 749 /** 750 * <code>resizeAndValidate</code> sets the minimum, preferred and 751 * maximum sizes of the component according to the current leading 752 * and trailing borders, renderer depth and visible symbol count. 753 */ 754 public void resizeAndValidate() 755 { 756 Dimension d = null; 757 758 if (! isActive()) 759 { 760 d = new Dimension(0, 0); 761 } 762 else 763 { 764 double width = sequenceToGraphics(getVisibleSymbolCount()) 765 + rendererBorders; 766 double depth = renderer.getDepth(this); 767 768 if (direction == HORIZONTAL) 769 { 770 d = new Dimension((int) Math.ceil(width), 771 (int) Math.ceil(depth)); 772 } 773 else 774 { 775 d = new Dimension((int) Math.ceil(depth), 776 (int) Math.ceil(width)); 777 } 778 } 779 780 setMinimumSize(d); 781 setPreferredSize(d); 782 setMaximumSize(d); 783 revalidate(); 784 } 785 786 /** 787 * <code>addChangeListener</code> adds a listener for all types of 788 * change. 789 * 790 * @param cl a <code>ChangeListener</code>. 791 */ 792 public void addChangeListener(ChangeListener cl) 793 { 794 addChangeListener(cl, ChangeType.UNKNOWN); 795 } 796 797 /** 798 * <code>addChangeListener</code> adds a listener for specific 799 * types of change. 800 * 801 * @param cl a <code>ChangeListener</code>. 802 * @param ct a <code>ChangeType</code>. 803 */ 804 public void addChangeListener(ChangeListener cl, ChangeType ct) 805 { 806 ChangeSupport cs = getChangeSupport(ct); 807 cs.addChangeListener(cl, ct); 808 } 809 810 /** 811 * <code>removeChangeListener</code> removes a listener. 812 * 813 * @param cl a <code>ChangeListener</code>. 814 */ 815 public void removeChangeListener(ChangeListener cl) 816 { 817 removeChangeListener(cl, ChangeType.UNKNOWN); 818 } 819 820 /** 821 * <code>removeChangeListener</code> removes a listener. 822 * 823 * @param cl a <code>ChangeListener</code>. 824 * @param ct a <code>ChangeType</code>. 825 */ 826 public void removeChangeListener(ChangeListener cl, ChangeType ct) 827 { 828 if (hasListeners()) 829 { 830 ChangeSupport cs = getChangeSupport(ct); 831 cs.removeChangeListener(cl, ct); 832 } 833 } 834 835 public boolean isUnchanging(ChangeType ct) 836 { 837 ChangeSupport cs = getChangeSupport(ct); 838 return cs.isUnchanging(ct); 839 } 840 841 /** 842 * <code>addSequenceViewerListener</code> adds a listener for 843 * mouse click <code>SequenceViewerEvent</code>s. 844 * 845 * @param svl a <code>SequenceViewerListener</code>. 846 */ 847 public void addSequenceViewerListener(SequenceViewerListener svl) 848 { 849 svSupport.addSequenceViewerListener(svl); 850 } 851 852 /** 853 * <code>removeSequenceViewerListener</code> removes a listener 854 * for mouse click <code>SequenceViewerEvent</code>s. 855 * 856 * @param svl a <code>SequenceViewerListener</code>. 857 */ 858 public void removeSequenceViewerListener(SequenceViewerListener svl) 859 { 860 svSupport.removeSequenceViewerListener(svl); 861 } 862 863 /** 864 * <code>addSequenceViewerMotionListener</code> adds a listener for 865 * mouse motion <code>SequenceViewerEvent</code>s. 866 * 867 * @param svml a <code>SequenceViewerMotionListener</code>. 868 */ 869 public void addSequenceViewerMotionListener(SequenceViewerMotionListener svml) 870 { 871 svmSupport.addSequenceViewerMotionListener(svml); 872 } 873 874 /** 875 * <code>addSequenceViewerMotionListener</code> removes a listener for 876 * mouse motion <code>SequenceViewerEvent</code>s. 877 * 878 * @param svml a <code>SequenceViewerMotionListener</code>. 879 */ 880 public void removeSequenceViewerMotionListener(SequenceViewerMotionListener svml) 881 { 882 svmSupport.removeSequenceViewerMotionListener(svml); 883 } 884 885 /** 886 * <code>getChangeSupport</code> lazily instantiates a helper for 887 * change listeners. 888 * 889 * @param ct a <code>ChangeType</code>. 890 * 891 * @return a <code>ChangeSupport</code> object. 892 */ 893 protected ChangeSupport getChangeSupport(ChangeType ct) 894 { 895 if (changeSupport != null) 896 { 897 return changeSupport; 898 } 899 900 synchronized(this) 901 { 902 if (changeSupport == null) 903 { 904 changeSupport = new ChangeSupport(); 905 } 906 907 return changeSupport; 908 } 909 } 910 911 /** 912 * <code>hasListeners</code> returns true if there are active 913 * listeners for BioJava events. 914 * 915 * @return a <code>boolean</code> value. 916 */ 917 protected boolean hasListeners() 918 { 919 return changeSupport != null; 920 } 921 922 /** 923 * <code>isActive</code> returns true if both the 924 * <code>Sequence</code> to be rendered and the 925 * <code>SequenceRenderer</code> are not null. 926 * 927 * @return a <code>boolean</code> value. 928 */ 929 protected boolean isActive() 930 { 931 return (sequence != null) && (renderer != null); 932 } 933 934 /** 935 * <code>_setRenderer</code> handles the details of listeners 936 * during changes. 937 * 938 * @param renderer a <code>SequenceRenderer</code>. 939 */ 940 private void _setRenderer(SequenceRenderer renderer) 941 { 942 // Remove our listeners from the old renderer, if necessary 943 if (this.renderer != null && 944 Changeable.class.isInstance(this.renderer)) 945 { 946 Changeable c = (Changeable) this.renderer; 947 948 c.removeChangeListener(layoutListener, SequenceRenderContext.LAYOUT); 949 c.removeChangeListener(repaintListener, SequenceRenderContext.REPAINT); 950 } 951 952 this.renderer = renderer; 953 954 // Update our cache of the renderer's total border size, but 955 // only is the sequence is not null. If the sequence was null 956 // this value is no longer correct, so we have to update 957 // rendererBorders in setSequence too. 958 if (sequence != null) 959 rendererBorders = renderer.getMinimumLeader(this) 960 + renderer.getMinimumTrailer(this); 961 962 // Add our listeners to the new renderer, if necessary 963 if (renderer != null && 964 Changeable.class.isInstance(renderer)) 965 { 966 Changeable c = (Changeable) renderer; 967 c.addChangeListener(layoutListener, SequenceRenderContext.LAYOUT); 968 c.addChangeListener(repaintListener, SequenceRenderContext.REPAINT); 969 } 970 } 971} 972