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