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