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.RenderingHints; 029import java.awt.Shape; 030import java.awt.event.MouseAdapter; 031import java.awt.event.MouseEvent; 032import java.awt.event.MouseListener; 033import java.awt.event.MouseMotionListener; 034import java.awt.geom.Point2D; 035import java.awt.geom.Rectangle2D; 036import java.beans.PropertyChangeEvent; 037import java.beans.PropertyChangeListener; 038import java.beans.PropertyChangeSupport; 039import java.io.Serializable; 040import java.util.ArrayList; 041import java.util.Arrays; 042 043import javax.swing.JComponent; 044import javax.swing.SwingConstants; 045 046import org.biojava.bio.seq.FeatureHolder; 047import org.biojava.bio.seq.Sequence; 048import org.biojava.bio.symbol.RangeLocation; 049import org.biojava.bio.symbol.SymbolList; 050import org.biojava.utils.ChangeAdapter; 051import org.biojava.utils.ChangeEvent; 052import org.biojava.utils.ChangeListener; 053import org.biojava.utils.ChangeSupport; 054import org.biojava.utils.ChangeType; 055import org.biojava.utils.ChangeVetoException; 056import org.biojava.utils.Changeable; 057 058/** 059 * A panel that displays a Sequence. 060 * <p> 061 * A SequencePoster can either display the sequence from left-to-right 062 * (HORIZONTAL) or from top-to-bottom (VERTICAL). It has an associated scale 063 * which is the number of pixels per symbol. It also has a lines property that 064 * controls how to wrap the sequence off one end and onto the other. 065 * <p> 066 * Each line in the SequencePoster is broken down into a list of strips, 067 * each rendered by an individual SequenceRenderer object. 068 * You could add a SequenceRenderer that draws on genes, another that 069 * draws repeats and another that prints out the DNA sequence. They are 070 * responsible for rendering their view of the sequence in the place that the 071 * SequencePoster positions them. 072 * 073 * @author Thomas Down 074 * @author Matthew Pocock 075 * @deprecated This doesn't handle loads of stuff. Use SequencePoster. 076 */ 077public class SequencePoster 078 extends 079 JComponent 080 implements 081 SwingConstants, 082 SequenceRenderContext, 083 Changeable 084{ 085 public static final ChangeType RENDERER = new ChangeType( 086 "The renderer for this SequencePoster has changed", 087 "org.biojava.bio.gui.sequence.SequencePoster", 088 "RENDERER", 089 SequenceRenderContext.LAYOUT 090 ); 091 092 private Sequence sequence; 093 private int direction; 094 private double scale; 095 private int lines; 096 private int spacer; 097 098 private SequenceRenderContext.Border leadingBorder; 099 private SequenceRenderContext.Border trailingBorder; 100 101 private SequenceRenderer renderer; 102 private double[] offsets; 103 private int realLines; 104 private double alongDim = 0.0; 105 private double acrossDim = 0.0; 106 private int symbolsPerLine = 0; 107 108 private RendererMonitor theMonitor; 109 private RenderingHints renderingHints = null; 110 111 private transient ChangeSupport changeSupport = null; 112 113 private SequenceViewerSupport svSupport = new SequenceViewerSupport(); 114 private MouseListener mouseListener = new MouseAdapter() { 115 public void mouseClicked(MouseEvent me) { 116 if(!isActive()) { 117 return; 118 } 119 int[] lineExtent = calcLineExtent(me); 120 me.translatePoint(-lineExtent[2], -lineExtent[3]); 121 SequenceViewerEvent sve = renderer.processMouseEvent( 122 new SubSequenceRenderContext( 123 SequencePoster.this, null, null, 124 new RangeLocation(lineExtent[0], lineExtent[1]) 125 ), 126 me, 127 new ArrayList() 128 ); 129 me.translatePoint(+lineExtent[2], +lineExtent[3]); 130 svSupport.fireMouseClicked(sve); 131 } 132 133 public void mousePressed(MouseEvent me) { 134 if(!isActive()) { 135 return; 136 } 137 int[] lineExtent = calcLineExtent(me); 138 me.translatePoint(-lineExtent[2], -lineExtent[3]); 139 SequenceViewerEvent sve = renderer.processMouseEvent( 140 new SubSequenceRenderContext( 141 SequencePoster.this, null, null, 142 new RangeLocation(lineExtent[0], lineExtent[1]) 143 ), 144 me, 145 new ArrayList() 146 ); 147 me.translatePoint(+lineExtent[2], +lineExtent[3]); 148 svSupport.fireMousePressed(sve); 149 } 150 151 public void mouseReleased(MouseEvent me) { 152 if(!isActive()) { 153 return; 154 } 155 int[] lineExtent = calcLineExtent(me); 156 me.translatePoint(-lineExtent[2], -lineExtent[3]); 157 SequenceViewerEvent sve = renderer.processMouseEvent( 158 new SubSequenceRenderContext( 159 SequencePoster.this, null, null, 160 new RangeLocation(lineExtent[0], lineExtent[1]) 161 ), 162 me, 163 new ArrayList() 164 ); 165 me.translatePoint(+lineExtent[2], +lineExtent[3]); 166 svSupport.fireMouseReleased(sve); 167 } 168 }; 169 public void addSequenceViewerListener(SequenceViewerListener svl) { 170 svSupport.addSequenceViewerListener(svl); 171 } 172 public void removeSequenceViewerListener(SequenceViewerListener svl) { 173 svSupport.removeSequenceViewerListener(svl); 174 } 175 176 private SequenceViewerMotionSupport svmSupport = new SequenceViewerMotionSupport(); 177 private MouseMotionListener mouseMotionListener = new MouseMotionListener() { 178 public void mouseDragged(MouseEvent me) { 179 if(!isActive()) { 180 return; 181 } 182 int[] lineExtent = calcLineExtent(me); 183 me.translatePoint(-lineExtent[2], -lineExtent[3]); 184 SequenceViewerEvent sve = renderer.processMouseEvent( 185 new SubSequenceRenderContext( 186 SequencePoster.this, null, null, 187 new RangeLocation(lineExtent[0], lineExtent[1]) 188 ), 189 me, 190 new ArrayList() 191 ); 192 me.translatePoint(+lineExtent[2], +lineExtent[3]); 193 svmSupport.fireMouseDragged(sve); 194 } 195 196 public void mouseMoved(MouseEvent me) { 197 if(!isActive()) { 198 return; 199 } 200 int[] lineExtent = calcLineExtent(me); 201 me.translatePoint(-lineExtent[2], -lineExtent[3]); 202 SequenceViewerEvent sve = renderer.processMouseEvent( 203 new SubSequenceRenderContext( 204 SequencePoster.this, null, null, 205 new RangeLocation(lineExtent[0], lineExtent[1]) 206 ), 207 me, 208 new ArrayList() 209 ); 210 me.translatePoint(+lineExtent[2], +lineExtent[3]); 211 svmSupport.fireMouseMoved(sve); 212 } 213 }; 214 public void addSequenceViewerMotionListener(SequenceViewerMotionListener svml) { 215 svmSupport.addSequenceViewerMotionListener(svml); 216 } 217 public void removeSequenceViewerMotionListener(SequenceViewerMotionListener svml) { 218 svmSupport.removeSequenceViewerMotionListener(svml); 219 } 220 221 protected boolean hasChangeListeners() { 222 return changeSupport != null; 223 } 224 225 protected ChangeSupport getChangeSupport(ChangeType ct) { 226 if(changeSupport != null) { 227 return changeSupport; 228 } 229 230 synchronized(this) { 231 if(changeSupport == null) { 232 changeSupport = new ChangeSupport(); 233 } 234 235 return changeSupport; 236 } 237 } 238 239 public void addChangeListener(ChangeListener cl) { 240 addChangeListener(cl, ChangeType.UNKNOWN); 241 } 242 243 public void addChangeListener(ChangeListener cl, ChangeType ct) { 244 ChangeSupport cs = getChangeSupport(ct); 245 cs.addChangeListener(cl, ct); 246 } 247 248 public void removeChangeListener(ChangeListener cl) { 249 removeChangeListener(cl, ChangeType.UNKNOWN); 250 } 251 252 public void removeChangeListener(ChangeListener cl, ChangeType ct) { 253 if(hasChangeListeners()) { 254 getChangeSupport(ct).removeChangeListener(cl, ct); 255 } 256 } 257 258 public boolean isUnchanging(ChangeType ct) { 259 return getChangeSupport(ct).isUnchanging(ct); 260 } 261 262 private ChangeListener layoutListener = new ChangeAdapter() { 263 public void postChange(ChangeEvent ce) { 264 resizeAndValidate(); 265 } 266 }; 267 private ChangeListener repaintListener = new ChangeAdapter() { 268 public void postChange(ChangeEvent ce) { 269 repaint(); 270 } 271 }; 272 273 /** 274 * Initializer. 275 */ 276 277 { 278 direction = HORIZONTAL; 279 scale = 12.0; 280 lines = 1; 281 spacer = 0; 282 283 theMonitor = new RendererMonitor(); 284 leadingBorder = new SequenceRenderContext.Border(); 285 trailingBorder = new SequenceRenderContext.Border(); 286 } 287 288 /** 289 * Create a new SeqeuncePanel. 290 */ 291 public SequencePoster() { 292 super(); 293 if(getFont() == null) { 294 setFont(new Font("Times New Roman", Font.PLAIN, 12)); 295 } 296 this.addPropertyChangeListener(theMonitor); 297 this.addMouseListener(mouseListener); 298 this.addMouseMotionListener(mouseMotionListener); 299 } 300 301 /** 302 * Set the SymboList to be rendered. This symbol list will be passed onto the 303 * SequenceRenderer instances registered with this SequencePoster. 304 * 305 * @param s the SymboList to render 306 */ 307 public void setSequence(Sequence s) { 308 Sequence oldSequence = sequence; 309 if(oldSequence != null) { 310 oldSequence.removeChangeListener(layoutListener); 311 } 312 this.sequence = s; 313 sequence.addChangeListener(layoutListener); 314 315 resizeAndValidate(); 316 firePropertyChange("sequence", oldSequence, s); 317 } 318 319 public Sequence getSequence() { 320 return sequence; 321 } 322 323 /** 324 * Retrieve the currently rendered SymbolList 325 * 326 * @return the current SymbolList 327 */ 328 public SymbolList getSymbols() { 329 return sequence; 330 } 331 332 public FeatureHolder getFeatures() { 333 return sequence; 334 } 335 336 public RangeLocation getRange() { 337 return new RangeLocation(1, sequence.length()); 338 } 339 340 public RangeLocation getVisibleRange() { 341 return getRange(); 342 } 343 344 /** 345 * Set the direction that this SequencePoster renders in. The direction can be 346 * one of HORIZONTAL or VERTICAL. Once the direction is set, the display will 347 * redraw. HORIZONTAL represents left-to-right rendering. VERTICAL represents 348 * AceDB-style vertical rendering. 349 * 350 * @param dir the new rendering direction 351 */ 352 public void setDirection(int dir) 353 throws IllegalArgumentException { 354 if(dir != HORIZONTAL && dir != VERTICAL) { 355 throw new IllegalArgumentException( 356 "Direction must be either HORIZONTAL or VERTICAL" 357 ); 358 } 359 int oldDirection = direction; 360 direction = dir; 361 resizeAndValidate(); 362 firePropertyChange("direction", oldDirection, direction); 363 } 364 365 /** 366 * Retrieve the current rendering direction. 367 * 368 * @return the rendering direction (one of HORIZONTAL and VERTICAL) 369 */ 370 public int getDirection() { 371 return direction; 372 } 373 374 /** 375 * Set the number of pixels to leave blank between each block of sequence 376 * information. 377 * <p> 378 * If the SeqeuncePanel chooses to display the sequence information split 379 * across multiple lines, then the spacer parameter indicates how many pixles 380 * will seperate each line. 381 * 382 * @param spacer the number of pixels seperating each line of sequence 383 * information 384 */ 385 public void setSpacer(int spacer) { 386 int oldSpacer = this.spacer; 387 this.spacer = spacer; 388 resizeAndValidate(); 389 firePropertyChange("spacer", oldSpacer, spacer); 390 } 391 392 /** 393 * Retrieve the current spacer value 394 * 395 * @return the number of pixels between each line of sequence information 396 */ 397 public int getSpacer() { 398 return spacer; 399 } 400 401 /** 402 * Set the scale. 403 * <p> 404 * The scale parameter is interpreted as the number of pixels per symbol. This 405 * may take on a wide range of values - for example, to render the symbols as 406 * text, you will need a scale of > 8, where as to render chromosome 1 you 407 * will want a scale < 0.00000001 408 * 409 * @param scale the new pixles-per-symbol ratio 410 */ 411 public void setScale(double scale) { 412 double oldScale = this.scale; 413 this.scale = scale; 414 resizeAndValidate(); 415 firePropertyChange("scale", oldScale, scale); 416 } 417 418 /** 419 * Retrieve the current scale. 420 * 421 * @return the number of pixles used to render one symbol 422 */ 423 public double getScale() { 424 return scale; 425 } 426 427 /** 428 * Set the absolute number of lines that the sequence will be rendered on. If 429 * this is set to 0, then the number of lines will be calculated according to 430 * how many lines will be needed to render the sequence in the currently 431 * available space. If it is set to any positive non-zero value, the sequence 432 * will be rendered using that many lines, and the SequencePoster will request 433 * enough space to accomplish this. 434 * 435 * @param lines the number of lines to split the sequence information over 436 */ 437 public void setLines(int lines) { 438 int oldLines = this.lines; 439 this.lines = lines; 440 resizeAndValidate(); 441 firePropertyChange("lines", oldLines, lines); 442 } 443 444 /** 445 * Retrieve the number of lines that the sequence will be rendered over. 446 * 447 * @return the current number of lines (0 if autocalculated) 448 */ 449 public int getLines() { 450 return lines; 451 } 452 453 /** 454 * Retrieve the object that encapsulates the leading border area - the space 455 * before sequence information is rendered. 456 * 457 * @return a SequenceRenderContext.Border instance 458 */ 459 public SequenceRenderContext.Border getLeadingBorder() { 460 return leadingBorder; 461 } 462 463 /** 464 * Retrieve the object that encapsulates the trailing border area - the space 465 * after sequence information is rendered. 466 * 467 * @return a SequenceRenderContext.Border instance 468 */ 469 public SequenceRenderContext.Border getTrailingBorder() { 470 return trailingBorder; 471 } 472 473 /** 474 * Paint this component. 475 * <p> 476 * This calls the paint method of the currently registered SequenceRenderer 477 * after setting up the graphics appropriately. 478 */ 479 public void paintComponent(Graphics g) { 480 if(!isActive()) { 481 return; 482 } 483 484 Graphics2D g2 = (Graphics2D) g; 485 if(renderingHints != null){ 486 g2.setRenderingHints(renderingHints); 487 } 488 489 Rectangle2D currentClip = g2.getClip().getBounds2D(); 490 double minPos; 491 double maxPos; 492 if(direction == HORIZONTAL) { 493 minPos = currentClip.getMinY(); 494 maxPos = currentClip.getMaxY(); 495 } else { 496 minPos = currentClip.getMinX(); 497 maxPos = currentClip.getMaxX(); 498 } 499 500 //System.out.println("minPos: " + minPos); 501 int minOffset = Arrays.binarySearch(offsets, minPos); 502 if(minOffset < 0) { 503 minOffset = -minOffset - 1; 504 } 505 //System.out.println("minOffset: " + minOffset); 506 double minCoord = (minOffset == 0) ? 0.0 : offsets[minOffset-1]; 507 //System.out.println("minCoord: " + minCoord); 508 int minP = 1 + (int) ((double) minOffset * symbolsPerLine); 509 //System.out.println("minP: " + minP); 510 511 Rectangle2D.Double clip = new Rectangle2D.Double(); 512 if (direction == HORIZONTAL) { 513 clip.width = alongDim; 514 clip.height = acrossDim; 515 g2.translate(leadingBorder.getSize() - alongDim * minOffset, minCoord); 516 } else { 517 clip.width = acrossDim; 518 clip.height = alongDim; 519 g2.translate(minCoord, leadingBorder.getSize() - alongDim * minOffset); 520 } 521 522 int min = minP; 523 for(int l = minOffset; l < realLines; l++) { 524 525 if (direction == HORIZONTAL) { 526 clip.x = l * alongDim; 527 clip.y = 0.0; 528 } else { 529 clip.x = 0.0; 530 clip.y = l * alongDim; 531 } 532 533 double depth = offsets[l] - spacer; 534 if(l != 0) { 535 depth -= offsets[l-1]; 536 } 537 538 if (direction == HORIZONTAL) { 539 clip.height = depth; 540 } else { 541 clip.width = depth; 542 } 543 544 Shape oldClip = g2.getClip(); 545 g2.clip(clip); 546 renderer.paint(g2, this); 547 g2.setClip(oldClip); 548 549 if (direction == HORIZONTAL) { 550 g2.translate(-alongDim, spacer + depth); 551 } else { 552 g2.translate(spacer + depth, -alongDim); 553 } 554 555 min += symbolsPerLine; 556 557 if( (min > sequence.length()) || (offsets[l] > maxPos)) { 558 break; 559 } 560 } 561 } 562 563 public void setRenderer(SequenceRenderer r) 564 throws ChangeVetoException { 565 if(hasChangeListeners()) { 566 ChangeEvent ce = new ChangeEvent( 567 this, 568 RENDERER, 569 r, 570 this.renderer 571 ); 572 ChangeSupport cs = getChangeSupport(RENDERER); 573 synchronized(cs) { 574 cs.firePreChangeEvent(ce); 575 _setRenderer(r); 576 cs.firePostChangeEvent(ce); 577 } 578 } else { 579 _setRenderer(r); 580 } 581 resizeAndValidate(); 582 } 583 584 protected void _setRenderer(SequenceRenderer r) { 585 if( (this.renderer != null) && (this.renderer instanceof Changeable) ) { 586 Changeable c = (Changeable) this.renderer; 587 c.removeChangeListener(layoutListener, SequenceRenderContext.LAYOUT); 588 c.removeChangeListener(repaintListener, SequenceRenderContext.REPAINT); 589 } 590 591 this.renderer = r; 592 593 if( (r != null) && (r instanceof Changeable) ) { 594 Changeable c = (Changeable) r; 595 c.addChangeListener(layoutListener, SequenceRenderContext.LAYOUT); 596 c.addChangeListener(repaintListener, SequenceRenderContext.REPAINT); 597 } 598 } 599 600 public double sequenceToGraphics(int seqPos) { 601 return ((double) (seqPos-1) * scale); 602 } 603 604 public int graphicsToSequence(double gPos) { 605 return (int) (gPos / scale) + 1; 606 } 607 608 public int graphicsToSequence(Point2D point) { 609 if(direction == HORIZONTAL) { 610 return graphicsToSequence(point.getX()); 611 } else { 612 return graphicsToSequence(point.getY()); 613 } 614 } 615 616 public void resizeAndValidate() { 617 //System.out.println("resizeAndValidate starting"); 618 Dimension d = null; 619 double acrossDim; 620 621 if(!isActive()) { 622 System.out.println("No sequence"); 623 // no sequence - collapse down to no size at all 624 alongDim = 0.0; 625 acrossDim = 0.0; 626 realLines = 0; 627 leadingBorder.setSize(0.0); 628 trailingBorder.setSize(0.0); 629 d = new Dimension(0, 0); 630 } else { 631 System.out.println("Fitting to sequence"); 632 633 int width; 634 Dimension parentSize = (getParent() != null) 635 ? getParent().getSize() 636 : new Dimension(500, 400); 637 if (direction == HORIZONTAL) { 638 width = parentSize.width; 639 } else { 640 width = parentSize.height; 641 } 642 643 System.out.println("Initial width: " + width); 644 // got a sequence - fit the size according to sequence length & preferred 645 // number of lines. 646 alongDim = scale * sequence.length(); 647 System.out.println("alongDim (pixles needed for sequence only): " 648 + alongDim); 649 acrossDim = 0.0; 650 651 double insetBefore = renderer.getMinimumLeader(this); 652 double insetAfter = renderer.getMinimumTrailer(this); 653 654 leadingBorder.setSize(insetBefore); 655 trailingBorder.setSize(insetAfter); 656 double insets = insetBefore + insetAfter; 657 //System.out.println("insetBefore: " + insetBefore); 658 //System.out.println("insetAfter: " + insetAfter); 659 660 if(lines > 0) { 661 // Fixed number of lines. Calculate width needed to lay out rectangle. 662 realLines = lines; 663 width = (int) Math.ceil( 664 insets + 665 alongDim / (double) lines 666 ); 667 } else { 668 // Calculated number of lines for a fixed width 669 double dWidth = (double) width; 670 dWidth -= insets; // leave space for insets 671 realLines = (int) Math.ceil(alongDim / (double) width); 672 width = (int) Math.ceil( 673 insets + 674 alongDim / (double) realLines 675 ); 676 } 677 678 acrossDim = 0.0; 679 symbolsPerLine = (int) Math.ceil((double) width / (double) scale); 680 //System.out.println("symbolsPerLine: " + symbolsPerLine); 681 //System.out.println("width: " + width); 682 //System.out.println("lines: " + lines); 683 //System.out.println("realLines: " + realLines); 684 if(symbolsPerLine < 1) { 685 throw new Error("Pants"); 686 } 687 int min = 1; 688 this.offsets = new double[realLines]; 689 int li = 0; 690 while(min <= sequence.length()) { 691 int max = min + symbolsPerLine - 1; 692 double depth = renderer.getDepth(this); 693 acrossDim += depth + spacer; 694 offsets[li] = acrossDim; 695 min = max + 1; 696 li++; 697 } 698 699 acrossDim += spacer * (realLines - 1); 700 alongDim = /* Math.ceil((double) width); */ symbolsPerLine * scale; 701 if (direction == HORIZONTAL) { 702 d = new Dimension( 703 (int) Math.ceil(alongDim + insetBefore + insetAfter), 704 (int) acrossDim 705 ); 706 } else { 707 d = new Dimension( 708 (int) acrossDim, 709 (int) Math.ceil(alongDim + insetBefore + insetAfter) 710 ); 711 } 712 } 713 714 setMinimumSize(d); 715 setPreferredSize(d); 716 revalidate(); 717 //System.out.println("resizeAndValidate ending"); 718 } 719 720 private class RendererMonitor implements PropertyChangeListener { 721 public void propertyChange(PropertyChangeEvent ev) { 722 repaint(); 723 } 724 } 725 726 protected int[] calcLineExtent(MouseEvent me) { 727 int pos; 728 if(direction == HORIZONTAL) { 729 pos = me.getY(); 730 } else { 731 pos = me.getX(); 732 } 733 734 int minOffset = Arrays.binarySearch(offsets, pos); 735 if(minOffset < 0) { 736 minOffset = -minOffset - 1; 737 } 738 int min = 1 + (int) ((double) minOffset * symbolsPerLine); 739 int max = min + symbolsPerLine - 1; 740 double minPos; 741 if(minOffset > 0) { 742 minPos = offsets[minOffset - 1]; 743 } else { 744 minPos = 0.0; 745 } 746 747 double ad = alongDim * minOffset; 748 749 int xdiff; 750 int ydiff; 751 if(direction == HORIZONTAL) { 752 xdiff = (int) -ad; 753 ydiff = (int) minPos; 754 } else { 755 xdiff = (int) minPos; 756 ydiff = (int) -ad; 757 } 758 759 return new int[] { min, max, xdiff, ydiff }; 760 } 761 762 protected boolean isActive() { 763 return 764 (sequence != null) && 765 (renderer != null); 766 } 767 768 /** 769 * @return the current rendering properties 770 */ 771 public RenderingHints getRenderingHints() { 772 return renderingHints; 773 } 774 /** 775 * Use this to switch on effects like Anti-aliasing etc 776 * @param renderingHints the desired rendering properties 777 */ 778 public void setRenderingHints(RenderingHints renderingHints) { 779 this.renderingHints = renderingHints; 780 } 781 782 public class Border 783 implements Serializable, SwingConstants { 784 protected final PropertyChangeSupport pcs; 785 private double size = 0.0; 786 private int alignment = CENTER; 787 788 public double getSize() { 789 return size; 790 } 791 792 public int getAlignment() { 793 return alignment; 794 } 795 796 public void setAlignment(int alignment) 797 throws IllegalArgumentException 798 { 799 if (alignment == LEADING || alignment == TRAILING || alignment == CENTER) { 800 int old = this.alignment; 801 this.alignment = alignment; 802 pcs.firePropertyChange("alignment", old, alignment); 803 } else { 804 throw new IllegalArgumentException( 805 "Alignment must be one of the constants LEADING, TRAILING or CENTER" 806 ); 807 } 808 } 809 810 private Border() { 811 alignment = CENTER; 812 pcs = new PropertyChangeSupport(this); 813 } 814 815 public void addPropertyChangeListener(PropertyChangeListener listener) { 816 pcs.addPropertyChangeListener(listener); 817 } 818 819 public void removePropertyChangeListener(PropertyChangeListener listener) { 820 pcs.removePropertyChangeListener(listener); 821 } 822 } 823} 824